#!perl
package App::mojopaste::Backend::File;
use Mojo::Base 'Mojolicious::Plugin';

use Mojo::File 'path';
use Mojo::Util qw(encode decode);
use Text::CSV;

my $ID = 0;

sub register {
  my ($self, $app, $config) = @_;
  my $dir = $app->config('paste_dir');
  path($dir)->make_path unless -d $dir;
  $app->helper('paste.load_p' => sub { _load_p($dir, @_) });
  $app->helper('paste.save_p' => sub { _save_p($dir, @_) });
}

sub _load_p {
  my ($dir, $c, $id) = @_;
  my @res = ('', '');

  eval {
    die "Hacking attempt! paste_id=($id)" if !$id or $id =~ m!\W!;
    return Mojo::Promise->new->resolve(decode 'UTF-8', path($dir, $id)->slurp);
  } or do {
    return Mojo::Promise->new->reject($@ || 'Paste not found');
  };
}

sub _save_p {
  my ($dir, $c, $text) = @_;
  my $id  = substr Mojo::Util::md5_sum($$ . time . $ID++), 0, 12;
  my @res = ('', '');

  eval {
    path($dir, $id)->spurt(encode 'UTF-8', $text);
    return Mojo::Promise->new->resolve($id);
  } or do {
    return Mojo::Promise->new->reject($@ || 'Unknown error');
  };
}

package main;
use Mojolicious::Lite;

use Mojo::JSON 'true';

plugin 'config' if $ENV{MOJO_CONFIG};
app->config->{backend}   ||= $ENV{PASTE_BACKEND} || 'File';
app->config->{paste_dir} ||= $ENV{PASTE_DIR}     || 'paste';

app->defaults(
  enable_charts => app->config('enable_charts') // $ENV{PASTE_ENABLE_CHARTS},
  embed         => 'description,graph,heading,nav',
  error         => '',
  paste         => '',
  placeholder   => 'Enter your text here and then press the "Save" button above.',
  site_name     => $ENV{MOJOPASTE_SITE_NAME} || 'Mojopaste',
  title         => 'Mojopaste',
);

my $backend = app->config('backend');
plugin $backend =~ /::/ ? $backend : "App::mojopaste::Backend::$backend";

helper no_such_paste => sub {
  my ($c, $err) = @_;
  $c->app->log->debug("no_such_paste: $err");
  $c->stash($_ => 'Could not find paste') for qw(error heading title);
  $c->render(description => '', layout => 'mojopaste', status => 404);
};

helper set_title => sub {
  my ($c, $prefix, $suffix) = @_;
  my $site_name = $c->stash('site_name');
  $suffix = $suffix ? "$site_name $suffix" : $site_name;
  $prefix =~ s![\n\r]+! !g;
  $prefix =~ s!^\W+!!g;
  $prefix = substr $prefix, 0, 56 - length $suffix;
  return $c->stash(title => "$prefix - $suffix");
};

get(
  '/' => {layout => 'mojopaste'} => sub {
    my $c = shift;

    return $c->set_title("Create new paste") unless my $id = $c->param('edit');
    return $c->render_later->paste->load_p($id)->then(sub {
      return $c->no_such_paste('Could not find paste') unless my $paste = shift;
      $c->set_title(substr($paste, 0, 80), 'edit');
      $c->param(paste => $paste)->render;
    })->catch(sub { $c->no_such_paste(shift) });
  },
  'index'
);

post(
  '/' => {layout => 'mojopaste'},
  sub {
    my $c     = shift;
    my $paste = $c->param('paste') || '';

    return $c->render('index', placeholder => 'You neeed to enter some characters!', status => 400)
      unless $paste =~ /\w/;
    return $c->render_later->paste->save_p($paste)->then(sub {
      $c->redirect_to('show', paste_id => shift);
    })->catch(sub { $c->reply->exception(shift) });
  }
);

