Refactoring, part II (HTML::Seamstress)
authorMarius Gavrilescu <marius@ieval.ro>
Sun, 28 Sep 2014 06:10:18 +0000 (09:10 +0300)
committerMarius Gavrilescu <marius@ieval.ro>
Sun, 28 Sep 2014 06:10:18 +0000 (09:10 +0300)
16 files changed:
app.psgi
css/custom.css
lib/Plack/App/Gruntmaster.pm
lib/Plack/App/Gruntmaster/HTML.pm [new file with mode: 0644]
tmpl/ct.en
tmpl/ct_entry.en
tmpl/footer.en [deleted file]
tmpl/header.en [deleted file]
tmpl/log.en
tmpl/log_entry.en
tmpl/pb.en
tmpl/pb_entry.en
tmpl/skel.en [new file with mode: 0644]
tmpl/st.en
tmpl/us.en
tmpl/us_entry.en

index 927653e4212324118578bf9ed9f86409c56965e4..2a0c979701cc1d01f1e9e2ebcbb3abe71cd14358 100644 (file)
--- a/app.psgi
+++ b/app.psgi
@@ -6,6 +6,7 @@ use Gruntmaster::Data;
 use Plack::App::Gruntmaster;
 use Plack::Builder;
 use Plack::Request;
+use Plack::Util;
 use Digest::SHA qw/sha256/;
 use Log::Log4perl;
 use Tie::Hash::Expire;
@@ -20,7 +21,6 @@ tie my %auth, 'Tie::Hash::Expire', {expire_seconds => 300};
 
 sub authenticate {
        my ($user, $pass, $env) = @_;
-       say "Checking $user and $pass";
        my $key = sha256 "$user:$pass";
        $env->{'gruntmaster.user'} = $user;
        return 1 if exists $auth{$key};
@@ -28,6 +28,18 @@ sub authenticate {
        $auth{key} = 1;
 }
 
+sub add_headers {
+       my $app = $_[0];
+       sub {
+               my $resp = $app->($_[0]);
+               my $hdrs = Plack::Util::headers($resp->[1]);
+               $hdrs->set('Content-Security-Policy', CONTENT_SECURITY_POLICY);
+               $hdrs->set('Cache-Control', 'public, max-age=604800') if $_[0]->{PATH_INFO} =~ qr,^/static/,;
+               $resp->[1] = $hdrs->headers;
+               $resp;
+       }
+}
+
 Log::Log4perl->init('log.conf');
 my $access_logger = Log::Log4perl->get_logger('access');
 $ENV{DBIC_NULLABLE_KEY_NOWARN} = 1;
@@ -35,11 +47,10 @@ $ENV{DBIC_NULLABLE_KEY_NOWARN} = 1;
 builder {
        enable 'AccessLog', format => ACCESSLOG_FORMAT, logger => sub { $access_logger->info(@_) };
        enable 'ContentLength';
-       enable Header => set => ['Content-Security-Policy', CONTENT_SECURITY_POLICY];
-       enable_if { $_[0]->{PATH_INFO} =~ qr,^/static/,} Header => set => ['Cache-Control', 'public, max-age=604800'];
+       enable \&add_headers;
        enable 'Static', path => qr,^/static/,;
        enable 'Log4perl', category => 'plack';
-       enable_if { shift->{HTTP_WWW_AUTHENTICATE} } 'Auth::Basic', authenticator => \&authenticate, realm => 'Gruntmaster 6000';
+       enable_if { shift->{HTTP_AUTHORIZATION} } 'Auth::Basic', authenticator => \&authenticate, realm => 'Gruntmaster 6000';
        enable_if { $_[0]->{PATH_INFO} eq '/ok' } sub { sub{ [200, [], []] }};
        enable sub { my $app = $_[0]; sub { $_[0]->{'gruntmaster.dbic'} = $db; $app->($_[0]) } };
        Plack::App::Gruntmaster->run_if_script
index 13943543d72beec9ad91c8377a388673a5ca71ed..3c62f0140bd485b4cd86c29272c5dbd6f896c75d 100644 (file)
@@ -8,7 +8,6 @@ footer{
        width: 60em;
        margin: auto;
        margin-top: 6em;
-       white-space: pre-wrap;
        color: #777;
 }
 
@@ -59,4 +58,4 @@ a.dropdown-toggle{
 #clock{
        float: right;
        margin-right: 1em;
-}
\ No newline at end of file
+}
index 3c989ac10fe533058e1b3d12bcf219d68d7ffcb3..3c26ecfd0cbd69a9151fdb0ece3037eb60193be6 100644 (file)
@@ -4,53 +4,24 @@ use 5.014000;
 use strict;
 our $VERSION = '5999.000_001';
 
-use Apache2::Authen::Passphrase qw/pwcheck pwset/;
 use CSS::Minifier::XS;
+use Encode qw/encode decode/;
 use File::Slurp qw/read_file/;
 use JavaScript::Minifier::XS;
-
-use HTML::Template::Compiled;
+use JSON::MaybeXS qw/encode_json/;
 use PerlX::Maybe;
 use Scope::Upper qw/unwind SUB UP/;
+use Web::Simple;
 
 use Gruntmaster::Data;
-use Web::Simple;
-no warnings FATAL => 'all';
-use warnings;
-no warnings::illegalproto;
+use Plack::App::Gruntmaster::HTML;
+
+use warnings NONFATAL => 'all';
+no warnings 'illegalproto';
 no if $] >= 5.017011, warnings => 'experimental::smartmatch';
 
 ##################################################
 
