From 3b69df7a785bbfc5b8c4a1719c046f9800aa3548 Mon Sep 17 00:00:00 2001 From: Marius Gavrilescu <marius@ieval.ro> Date: Sun, 28 Sep 2014 09:10:18 +0300 Subject: [PATCH] Refactoring, part II (HTML::Seamstress) --- app.psgi | 19 ++- css/custom.css | 3 +- lib/Plack/App/Gruntmaster.pm | 75 ++++------- lib/Plack/App/Gruntmaster/HTML.pm | 204 ++++++++++++++++++++++++++++++ tmpl/ct.en | 36 +++--- tmpl/ct_entry.en | 16 ++- tmpl/footer.en | 5 - tmpl/log.en | 21 +-- tmpl/log_entry.en | 15 ++- tmpl/pb.en | 40 +----- tmpl/pb_entry.en | 33 ++--- tmpl/{header.en => skel.en} | 13 +- tmpl/st.en | 10 +- tmpl/us.en | 5 +- tmpl/us_entry.en | 10 +- 15 files changed, 332 insertions(+), 173 deletions(-) create mode 100644 lib/Plack/App/Gruntmaster/HTML.pm delete mode 100644 tmpl/footer.en rename tmpl/{header.en => skel.en} (79%) diff --git a/app.psgi b/app.psgi index 927653e..2a0c979 100644 --- 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 diff --git a/css/custom.css b/css/custom.css index 1394354..3c62f01 100644 --- a/css/custom.css +++ b/css/custom.css @@ -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 +} diff --git a/lib/Plack/App/Gruntmaster.pm b/lib/Plack/App/Gruntmaster.pm index 3c989ac..3c26ecf 100644 --- a/lib/Plack/App/Gruntmaster.pm +++ b/lib/Plack/App/Gruntmaster.pm @@ -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 index 0000000..16ae617 --- /dev/null +++ b/lib/Plack/App/Gruntmaster/HTML.pm @@ -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); +} diff --git a/tmpl/ct.en b/tmpl/ct.en index 9420708..31642df 100644 --- a/tmpl/ct.en +++ b/tmpl/ct.en @@ -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> diff --git a/tmpl/ct_entry.en b/tmpl/ct_entry.en index 731872b..6f0a580 100644 --- a/tmpl/ct_entry.en +++ b/tmpl/ct_entry.en @@ -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 index c0f928d..0000000 --- a/tmpl/footer.en +++ /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/log.en b/tmpl/log.en index 1509168..eb4bf6f 100644 --- a/tmpl/log.en +++ b/tmpl/log.en @@ -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> diff --git a/tmpl/log_entry.en b/tmpl/log_entry.en index adf6bfd..eb05db8 100644 --- a/tmpl/log_entry.en +++ b/tmpl/log_entry.en @@ -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 diff --git a/tmpl/pb.en b/tmpl/pb.en index 35cfbd4..ae1a002 100644 --- a/tmpl/pb.en +++ b/tmpl/pb.en @@ -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 diff --git a/tmpl/pb_entry.en b/tmpl/pb_entry.en index f3d0f75..8189d0b 100644 --- a/tmpl/pb_entry.en +++ b/tmpl/pb_entry.en @@ -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/header.en b/tmpl/skel.en similarity index 79% rename from tmpl/header.en rename to tmpl/skel.en index 0f5938b..a5a3fef 100644 --- a/tmpl/header.en +++ b/tmpl/skel.en @@ -1,11 +1,12 @@ <!DOCTYPE html> -<title>TITLE_GOES_HERE</title> +<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"> @@ -40,5 +41,13 @@ <div class="container-fluid"> -<h1 id="title">TITLE_GOES_HERE</h1> +<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> diff --git a/tmpl/st.en b/tmpl/st.en index 6417f48..1c4c592 100644 --- a/tmpl/st.en +++ b/tmpl/st.en @@ -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> diff --git a/tmpl/us.en b/tmpl/us.en index 2b77d08..dad8844 100644 --- a/tmpl/us.en +++ b/tmpl/us.en @@ -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> diff --git a/tmpl/us_entry.en b/tmpl/us_entry.en index 4d1a97a..413e897 100644 --- a/tmpl/us_entry.en +++ b/tmpl/us_entry.en @@ -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> -- 2.39.5