get(
  '/:paste_id',
  sub {
    my $c      = shift;
    my $format = $c->stash('format') || '';

    $c->render_later->paste->load_p($c->stash('paste_id'))->then(sub {
      my $paste = shift;
      if (!$paste) {
        $c->no_such_paste('Could not find paste');
      }
      elsif ($c->param('raw') or $format eq 'txt') {
        $c->res->headers->content_type('text/plain; charset=utf-8');
        $c->render(text => $paste);
      }
      else {
        $c->set_title(substr($paste, 0, 80));
        $c->res->headers->header('X-Plain-Text-URL' => $c->url_for(format => 'txt')->userinfo(undef)->to_abs);
        $c->stash(embed => $c->param('embed')) if $c->param('embed');
        $c->render(layout => 'mojopaste', paste => $paste);
      }
    })->catch(sub { $c->no_such_paste(shift) });
  },
  'show'
);

app->defaults('enable_charts') and get(
  '/:paste_id/chart' => {layout => 'mojopaste'},
  sub {
    my $c     = shift;
    my $chart = {element => 'chart', data => [], hideHover => true, resize => true};
    my ($heading, $description, $error) = ('', '', '');

    $c->render_later->paste->load_p($c->stash('paste_id'))->then(sub {
      return $c->no_such_paste('Could not find paste') unless my $paste = shift;

      while ($paste =~ s!^\s*(?://|\#)(.*)!!m) {
        $description .= $1 if $heading;
        $heading ||= $1;
      }

      eval {
        _chart($chart, grep { $_ =~ /\S/ } split /\r?\n/, $paste);
      } or do {
        $error = $@ || 'Unknown error';
        $error =~ s!\s*at .*? line \d+.*!!s;
      };

      $c->set_title($heading || $description || substr($paste, 0, 80), 'graph');
      $c->stash(embed => $c->param('embed')) if $c->param('embed');
      $c->render(chart => $chart, description => $description // '', error => $error, heading => $heading);
    })->catch(sub { $c->no_such_paste(shift) });
  },
  'chart'
);

app->start;

sub _chart {
  my $chart = shift;
  my $csv   = Text::CSV->new;

  $csv->parse(shift @_);    # heading
  $chart->{ykeys}             = [$csv->fields];
  $chart->{xkey}              = shift @{$chart->{ykeys}};
  $chart->{labels}            = $chart->{ykeys};
  $chart->{pointStrokeColors} = '#222';

  while (@_) {
    die $csv->error_input unless $csv->parse(shift @_);
    my @row = $csv->fields or next;
    push @{$chart->{data}}, {$chart->{xkey} => shift(@row), map { ($_ => 0 + shift @row) } @{$chart->{ykeys}}};
  }

  die 'Could not parse CSV data.' unless @{$chart->{data}};
  return $chart;
}

=pod

=encoding utf8

=head1 NAME

mojopaste - Pastebin application

=head1 DESCRIPTION

See L<App::mojopaste>.

=head1 AUTHOR

Jan Henning Thorsen - C<jhthorsen@cpan.org>

=cut

__DATA__
@@ layouts/mojopaste.html.ep
<!DOCTYPE>
<html>
<head>
  <title><%= title %></title>
  <meta name="description" content="A Mojolicious powered pastebin">
  <meta name="viewport" content="width=device-width, initial-scale=0.9" />
  %= stylesheet begin
  :root {
    --root-bg-color: #1d1e19;
    --root-font-color: #d5d9bc;
    --root-font-size: 16px;
    --root-font-family: Menlo, Bitstream Vera Sans Mono, DejaVu Sans Mono, Monaco, Consolas, monospace;
    --gutter: 2rem;
    --nav-bg-color: #191a15;
    --nav-height: 3rem;
  }

  @media (max-width: 800px) {
    :root {
      --gutter: 1rem;
    }
  }

  * {
    border: 0;
    padding: 0;
    margin: 0;
    box-sizing: border-box;
  }

  html, body, textarea {
    background: var(--root-bg-color);
  }

  html, body, textarea, button {
    font-family: var(--root-font-family);
    font-size: var(--root-font-size);
    color: var(--root-font-color);
  }

  h2 {
    margin: var(--gutter);
    margin-bottom: 1rem;
  }

  p {
    margin: 1rem var(--gutter);
  }

  a {
    color: var(--root-font-color);
  }

  a:hover,
  .btn:hover {
    background-color: #11120f;
  }

  nav {
    background: var(--nav-bg-color);
    position: sticky;
    top: 0;
    height: var(--nav-height);
    box-shadow: -1px 0 0 1px rgba(0, 0, 0, 0.2);
  }

  .btn,
  .brand {
    --padding: 0.4rem;

    background: var(--nav-bg-color);
    text-decoration: none;
    line-height: calc(var(--nav-height) - var(--padding) * 2);
    margin-left: calc(var(--gutter) / 2);
    padding: var(--padding) calc(var(--gutter) / 2);
    float: left;
    display: block;
    cursor: pointer;
  }

  .brand {
    display: block !important;
  }

  .brand img {
    height: calc(var(--nav-height) - var(--padding) * 2);
    vertical-align: bottom;
  }

  textarea,
  .pre,
  .prettyprint {
    padding: var(--gutter);
    width: 100vw;
    max-width: 100vw;
    overflow: scroll;
    outline: 0;
  }

  .has-nav .prettyprint {
    height: calc(100vh - var(--nav-height));
  }

  .prettyprint.linenums {
    padding-left: 0;
  }

  .linenums {
    padding-left: 3.5em;
  }

  @media (min-width: 800px) {
    .linenums {
      padding-left: 4.5em;
    }
  }

  @media print {
    nav a,
    nav .btn {
      display: none;
    }

    .prettyprint {
      white-space: pre-wrap;
    }
  }

  /*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */
  .prettyprint{font-family:Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Consolas,monospace;border:0!important}.pln{color:#c5c8c6}ol.linenums{margin-top:0;margin-bottom:0;color:#969896}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{list-style-type:decimal}@media screen{.str{color:#b5bd68}.kwd{color:#b294bb}.com{color:#969896}.typ{color:#81a2be}.lit{color:#de935f}.pun{color:#c5c8c6}.opn{color:#c5c8c6}.clo{color:#c5c8c6}.tag{color:#c66}.atn{color:#de935f}.atv{color:#8abeb7}.dec{color:#de935f}.var{color:#c66}.fun{color:#81a2be}}

  .morris-hover{position:absolute;z-index:1000}
  .morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(0,0,0,0.8);font-size:12px;text-align:center}
  .morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0}
  .morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0}

  % end
  <script type="text/javascript">
  window.onkeydown = function(e) {
    var el;
    if (!e.ctrlKey && !e.metaKey) return; // ctrl or cmd
    if (e.keyCode != 65 && e.keyCode != 97) return; // A or a
    if (el = document.querySelector(".prettyprint")) {
      var range = document.createRange();
      range.selectNodeContents(el);
      var selection = window.getSelection();
      selection.removeAllRanges();
      selection.addRange(range);
      e.preventDefault();
    }
    else if(el = document.querySelector("textarea")) {
      el.select();
      e.preventDefault();
    }
  };
  window.onload = function(e) {
    var form = document.querySelector('form');
    var pre = document.querySelector('pre');

    if (form) {
      document.querySelector('textarea').focus();
      document.querySelector('.save.btn').onclick = function(e) { form.action = '<%= url_for 'index' %>' };
    }
    if (pre && pre.firstChild.nodeValue.length < 50000) {
      prettyPrint();
    }
  };
  /* prettify from google */
  !function(){var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
  (function(){function S(a){function d(e){var b=e.charCodeAt(0);if(b!==92)return b;var a=e.charAt(1);return(b=r[a])?b:"0"<=a&&a<="7"?parseInt(e.substring(1),8):a==="u"||a==="x"?parseInt(e.substring(2),16):e.charCodeAt(1)}function g(e){if(e<32)return(e<16?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return e==="\\"||e==="-"||e==="]"||e==="^"?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),e=[],a=
  b[0]==="^",c=["["];a&&c.push("^");for(var a=a?1:0,f=b.length;a<f;++a){var h=b[a];if(/\\[bdsw]/i.test(h))c.push(h);else{var h=d(h),l;a+2<f&&"-"===b[a+1]?(l=d(b[a+2]),a+=2):l=h;e.push([h,l]);l<65||h>122||(l<65||h>90||e.push([Math.max(65,h)|32,Math.min(l,90)|32]),l<97||h>122||e.push([Math.max(97,h)&-33,Math.min(l,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];f=[];for(a=0;a<e.length;++a)h=e[a],h[0]<=f[1]+1?f[1]=Math.max(f[1],h[1]):b.push(f=h);for(a=0;a<b.length;++a)h=b[a],c.push(g(h[0])),
  h[1]>h[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(g(h[1])));c.push("]");return c.join("")}function s(e){for(var a=e.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],f=0,h=0;f<c;++f){var l=a[f];l==="("?++h:"\\"===l.charAt(0)&&(l=+l.substring(1))&&(l<=h?d[l]=-1:a[f]=g(l))}for(f=1;f<d.length;++f)-1===d[f]&&(d[f]=++x);for(h=f=0;f<c;++f)l=a[f],l==="("?(++h,d[h]||(a[f]="(?:")):"\\"===l.charAt(0)&&(l=+l.substring(1))&&l<=h&&
  (a[f]="\\"+d[l]);for(f=0;f<c;++f)"^"===a[f]&&"^"!==a[f+1]&&(a[f]="");if(e.ignoreCase&&m)for(f=0;f<c;++f)l=a[f],e=l.charAt(0),l.length>=2&&e==="["?a[f]=b(l):e!=="\\"&&(a[f]=l.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var x=0,m=!1,j=!1,k=0,c=a.length;k<c;++k){var i=a[k];if(i.ignoreCase)j=!0;else if(/[a-z]/i.test(i.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){m=!0;j=!1;break}}for(var r={b:8,t:9,n:10,v:11,
  f:12,r:13},n=[],k=0,c=a.length;k<c;++k){i=a[k];if(i.global||i.multiline)throw Error(""+i);n.push("(?:"+s(i)+")")}return RegExp(n.join("|"),j?"gi":"g")}function T(a,d){function g(a){var c=a.nodeType;if(c==1){if(!b.test(a.className)){for(c=a.firstChild;c;c=c.nextSibling)g(c);c=a.nodeName.toLowerCase();if("br"===c||"li"===c)s[j]="\n",m[j<<1]=x++,m[j++<<1|1]=a}}else if(c==3||c==4)c=a.nodeValue,c.length&&(c=d?c.replace(/\r\n?/g,"\n"):c.replace(/[\t\n\r ]+/g," "),s[j]=c,m[j<<1]=x,x+=c.length,m[j++<<1|1]=
  a)}var b=/(?:^|\s)nocode(?:\s|$)/,s=[],x=0,m=[],j=0;g(a);return{a:s.join("").replace(/\n$/,""),d:m}}function H(a,d,g,b){d&&(a={a:d,e:a},g(a),b.push.apply(b,a.g))}function U(a){for(var d=void 0,g=a.firstChild;g;g=g.nextSibling)var b=g.nodeType,d=b===1?d?a:g:b===3?V.test(g.nodeValue)?a:d:d;return d===a?void 0:d}function C(a,d){function g(a){for(var j=a.e,k=[j,"pln"],c=0,i=a.a.match(s)||[],r={},n=0,e=i.length;n<e;++n){var z=i[n],w=r[z],t=void 0,f;if(typeof w==="string")f=!1;else{var h=b[z.charAt(0)];
  if(h)t=z.match(h[1]),w=h[0];else{for(f=0;f<x;++f)if(h=d[f],t=z.match(h[1])){w=h[0];break}t||(w="pln")}if((f=w.length>=5&&"lang-"===w.substring(0,5))&&!(t&&typeof t[1]==="string"))f=!1,w="src";f||(r[z]=w)}h=c;c+=z.length;if(f){f=t[1];var l=z.indexOf(f),B=l+f.length;t[2]&&(B=z.length-t[2].length,l=B-f.length);w=w.substring(5);H(j+h,z.substring(0,l),g,k);H(j+h+l,f,I(w,f),k);H(j+h+B,z.substring(B),g,k)}else k.push(j+h,w)}a.g=k}var b={},s;(function(){for(var g=a.concat(d),j=[],k={},c=0,i=g.length;c<i;++c){var r=
  g[c],n=r[3];if(n)for(var e=n.length;--e>=0;)b[n.charAt(e)]=r;r=r[1];n=""+r;k.hasOwnProperty(n)||(j.push(r),k[n]=q)}j.push(/[\S\s]/);s=S(j)})();var x=d.length;return g}function v(a){var d=[],g=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
  q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&g.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),g.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,q])):d.push(["com",
  /^#[^\n\r]*/,q,"#"]));a.cStyleComments&&(g.push(["com",/^\/\/[^\n\r]*/,q]),g.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));if(b=a.regexLiterals){var s=(b=b>1?"":"\n\r")?".":"[\\S\\s]";g.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|\%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+s+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+
  s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,
  q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d=
  c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i<c.length;++i)b(c[i]);d===(d|0)&&c[0].setAttribute("value",d);var r=j.createElement("ol");
  r.className="linenums";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.className="L"+(i+d)%10,k.firstChild||k.appendChild(j.createTextNode("\u00a0")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return F[a]}function K(a){var d=a.h;try{var g=T(a.c,a.i),b=g.a;
  a.a=b;a.d=g.d;a.e=0;I(d,b)(a);var s=/\bMSIE\s(\d+)/.exec(navigator.userAgent),s=s&&+s[1]<=8,d=/\n/g,x=a.a,m=x.length,g=0,j=a.d,k=j.length,b=0,c=a.g,i=c.length,r=0;c[i]=m;var n,e;for(e=n=0;e<i;)c[e]!==c[e+2]?(c[n++]=c[e++],c[n++]=c[e++]):e+=2;i=n;for(e=n=0;e<i;){for(var p=c[e],w=c[e+1],t=e+2;t+2<=i&&c[t+1]===w;)t+=2;c[n++]=p;c[n++]=w;e=t}c.length=n;var f=a.c,h;if(f)h=f.style.display,f.style.display="none";try{for(;b<k;){var l=j[b+2]||m,B=c[r+2]||m,t=Math.min(l,B),A=j[b+1],G;if(A.nodeType!==1&&(G=x.substring(g,
  t))){s&&(G=G.replace(d,"\r"));A.nodeValue=G;var L=A.ownerDocument,o=L.createElement("span");o.className=c[r+1];var v=A.parentNode;v.replaceChild(o,A);o.appendChild(A);g<l&&(j[b+1]=A=L.createTextNode(x.substring(t,l)),v.insertBefore(A,o.nextSibling))}g=t;g>=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
  "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],
  O=[N,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],E=[E,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],P=[y,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
  Q=[y,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],W=[y,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],y=[y,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],R=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
  V=/\S/,X=v({keywords:[M,O,E,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",P,Q,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),F={};p(X,["default-code"]);p(C([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<\%([\S\s]+?)(?:\%>|$)/],["pun",/^(?:<[\%?]|[\%?]>)/],["lang-",
  /^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);p(C([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],
  ["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);p(C([],[["atv",/^[\S\s]+/]]),["uq.val"]);p(v({keywords:M,hashComments:!0,cStyleComments:!0,types:R}),["c","cc","cpp","cxx","cyc","m"]);p(v({keywords:"null,true,false"}),["json"]);p(v({keywords:O,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:R}),
  ["cs"]);p(v({keywords:N,cStyleComments:!0}),["java"]);p(v({keywords:y,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);p(v({keywords:P,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);p(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);p(v({keywords:Q,
  hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);p(v({keywords:E,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);p(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);p(v({keywords:W,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]);
  p(C([],[["str",/^[\S\s]+/]]),["regex"]);var Y=D.PR={createSimpleLexer:C,registerLangHandler:p,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:D.prettyPrintOne=function(a,d,g){var b=document.createElement("div");b.innerHTML="<pre>"+a+"</pre>";b=b.firstChild;g&&J(b,g,!0);K({h:d,j:g,c:b,i:1});
  return b.innerHTML},prettyPrint:D.prettyPrint=function(a,d){function g(){for(var b=D.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;i<p.length&&c.now()<b;i++){for(var d=p[i],j=h,k=d;k=k.previousSibling;){var m=k.nodeType,o=(m===7||m===8)&&k.nodeValue;if(o?!/^\??prettify\b/.test(o):m!==3||/\S/.test(k.nodeValue))break;if(o){j={};o.replace(/\b(\w+)=([\w%+\-.:]+)/g,function(a,b,c){j[b]=c});break}}k=d.className;if((j!==h||e.test(k))&&!v.test(k)){m=!1;for(o=d.parentNode;o;o=o.parentNode)if(f.test(o.tagName)&&
  o.className&&e.test(o.className)){m=!0;break}if(!m){d.className+=" prettyprinted";m=j.lang;if(!m){var m=k.match(n),y;if(!m&&(y=U(d))&&t.test(y.tagName))m=y.className.match(n);m&&(m=m[1])}if(w.test(d.tagName))o=1;else var o=d.currentStyle,u=s.defaultView,o=(o=o?o.whiteSpace:u&&u.getComputedStyle?u.getComputedStyle(d,q).getPropertyValue("white-space"):0)&&"pre"===o.substring(0,3);u=j.linenums;if(!(u=u==="true"||+u))u=(u=k.match(/\blinenums\b(?::(\d+))?/))?u[1]&&u[1].length?+u[1]:!0:!1;u&&J(d,u,o);r=
  {h:m,c:d,j:u,i:o};K(r)}}}i<p.length?setTimeout(g,250):"function"===typeof a&&a()}for(var b=d||document.body,s=b.ownerDocument||document,b=[b.getElementsByTagName("pre"),b.getElementsByTagName("code"),b.getElementsByTagName("xmp")],p=[],m=0;m<b.length;++m)for(var j=0,k=b[m].length;j<k;++j)p.push(b[m][j]);var b=q,c=Date;c.now||(c={now:function(){return+new Date}});var i=0,r,n=/\blang(?:uage)?-([\w.]+)(?!\S)/,e=/\bprettyprint\b/,v=/\bprettyprinted\b/,w=/pre|xmp/i,t=/^code$/i,f=/^(?:pre|code|xmp)$/i,
  h={};g()}};typeof define==="function"&&define.amd&&define("google-code-prettify",[],function(){return Y})})();}()
  </script>
</head>

<body class="<%= $embed =~ /nav/ ? 'has-nav' : '' %>">
  %= content
</body>
</html>
@@ layouts/mojopaste.txt.ep
%= content
@@ index.html.ep
%= form_for 'invalid', method => 'post', begin
%= include 'nav'
%= text_area 'paste', placeholder => $placeholder, tabindex => 1
% end
@@ chart.html.ep
%= include 'nav' if $embed =~ /nav/;
% if ($heading and $embed =~ /heading/) {
<h2><%= $heading %></h2>
% }
% if ($description and $embed =~ /description/) {
<p><%= $description %></p>
% }
% if ($embed =~ /graph/) {
<div id="chart"><%= $error %></div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
  % unless ($error) {
<script>
try {
  var chart = document.getElementById('chart');
  var tid, resize = function() { chart.style.height = (window.innerHeight - chart.offsetTop) + 'px' };
  resize();
  window.onresize = function() { if (tid) clearTimeout(tid); var tid = setTimeout(resize, 10); }
  new Morris.Line(<%== Mojo::JSON::encode_json(stash 'chart') %>);
} catch(e) {
  document.getElementById('chart').innerHTML = 'Invalid chart data. ' + e;
}
</script>
  % }
% }
@@ show.html.ep
%= include 'nav' if $embed =~ /nav/;
<pre class="prettyprint linenums"><%= $error || $paste %></pre>
@@ nav.html.ep
% my $paste_id = stash 'paste_id';
<nav>
  %= link_to 'index', class => 'brand', begin
    %= image '/images/logo.png', alt => ''
    <span>Mojopaste</span>
  % end

  % if ($paste_id) {
    %= link_to 'New', 'index', class => 'btn'
    %= link_to 'Edit', url_for('index')->query(edit => $paste_id), class => 'btn'

    % if (exists stash->{chart}) {
      %= link_to 'Data', url_for('show', paste_id => $paste_id), class => 'btn'
    % } else {
      %= link_to 'Raw', url_for('show', paste_id => $paste_id, format => 'txt'), class => 'btn'
      %= link_to 'Graph', url_for('chart'), class => 'btn' if $enable_charts
    % }

  % } else {
    <button class="btn save" tabindex="2">Save</button>
  % }
</nav>
@@ show.txt.ep
Paste not found: <%= $paste_id %>
@@ images/logo.png (base64)
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