-sub read_templates {
-       my $name = shift;
-
-       my %tmpl = map { m/\.(.+)$/; $1 => scalar read_file $_ } <tmpl/$name.*>;
-       my %arti = map { m/\.(.+)$/; $1 => scalar read_file $_ } <a/$name.*>;
-       return %tmpl ? %tmpl : %arti
-}
-
-my %header_templates = read_templates 'header';
-my %footer_templates = read_templates 'footer';
-my %templates;
-
-sub render {
-       my ($tmpl, $title, %params) = @_;
-       unless ($templates{$tmpl}) {
-               $templates{$tmpl} = { read_templates $tmpl };
-               for my $lang (keys $templates{$tmpl}) {
-                       my $header = $header_templates{$lang} =~ s/TITLE_GOES_HERE/$title/rg;
-                       $templates{$tmpl}{$lang} = $header . $templates{$tmpl}{$lang};
-               }
-               $templates{$tmpl}{$_} .= $footer_templates{$_} for keys $templates{$tmpl};
-
-       }
-
-       my $htc = HTML::Template::Compiled->new(scalarref => \$templates{$tmpl}{en}, default_escape => 'HTML', use_perl => 1);
-       $htc->param(%params);
-       [200, ['Content-Type' => 'text/html'], [$htc->output]]
-}
-
 use constant USER_REGEX => qr/^\w{2,20}$/a;
 
 use constant CONTENT_TYPES => +{
@@ -74,7 +45,7 @@ use constant FORMAT_EXTENSION => {
 };
 
 use constant NOT_FOUND => [404, ['Content-Type' => 'text/plain'], ['Not found']];
-use constant FORBIDDEN => [401, ['Content-Type' => 'text/plain', 'WWW-Authenticate' => ' Basic realm="Gruntmaster 6000"'], ['Forbidden']];
+use constant FORBIDDEN => [401, ['Content-Type' => 'text/plain', 'WWW-Authenticate' => 'Basic realm="Gruntmaster 6000"'], ['Forbidden']];
 
 my $env;
 
@@ -86,7 +57,7 @@ sub remote_user {
        $user
 }
 
-sub admin   { remote_user && remote_user->isadmin }
+sub admin   { remote_user && remote_user->admin }
 sub contest { db->contest ($_{contest}) }
 sub problem { db->problem ($_{problem}) }
 sub job     { db->job     ($_{job})     }
@@ -102,6 +73,7 @@ sub response {
        }
        bless {template => $template, title => $title, params => $params}, __PACKAGE__.'::Response'
 }
+
 sub forbid   {
        return if !shift || admin;
        unwind FORBIDDEN, SUB UP
@@ -149,8 +121,9 @@ sub dispatch_request{
                        response_filter {
                                my ($r) = @_;
                                return $r if ref $r ne 'Plack::App::Gruntmaster::Response';
-                               return [200, ['Content-Type' => 'application/json', 'X-Forever' => 1], [encode_json $r->{params}]] if $env->{HTTP_ACCEPT} =~ m,^\s*application/json\s*$,g;
-                               render $r->{template}, $r->{title}, %{$r->{params}}
+                               return [200, ['Content-Type' => 'application/json', 'X-Forever' => 1], [encode 'UTF-8', encode_json $r->{params}]] if $env->{HTTP_ACCEPT} =~ m,^\s*application/json\s*$,g;
+                               my $ret = render $r->{template}, 'en', title => $r->{title}, %{$r->{params}};
+                               [200, ['Content-Type' => 'text/html'], [encode 'UTF-8', $ret]]
                        },
                },
 
@@ -169,8 +142,8 @@ sub dispatch_request{
 
                sub (/us/)                                        { response us => 'Users', {us => db->user_list} },
                sub (/ct/  + ?:owner~)                            { response ct => 'Contests', db->contest_list(%_) },
-               sub (/log/ + ?:contest~&:owner~&:page~&:problem~) { response log => 'Job list', {log => db->job_list(%_)} },
-               sub (/pb/  + ?:owner~&:contest~)                  { response pb => 'Problems', db->problem_list(%_) },
+               sub (/log/ + ?:contest~&:owner~&:page~&:problem~) { response log => 'Job list', {%{db->job_list(%_)}, maybe contest => $_{contest}} },
+               sub (/pb/  + ?:owner~&:contest~)                  { response pb => 'Problems', {%{db->problem_list(%_)}, maybe contest => $_{contest}} },
 
                sub (/us/:user)    { response us_entry => user->name, db->user_entry($_{user}) },
                sub (/ct/:contest) { response ct_entry => contest->name, db->contest_entry($_{contest}) },
@@ -182,11 +155,7 @@ sub dispatch_request{
                },
 
                sub (/) { redispatch_to '/index' },
-
-               sub (/:article) {
-                       my $title = read_file "a/$_{article}.en.title";
-                       response $_{article} => $title, {};
-               }
+               sub (/:article) { [200, ['Content-Type' => 'text/html'], [render_article $_{article}, 'en']] }
        },
 
        sub (POST) {
@@ -210,16 +179,16 @@ sub dispatch_request{
                        reply 'Password changed successfully';
                },
 
-               sub (/action/submit + %:problem=&:contest~&prog_format=&private~ + *source_code=) {
+               sub (/action/submit + %:problem=&:contest~&:prog_format=&:private~&:source_code~ + *:prog~) {
                        forbid !remote_user;
-                       return reply 'This contest has finished' if contest->is_finished;
-                       return reply 'This contest has not yet started' if !admin && contest->is_pending;
-                       return reply 'Maximum source size is 10KB' if $_{source_code}->size > 25 * 1024;
+                       return reply 'This contest has finished' if contest && contest->is_finished;
+                       return reply 'This contest has not yet started' if !admin && contest && contest->is_pending;
+                       return reply 'Maximum source size is 10KB' if ($_{prog} ? $_{prog}->size : length $_{source_code}) > 10 * 1024;
                        return reply 'You must wait 30 seconds between jobs' if !admin && time <= remote_user->lastjob + 30;
                        remote_user->update({lastjob => time});
 
-                       my $prog = read_file $_{source_code}->path;
-                       unlink $_{source_code}->path;
+                       my $prog = $_{prog} ? read_file $_{prog}->path : $_{source_code};
+                       unlink $_{prog}->path if $_{prog};
                        db->jobs->create({
                                maybe contest => $_{contest},
                                maybe private => $_{private},
diff --git a/lib/Plack/App/Gruntmaster/HTML.pm b/lib/Plack/App/Gruntmaster/HTML.pm
new file mode 100644 (file)
index 0000000..16ae617
--- /dev/null
@@ -0,0 +1,204 @@
+package Plack::App::Gruntmaster::HTML;
+use v5.14;
+use parent qw/Exporter/;
+our @EXPORT = qw/render render_article/;
+
+use File::Slurp qw/read_file/;
+use HTML::Seamstress;
+use POSIX qw//;
+use Data::Dumper qw/Dumper/;
+
+sub ftime ($)   { POSIX::strftime '%c', localtime shift }
+sub literal ($) { HTML::Element::Library::super_literal shift // '' }
+
+sub HTML::Element::edit_href {
+       my ($self, $sub) = @_;
+       local $_ = $self->attr('href');
+       $sub->();
+       $self->attr(href => $_);
+}
+
+sub HTML::Element::iter3 {
+       my ($self, $data, $code) = @_;
+       my $orig = $self;
+       my $prev = $orig;
+       for my $el (@$data) {
+               my $current = $orig->clone;
+               $code->($el, $current);
+               $prev->postinsert($current);
+               $prev = $current;
+       }
+       $orig->detach;
+}
+
+sub HTML::Element::fid    { shift->look_down(id    => shift) }
+sub HTML::Element::fclass { shift->look_down(class => shift) }
+
+sub HTML::Element::namedlink {
+       my ($self, $id, $name) = @_;
+       $name = $id unless $name =~ /[[:graph:]]/;
+       $self = $self->find('a');
+       $self->edit_href(sub {s/id/$id/});
+       $self->replace_content($name);
+}
+
+sub render {
+       my ($tmpl, $lang, %args) = @_;
+       $lang //= 'en';
+       my $meat = _render($tmpl, $lang, %args);
+       _render('skel', $lang, %args, meat => $meat)
+}
+
+sub render_article {
+       my ($art, $lang) = @_;
+       $lang //= 'en';
+       my $title = read_file "a/$art.$lang.title";
+       my $meat  = read_file "a/$art.$lang";
+       _render('skel', $lang, title => $title , meat => $meat)
+}
+
+sub _render {
+       my ($tmpl, $lang, %args) = @_;
+       my $builder = HTML::Seamstress->new;
+       $builder->ignore_unknown(0);
+       my $tree = $builder->parse_file("tmpl/$tmpl.$lang");
+       $tree = $tree->guts unless $tmpl eq 'skel';
+       $tree->defmap(smap => \%args);
+       my $process = __PACKAGE__->can("process_$tmpl");
+       $process->($tree, %args) if $process;
+       $tree->as_HTML;
+}
+
+sub process_skel {
+       my ($tree, %args) = @_;
+       $tree->content_handler(
+               title   =>         $args{title},
+               content => literal $args{meat});
+}
+
+sub process_us_entry {
+       my ($tree, %args) = @_;
+       $tree->fid($_)->attr('href', "/$_/?owner=$args{id}") for qw/log pb/;
+}
+
+sub process_us {
+       my ($tree, %args) = @_;
+       my $item = $tree->fclass('list-group-item');
+       $item->replace_with(map {
+               my $new = $item->clone;
+               $new->attr(href => $_->{id});
+               $new->replace_content($_->{name} || $_->{id});
+               $new
+       } @{$args{us}});
+}
+
+sub process_ct_entry {
+       my ($tree, %args) = @_;
+       $_->edit_href (sub {s/contest_id/$args{id}/}) for $tree->find('a');
+       $tree->fid('links')->detach unless $args{started};
+       $tree->content_handler(
+               start       => ftime   $args{start},
+               stop        => ftime   $args{stop},
+               description => literal $args{description});
+}
+
+sub process_ct {
+       my ($tree, %args) = @_;
+       my $iter = sub {
+               my ($data, $tr) = @_;
+               $data->{$_} = ftime $data->{$_} for qw/start stop/;
+               $tr->hashmap(class => $data, [qw/name owner/]);
+               $tr->fclass('name')->namedlink($data->{id}, $data->{name});
+               $tr->fclass('owner')->namedlink($data->{owner}, $data->{owner_name});
+       };
+       $args{$_} ? $tree->fid($_)->find('tbody')->find('tr')->iter3($args{$_}, $iter) : $tree->fid($_)->detach for qw/running pending finished/;
+}
+
+sub process_pb_entry {
+       my ($tree, %args) = @_;
+       $tree->fid('owner')->edit_href(sub{s/owner_id/$args{owner}/});
+       $tree->fid('job_log')->edit_href(sub{s/problem_id/$args{id}/});
+       $tree->content_handler(
+               statement => literal $args{statement},
+               author    =>         $args{author},
+               owner     =>         $args{owner_name} || $args{owner});
+       if ($args{cansubmit}) {
+               $tree->look_down(name => 'problem')->attr(value => $args{id});
+               my $contest = $tree->look_down(name => 'contest');
+               $contest->attr(value => $args{contest}) if $args{contest};
+               $contest->detach unless $args{contest}
+       } else {
+               $tree->fid('submit')->detach
+       }
+}
+
+sub process_pb {
+       my ($tree, %args) = @_;
+       my $titer = sub {
+               my ($data, $tr) = @_;
+               $tr->set_child_content(class => 'author', $data->{author});
+               $tr->fclass('name')->namedlink($data->{id}, $data->{name});
+               $tr->fclass('name')->find('a')->edit_href(sub {$_ .= "?contest=$args{contest}"}) if $args{contest};
+               $tr->fclass('owner')->namedlink($data->{owner}, $data->{owner_name});
+       };
+       my $iter = sub {
+               my ($data, $div) = @_;
+               $div->attr(id => $data);
+               $div->find('h2')->replace_content(ucfirst $data);
+               $div->find('tbody')->iter3($args{$data}, $titer);
+       };
+       $tree->fid('beginner')->iter3([grep {$args{$_}} qw/beginner easy medium hard/], $iter);
+}
+
+sub process_log_entry {
+       my ($tree, %args) = @_;
+       $args{errors} ? $tree->fid('errors')->find('pre')->replace_content($args{errors}) : $tree->fid('errors')->detach;
+       my $iter = sub {
+               my ($data, $tr) = @_;
+               $data->{time} = sprintf '%.4fs', $data->{time};
+               $tr->defmap(class => $data);
+               $tr->fclass('result_text')->attr(class => "r$data->{result}")
+       };
+       @{$args{results}} ? $tree->fid('results')->find('tbody')->find('tr')->iter3($args{results}, $iter) : $tree->fid('results')->detach;
+}
+
+sub process_log {
+       my ($tree, %args) = @_;
+       my $iter = sub {
+               my ($data, $tr) = @_;
+               $tr->fclass('id')->namedlink($data->{id});
+               $tr->fclass('problem')->namedlink($data->{problem}, $data->{problem_name});
+               $tr->fclass('problem')->find('a')->edit_href(sub{$_ .= "?contest=$args{contest}"}) if $args{contest};
+               $tr->fclass('date')->replace_content(ftime $data->{date});
+               $tr->fclass('size')->namedlink("$data->{id}.$data->{extension}", sprintf "%.2fKB", $data->{size}/1024);
+               $tr->fclass('size')->attr('data-private', '') if $data->{private};
+               $tr->fclass('owner')->namedlink($data->{owner}, $data->{owner_name});
+               $tr->fclass('result_text')->replace_content($data->{result_text});
+               $tr->fclass('result_text')->attr(class => "r$data->{result}");
+       };
+       $tree->find('table')->find('tbody')->find('tr')->iter3($args{log}, $iter);
+       $args{next_page} ? $tree->fclass('next')->namedlink($args{next_page}, 'Next') : $tree->fclass('next')->attr(class => 'next disabled');
+       $args{previous_page} ? $tree->fclass('previous')->namedlink($args{previous_page}, 'Previous') : $tree->fclass('previous')->attr(class => 'previous disabled');
+       $tree->fclass('current')->replace_content("Page $args{current_page} of $args{last_page}");
+}
+
+sub process_st {
+       my ($tree, %args) = @_;
+       $args{problems} //= [];
+       my $pbiter = sub {
+               my ($data, $th) = @_;
+               $th->attr(class => undef);
+               $th->namedlink($data->id, $data->name);
+       };
+       $tree->fclass('problem')->iter3($args{problems}, $pbiter);
+       my $iter = sub {
+               my ($st, $tr) = @_;
+               $tr->set_child_content(class => 'rank', $st->{rank});
+               $tr->set_child_content(class => 'score', $st->{score});
+               $tr->fclass('user')->namedlink($st->{user}->id, $st->{user}->name);
+               my $pbscore = $tr->fclass('pbscore');
+               $pbscore->detach unless $st->{problems};
+               $pbscore->iter($pbscore => @{$st->{scores}});
+       };
+       $tree->find('tbody')->find('tr')->iter3($args{st}, $iter);
+}
index 9420708957ff66c8215ae2fd20aed42c2f4927c8..31642df37dc49c6c48d94c0967c3804d222892e2 100644 (file)
@@ -1,41 +1,35 @@
-<tmpl_if running>
+<div id="running">
 <h1>Running contests</h1>
 <table border class="table table-bordered table-striped">
 <thead>
 <tr><th>Name<th>Start date<th>Stop date<th>Owner
+</thead>
+
 <tbody>
-<tmpl_loop running><tr><td><a href="/ct/<tmpl_var id>"><tmpl_var name></a>
-<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{start}; %>
-<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{stop};  %>
-<td><a href="/us/<tmpl_var owner>"><tmpl_if owner_name><tmpl_var owner_name><tmpl_else><tmpl_var owner></tmpl_if></a>
-</tmpl_loop>
+<tr><td class="name"><a href="/ct/id">Contest name</a><td class="start">...<td class="stop">...<td class="owner"><a href="/us/id">Owner name</a>
 </table>
-</tmpl_if>
+</div>
 
-<tmpl_if pending>
+<div id="pending">
 <h1>Pending contests</h1>
 <table border class="table table-bordered table-striped">
 <thead>
 <tr><th>Name<th>Start date<th>Stop date<th>Owner
+</thead>
+
 <tbody>
-<tmpl_loop pending><tr><td><a href="/ct/<tmpl_var id>"><tmpl_var name></a>
-<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{start}; %>
-<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{stop};  %>
-<td><a href="/us/<tmpl_var owner>"><tmpl_if owner_name><tmpl_var owner_name><tmpl_else><tmpl_var owner></tmpl_if></a>
-</tmpl_loop>
+<tr><td class="name"><a href="/ct/id">Contest name</a><td class="start">...<td class="stop">...<td class="owner"><a href="/us/id">Owner name</a>
 </table>
-</tmpl_if>
+</div>
 
-<tmpl_if finished>
+<div id="finished">
 <h1>Finished contests</h1>
 <table border class="table table-bordered table-striped">
 <thead>
 <tr><th>Name<th>Start date<th>Stop date<th>Owner
+</thead>
+
 <tbody>
-<tmpl_loop finished><tr><td><a href="/ct/<tmpl_var id>"><tmpl_var name></a>
-<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{start}; %>
-<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{stop};  %>
-<td><a href="/us/<tmpl_var owner>"><tmpl_if owner_name><tmpl_var owner_name><tmpl_else><tmpl_var owner></tmpl_if></a>
-</tmpl_loop>
+<tr><td class="name"><a href="/ct/id">Contest name</a><td class="start">...<td class="stop">...<td class="owner"><a href="/us/id">Owner name</a>
 </table>
-</tmpl_if>
+</div>
index 731872b965783f534e451dcf3e242d14dc156966..6f0a5806cf9d86c80c73e5bc16859b35d22d0e11 100644 (file)
@@ -1,8 +1,12 @@
-Contest start time: <%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{start}; %><br>
-Contest stop  time: <%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{stop};  %><p>
+<dl>
+<dt>Contest start time</dt> <dd id="start">start</dd>
+<dt>Contest stop time</dt>  <dd id="stop">stop</dd>
+</dl>
 
-<tmpl_var ESCAPE=0 description>
+<div id="description">description</div>
 
-<tmpl_if started><a href="/pb/?contest=<tmpl_var id>">Problems</a><br>
-<a href="/log/?contest=<tmpl_var id>">Job log</a><br>
-<a href="/st/<tmpl_var id>">Standings</a></tmpl_if>
+<div id="links">
+<a href="/pb/?contest=contest_id">Problems</a><br>
+<a href="/log/?contest=contest_id">Job log</a><br>
+<a href="/st/contest_id">Standings</a>
+</div>
diff --git a/tmpl/footer.en b/tmpl/footer.en
deleted file mode 100644 (file)
index c0f928d..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<footer>
-Dilmom: Why don't you call your product the Gruntmaster 6000?
-Dilbert: What kind of product do you see when you imagine a Gruntmaster 6000?
-Dilmom: Well, it's a stripped-down version of the Gruntmaster 9000, of course. But it's software-upgradeable.
-</footer>
diff --git a/tmpl/header.en b/tmpl/header.en
deleted file mode 100644 (file)
index 0f5938b..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<title>TITLE_GOES_HERE</title>
-<meta charset="utf-8">
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
-<link rel="stylesheet" href="/css/cyborg.css" id="stylesheet">
-<script src="/js.js" type="text/javascript"></script>
-
-<nav class="navbar navbar-default navbar-static-top" role="navigation">
-<div class="container-fluid">
-<div class="navbar-header">
-<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button>
-<a class="navbar-brand" href="/">Gruntmaster 6000</a>
-</div>
-
-<div class="collapse navbar-collapse">
-<ul class="nav navbar-nav">
-<li><a href="/pb/">Problems</a>
-<li><a href="/ct/">Contests</a>
-<li><a href="/account">Account</a>
-</ul>
-
-<ul class="nav navbar-nav navbar-right">
-<li><a class="dropdown-toggle" data-toggle="dropdown"> Theme <span class="caret"></span></a>
-
-<ul class="dropdown-menu" role="menu">
-<li><a href="#" id="theme_slate">Gunmetal gray</a>
-<li><a href="#" id="theme_cyborg">Black</a>
-<li><a href="#" id="theme_cerulean">White</a>
-<li><a href="#" id="theme_cosmo">Metro</a>
-</ul>
-
-<li><a href="/log/">Job log</a>
-</ul>
-</div>
-</div>
-</nav>
-
-<iframe src="http://free.timeanddate.com/clock/i47sdccv/n49/tlro/fc99f/tct/pct/ta1" frameborder="0" id="clock" width="98" height="18" sandbox="allow-scripts allow-same-origin"></iframe>
-
-<div class="container-fluid">
-
-<h1 id="title">TITLE_GOES_HERE</h1>
-<div id="result"></div>
index 150916835ef44593d6f51161bb27c62e4831fb9c..eb4bf6f0926bd134a89bc577b875b5fc36ad8c6c 100644 (file)
@@ -1,17 +1,18 @@
 <table border class="table table-bordered table-striped">
-<thead>
-<tr><th>ID<th>Problem<th>Date<th>Size<th>User<th>Result
+<thead><tr><th>ID<th>Problem<th>Date<th>Size<th>User<th>Result</thead>
+
 <tbody>
-<tmpl_loop log><tr><td><a href="/log/<tmpl_var id>"><tmpl_var id></a>
-<td><a href="/pb/<tmpl_var problem>"><tmpl_var problem_name></a>
-<td><%perl __OUT__ POSIX::strftime '%c', localtime __CURRENT__->{date}; %>
-<td><a href="/src/<tmpl_var id>.<tmpl_var extension>"<tmpl_if private> data-private</tmpl_if>><%perl __OUT__ sprintf '%.2fKB', __CURRENT__->{size}/1024; %></a>
-<td><a href="/us/<tmpl_var owner>"><tmpl_if owner_name><tmpl_var owner_name><tmpl_else><tmpl_var user></tmpl_if></a>
-<td class="r<tmpl_var result>"><tmpl_var result_text>
+<tr><td class="id"><a href="/log/id">Job ID</a>
+<td class="problem"><a href="/pb/id">Problem name</a>
+<td class="date">Date
+<td class="size"><a href="/src/id">3.14KB</a>
+<td class="owner"><a href="/us/id">Owner name</a>
+<td class="result_text"><tmpl_var result_text>
 </tmpl_loop>
 </table>
 
 <ul class="pager">
-<%perl if (__CURRENT__->{page} > 1) { %><li class="previous"><a href="?page=<%perl __OUT__ __CURRENT__->{page} - 1; %>">Previous</a><%perl } %>
-<%perl if (__CURRENT__->{page} < __CURRENT__->{pages} - 1) { %><li class="next"><a href="?page=<%perl __OUT__ __CURRENT__->{page} + 1; %>">Next</a><%perl } %>
+<li class="previous"><a href="?page=id">Previous</a>
+<li class="current">Page 1 of 100
+<li class="next"><a href="?page=id">Next</a>
 </ul>
index adf6bfde3df7f1e85d98fc314bd19b23f178e7c7..eb05db8caae6796c1747dcc22e660d11a2c65acc 100644 (file)
@@ -1,15 +1,16 @@
-<tmpl_if errors>
+<div id="errors">
 <h2>Compiler output</h2>
-<pre><tmpl_var errors></pre>
-</tmpl_if>
+<pre></pre>
+</div>
 
-<tmpl_if results>
+<div id="results">
 <h2>Results</h2>
 <table border class="table table-border table-striped">
 <thead>
 <tr><th>Test number<th>Result<th>Time
+</thead>
+
 <tbody>
-<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>
+<tr><td class="id"><td class="result_text"><td class="time">
 </table>
-</tmpl_if>
\ No newline at end of file
+</div>
\ No newline at end of file
index 35cfbd418c9b9b9997c2846889496dca3caa5c06..ae1a0023722bac78047e646d637749b4d21e424f 100644 (file)
@@ -1,39 +1,11 @@
-<tmpl_if beginner>
+<div>
+<div id="beginner">
 <h2>Beginner</h2>
 <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_if owner_name><tmpl_var owner_name><tmpl_else><tmpl_var owner></tmpl_if></a>
-</tmpl_loop>
-</table>
-</tmpl_if>
-
-<tmpl_if easy>
-<h2>Easy</h2>
-<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_if owner_name><tmpl_var owner_name><tmpl_else><tmpl_var owner></tmpl_if></a>
-</tmpl_loop>
-</table>
-</tmpl_if>
+<thead><tr><th>Name<th>Author<th>Owner</thead>
 
-<tmpl_if medium>
-<h2>Medium</h2>
-<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_if owner_name><tmpl_var owner_name><tmpl_else><tmpl_var owner></tmpl_if></a>
-</tmpl_loop>
-</table>
-</tmpl_if>
-
-<tmpl_if hard>
-<h2>Hard</h2>
-<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_if owner_name><tmpl_var owner_name><tmpl_else><tmpl_var owner></tmpl_if></a>
-</tmpl_loop>
+<tr><td class="name"><a href="id">Name</a><td class="author">author<td class="owner"><a href="/us/id">Owner name</a>
 </table>
-</tmpl_if>
+</div>
+</div>
\ No newline at end of file
index f3d0f7579603123cf8e29590f69644e382f33d94..8189d0b745d6b7e72745e8e0a164ab01772dc1fe 100644 (file)
@@ -1,35 +1,38 @@
 <div class="row">
 <div class="col-md-9">
-<tmpl_var ESCAPE=0 statement>
+<div id="statement"></div>
 </div>
 
 <div class="col-md-3">
 <dl>
-<dt>Author</dt> <dd><tmpl_var author></dd>
-<dt>Owner</dt> <dd><a href="/us/<tmpl_var owner>"><tmpl_if owner_name><tmpl_var owner_name><tmpl_else><tmpl_var owner></tmpl_if></a></dd>
+<dt>Author</dt> <dd id="author">author</dd>
+<dt>Owner</dt> <dd id="owner">owner</dd>
 </dl>
 
-<a href="/pb/<tmpl_var id>/log/">Job log</a>
+<a href="/pb/problem_id/log/" id="job_log">Job log</a>
 
-<tmpl_if cansubmit>
+<div id="submit">
 <h1>Submit solution</h1>
-<form action="<tmpl_var id>/submit" method="POST" enctype="multipart/form-data" role="form">
-<input type="hidden" name="problem" value="<tmpl_var id>">
-<tmpl_if_defined contest><input type="hidden" name="contest" value="<tmpl_var contest>"></tmpl_if_defined>
+<form action="/action/submit" method="POST" enctype="multipart/form-data" role="form">
+<input type="hidden" name="problem" value="problem_id">
+<input type="hidden" name="contest" value="contest_id">
 
 <div class="form-group"><label for="prog">File:</label><input id="prog" name="prog" type="file"></div>
 <div class="form-group"><label for="source_code">Source code:</label> <textarea class="form-control" id="source_code" name="source_code"></textarea></div>
 <div class="form-group"><label for="prog_format">File format:</label><select id="prog_format" name="prog_format" class="form-control" required>
-<option value="C">C</option>
-<option value="CPP">C++</option>
-<option value="JAVA">Java</option>
-<option value="PASCAL">Pascal</option>
-<option value="PERL">Perl</option>
-<option value="PYTHON">Python</option>
+<option value="C">C (gcc)</option>
+<option value="CPP">C++ (g++)</option>
+<option value="GCCGO">Go (gccgo)</option>
+<option value="GOLANG">Go (gc)</option>
+<option value="HASKELL">Haskell (ghc)</option>
+<option value="JAVA">Java (javac)</option>
+<option value="PASCAL">Pascal (fpc)</option>
+<option value="PERL">Perl (perl)</option>
+<option value="PYTHON">Python (python)</option>
 </select></div>
 
 <input type="submit" value="Submit job" class="btn btn-primary">
 </form>
-</tmpl_if>
+</div>
 </div>
 </div>
diff --git a/tmpl/skel.en b/tmpl/skel.en
new file mode 100644 (file)
index 0000000..a5a3fef
--- /dev/null
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title smap="title">TITLE</title>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+<link rel="stylesheet" href="/css/cyborg.css" id="stylesheet">
+<script src="/js.js" type="text/javascript"></script>
+
+<body>
+<nav class="navbar navbar-default navbar-static-top" role="navigation">
+<div class="container-fluid">
+<div class="navbar-header">
+<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button>
+<a class="navbar-brand" href="/">Gruntmaster 6000</a>
+</div>
+
+<div class="collapse navbar-collapse">
+<ul class="nav navbar-nav">
+<li><a href="/pb/">Problems</a>
+<li><a href="/ct/">Contests</a>
+<li><a href="/account">Account</a>
+</ul>
+
+<ul class="nav navbar-nav navbar-right">
+<li><a class="dropdown-toggle" data-toggle="dropdown"> Theme <span class="caret"></span></a>
+
+<ul class="dropdown-menu" role="menu">
+<li><a href="#" id="theme_slate">Gunmetal gray</a>
+<li><a href="#" id="theme_cyborg">Black</a>
+<li><a href="#" id="theme_cerulean">White</a>
+<li><a href="#" id="theme_cosmo">Metro</a>
+</ul>
+
+<li><a href="/log/">Job log</a>
+</ul>
+</div>
+</div>
+</nav>
+
+<iframe src="http://free.timeanddate.com/clock/i47sdccv/n49/tlro/fc99f/tct/pct/ta1" frameborder="0" id="clock" width="98" height="18" sandbox="allow-scripts allow-same-origin"></iframe>
+
+<div class="container-fluid">
+
+<h1 id="title">TITLE</h1>
+<div id="result"></div>
+
+<div id="content">Content goes here</div>
+
+<footer>
+Dilmom: Why don't you call your product the Gruntmaster 6000?<br>
+Dilbert: What kind of product do you see when you imagine a Gruntmaster 6000?<br>
+Dilmom: Well, it's a stripped-down version of the Gruntmaster 9000, of course. But it's software-upgradeable.
+</footer>
index 6417f48bc1e894a3611707e717399d7737ed7944..1c4c592124bef267a4c95d6c149f9ed62cc3d97a 100644 (file)
@@ -1,12 +1,8 @@
 <table border class="table table-border table-striped">
 <thead>
-<tmpl_if problems><tr><th>Rank<th>User<tmpl_loop problems><th><a href="/pb/<tmpl_var _.id>"><tmpl_var _.name></a></tmpl_loop><th>Total
-<tmpl_else><tr><th>Rank<th>User<th>Score
-</tmpl_if>
+<tr><th>Rank<th>User<th class="problem"><a href="/pb/id">Problem name</a><th>Score
+</thead>
 
 <tbody>
-<tmpl_loop st><tr><td><tmpl_var rank><td><a href="/us/<tmpl_var user.id>"><tmpl_if user.name><tmpl_var user.name><tmpl_else><tmpl_var user.id></tmpl_if></a>
-<tmpl_if problems><tmpl_loop scores><td><tmpl_var _>
-</tmpl_loop></tmpl_if><td><tmpl_var score>
-</tmpl_loop>
+<tr><td class="rank">1<td class="user"><a href="/us/id">User noame</tmpl_if></a><td class="pbscore">100<td class="score">100
 </table>
index 2b77d089752eee0472cbd58b9b1c2867152d7be3..dad8844cd271d0588cdb392efc6ee6e0f55f0fde 100644 (file)
@@ -1,2 +1,3 @@
-<div class="list-group"><tmpl_loop us><a class="list-group-item" href="/us/<tmpl_var id>"><tmpl_var name></a>
-</tmpl_loop><div>
+<div class="list-group">
+<a class="list-group-item" href="/us/id">Name</a>
+<div>
index 4d1a97ab5e75617974d00693d060caeabcac0102..413e8978dbb6c43b6e2097bd3b1db308a8fb316c 100644 (file)
@@ -1,8 +1,8 @@
 <dl>
-<dt>Town</dt> <dd><tmpl_var town></dd>
-<dt>University</dt> <dd><tmpl_var university></dd>
-<dt>Level</dt> <dd><tmpl_var level></dd>
+<dt>Town</dt> <dd smap="town">Town</dd>
+<dt>University</dt> <dd smap="university">University</dd>
+<dt>Level</dt> <dd smap="level">Level</dd>
 </dl>
 
-<a href="/log/?owner=<tmpl_var id>">Job log</a><br>
-<a href="/pb/?owner=<tmpl_var id>">Owned problems</a>
+<a href="/log/?owner=id" id="log">Job log</a><br>
+<a href="/pb/?owner=id" id="pb">Owned problems</a>
This page took 0.031822 seconds and 4 git commands to generate.