From 7a3a80c7915e10b3e0abb59d92a90424a86a036b Mon Sep 17 00:00:00 2001 From: Matthew Connelly Date: Mon, 13 Apr 2015 03:56:29 +0100 Subject: [PATCH] First commit --- .gitignore | 9 ++ README.md | 13 ++ bin/app.pl | 9 ++ config.yml | 167 +++++++++++++++++++++++ data/schema.sql | 9 ++ lib/App/WerePaste.pm | 96 +++++++++++++ lib/App/WerePaste/Schema.pm | 11 ++ lib/App/WerePaste/Schema/Result/Paste.pm | 28 ++++ lib/App/WerePaste/Util/PygmentsBridge.pm | 52 +++++++ public/reset.css | 26 ++++ public/solarized256.css | 87 ++++++++++++ public/style.css | 62 +++++++++ start | 5 + views/index.tt | 30 ++++ views/layouts/main.tt | 19 +++ views/show.tt | 27 ++++ 16 files changed, 650 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 bin/app.pl create mode 100644 config.yml create mode 100644 data/schema.sql create mode 100644 lib/App/WerePaste.pm create mode 100644 lib/App/WerePaste/Schema.pm create mode 100644 lib/App/WerePaste/Schema/Result/Paste.pm create mode 100644 lib/App/WerePaste/Util/PygmentsBridge.pm create mode 100644 public/reset.css create mode 100644 public/solarized256.css create mode 100644 public/style.css create mode 100755 start create mode 100644 views/index.tt create mode 100644 views/layouts/main.tt create mode 100644 views/show.tt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6eb0f66 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*~ +*.swp +blib +Makefile +Makefile.old +.build +*.db +_Inline +data/pastes.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ee431e --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +App::WerePaste +-------------- + +Perl-based pastebin software, largely a rewrite of Dancebin, using Pygments via Inline::Python for syntax highlighting. + +Requirements: + +- Dancer2 +- Dancer2::Plugin::DBIC +- Data::UUID +- Try::Tiny +- Inline::Python +- pip module Pygments diff --git a/bin/app.pl b/bin/app.pl new file mode 100755 index 0000000..f2e3686 --- /dev/null +++ b/bin/app.pl @@ -0,0 +1,9 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use App::WerePaste; + +App::WerePaste->start; diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..d57e030 --- /dev/null +++ b/config.yml @@ -0,0 +1,167 @@ +appname: "WerePaste" +layout: "main" +charset: "UTF-8" +tz: "Europe/London" +locale: "en_GB" + +logger: "console" +log: "debug" + +template: "template_toolkit" +engines: + template: + template_toolkit: + encoding: 'utf8' + start_tag: '[%' + end_tag: '%]' + + +plugins: + DBIC: + default: + schema_class: 'App::WerePaste::Schema' + dsn: dbi:SQLite:dbname=data/pastes.db + options: + sqlite_unicode: 1 + +# Comment out for production +show_errors: 1 + +# Sets the expiration for posts +# Options can be any combination of weeks, days, hours, minutes, seconds +expiration: + weeks: 2 + + +# Languages supported by Pygments +languages: + - Programming languages: + - ActionScript: + - Ada: + - ANTLR: + - AppleScript: + - Assembly: nasm + - Asymptote: + - Awk: + - Bash: + - Befunge: + - Boo: + - BrainFuck: + - C: + - C++: + - C#: + - Clojure: + - CoffeeScript: + - ColdFusion: cfm + - Common Lisp: + - Coq: + - Cryptol: + - Cython: + - D: + - Dart: + - Delphi: + - Dylan: + - Erlang: + - Factor: + - Fancy: + - Fortran: + - F#: + - GAP: + - Gherkin: + - GL shaders: + - Groovy: + - Haskell: + - IDL: + - Io: + - Java: + - JavaScript: + - Lasso: + - LLVM: + - Logtalk: + - Lua: + - Matlab: + - MiniD: + - Modelica: + - Modula-2: + - MuPad: + - Nemerle: + - Nimrod: + - Objective-C: + - Objective-J: + - Octave: + - OCaml: + - PHP: + - Perl: + - PovRay: + - PostScript: + - PowerShell: + - Prolog: + - Python: + - REBOL: + - Red: + - Redcode: + - Ruby: + - Rust: + - R: + - S: + - S-Plus: + - Scala: + - Scheme: + - Scilab: + - Smalltalk: + - SNOBOL: + - Tcl: + - Vala: + - Verilog: + - VHDL: + - Visual Basic.NET: vbnet + - Visual FoxPro: foxpro + - XQuery: + - Zephir: + - Template Languages: + - Cheetah: + - Django: + - ERB: + - Genshi: + - Jinja: + - JSP: + - Mako: + - Myghty: + - Smarty: + - Tea: + - Configuration Markup: + - ApacheConf: + - INI-style: ini + - Lighttpd: + - Nginx: + - Other Markups: + - BBCode: + - CMake: + - CSS: + - Debian control file: control + - Diff: + - DTD: + - Gettext Catalogs: pot + - Gnuplot: + - Groff: + - HTML: + - HTTP Session: http + - IRC log (irssi-format): irc + - Makefile: + - MoinMoin/Trac wiki markup: moin + - MySQL: + - POV-Ray Scenes: pov + - Ragel: + - Redcode: + - ReST: + - SQL: + - PostgreSQL: psql + - SQLite: + - Squid Configuration: squid + - TeX: + - tcsh: + - VimScript: vim + - Windows Batch Script: bat + - XML: + - XSLT: + - YAML: diff --git a/data/schema.sql b/data/schema.sql new file mode 100644 index 0000000..a21bfd6 --- /dev/null +++ b/data/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE posts ( + id TEXT PRIMARY KEY NOT NULL, + ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, /* Created timestamp */ + expiration TIMESTAMP, /* Created timestamp */ + language TEXT NOT NULL, /* Language (if detected) */ + title TEXT, /* Title/Filename */ + code BLOB NOT NULL, /* Code */ + html BLOB /* HTML version of Code */ +); diff --git a/lib/App/WerePaste.pm b/lib/App/WerePaste.pm new file mode 100644 index 0000000..6a20172 --- /dev/null +++ b/lib/App/WerePaste.pm @@ -0,0 +1,96 @@ +package App::WerePaste; + +use strict; +use warnings; + +use App::WerePaste::Util::PygmentsBridge qw/:all/; +use Dancer2; +use Dancer2::Plugin::DBIC qw/schema/; +use DateTime; +use Data::UUID; + +my $lastexpunge = 0; + +sub DeploySchema { + # need to find a way to handle this that doesn't shit errors everywhere + eval {schema->deploy}; +} +sub DateTimeToQueryable { + my $dt = DateTime->now(time_zone => config->{tz}); + $dt->add(@_) if scalar @_; + return schema->storage->datetime_parser->format_datetime($dt); +} +sub ExpirationToDate { + my $expire = shift; + $expire = $expire ? { split ':', $expire } : undef; + return undef if $expire and $expire->{never}; + return DateTimeToQueryable(%{ $expire || config->{default_expire} }); +} +sub GetUUID { + my $uuid = Data::UUID->new->create_str; + $uuid =~ s/\-//g; + return lc $uuid; +} +sub CheckExpired { + return unless time > ($lastexpunge+900); #expunge once every 15 mins + $lastexpunge = time; + schema->resultset('Paste')->search({ expiration => [undef, { '<' => DateTimeToQueryable() }]})->delete_all; +} +sub ValidateParams { + my $params = shift; + if($params->{id}) { + return undef unless lc($params->{id}) =~ /^[a-f0-9]*$/; + return 1; + } + return undef unless $params->{code}; + return undef unless $params->{title} =~ /^[a-zA-Z0-9\.\-_ @\(\)]{0,255}$/; + return undef unless $params->{lang} =~ /^[a-z0-9\.\-\+# ]{0,40}$/; + return undef unless $params->{expiration} =~ /^([a-z]+:[0-9]+)(,[a-z]+:[0-9]+)*$/ or not $params->{expiration}; + return 1; +} +sub GetPaste { + my $id = shift; + return schema->resultset('Paste')->single({ id => $id }) or return undef; +} +sub SubmitPaste { + my $params = shift; + my ($lang,$html) = PygmentsHighlight(lang => $params->{lang}, code => $params->{code}); + my $id = GetUUID(); + my $result = schema->resultset('Paste')->create({ + id => $id, + title => $params->{title}, + language => $lang, + expiration => ExpirationToDate($params->{expiration}), + code => $params->{code}, + html => $html, + }) or return undef; + return $id; +} +sub ValidateAndGet { + my $params = shift; + ValidateParams($params) or return undef; + return GetPaste(lc $params->{id}) or return undef; +} + +# Startup +DeploySchema(); +# Hooks +hook 'before' => sub { CheckExpired(); }; +# Routes +#get +get '/' => sub { template 'index.tt'; }; +get '/:id' => sub { my $paste=ValidateAndGet(scalar params('route')) or pass; template 'show.tt', { paste => $paste };}; +get '/:id/copy' => sub { my $paste=ValidateAndGet(scalar params('route')) or pass; template 'index.tt', { paste => $paste }; }; +get '/:id/raw' => sub { my $paste=ValidateAndGet(scalar params('route')) or pass; content_type 'text/plain'; return $paste->code; }; +#post +post '/' => sub { + my $p = params('body'); + ValidateParams($p) or return send_error('Submitted paste is not valid. Check your post title and language, and try again.',400); + my $id = SubmitPaste($p) or return redirect '/503.html'; + return redirect "/$id"; +}; +#default +any qr/.*/ => sub { return send_error('Page gone',404); }; + +1; +__END__ diff --git a/lib/App/WerePaste/Schema.pm b/lib/App/WerePaste/Schema.pm new file mode 100644 index 0000000..64a9f36 --- /dev/null +++ b/lib/App/WerePaste/Schema.pm @@ -0,0 +1,11 @@ +package App::WerePaste::Schema; + +use strict; +use warnings; + +use base 'DBIx::Class::Schema'; + +__PACKAGE__->load_namespaces; + +1; +__END__ diff --git a/lib/App/WerePaste/Schema/Result/Paste.pm b/lib/App/WerePaste/Schema/Result/Paste.pm new file mode 100644 index 0000000..763b107 --- /dev/null +++ b/lib/App/WerePaste/Schema/Result/Paste.pm @@ -0,0 +1,28 @@ +package App::WerePaste::Schema::Result::Paste; + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +__PACKAGE__->table("pastes"); +__PACKAGE__->add_columns( + 'id', { data_type => 'text', is_nullable => 0 }, + 'ts', { data_type => 'timestamp', default_value => \"current_timestamp", is_nullable => 0 }, + 'expiration', { data_type => 'timestamp', is_nullable => 1 }, + 'language', { data_type => 'text', is_nullable => 0 }, + 'title', { data_type => 'text', is_nullable => 1 }, + 'code', { data_type => 'blob', is_nullable => 0 }, + 'html', { data_type => 'blob', is_nullable => 1 }, +); +__PACKAGE__->set_primary_key('id'); + +use Dancer2; +__PACKAGE__->load_components(qw/InflateColumn::DateTime/); +__PACKAGE__->add_columns( + '+ts' => { timezone => config->{tz}, locale => config->{locale} }, + '+expiration' => { timezone => config->{tz}, locale => config->{locale}, formatter => 'DateTime::Formatter::MySQL' } +); + +1; +__END__ diff --git a/lib/App/WerePaste/Util/PygmentsBridge.pm b/lib/App/WerePaste/Util/PygmentsBridge.pm new file mode 100644 index 0000000..3d4abea --- /dev/null +++ b/lib/App/WerePaste/Util/PygmentsBridge.pm @@ -0,0 +1,52 @@ +package App::WerePaste::Util::PygmentsBridge; + +use strict; +use Exporter; +use vars qw/$VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS/; + +use Carp; + +use Encode qw/decode_utf8/; +use Try::Tiny; + +use Inline Python => <<'END_INLINE_PYTHON'; +from pygments import highlight +from pygments.lexers import guess_lexer, get_lexer_by_name +from pygments.formatters import HtmlFormatter + +def py_highlight(c,l): + return highlight(c,l,HtmlFormatter(linenos=True,anchorlinenos=True,lineanchors="L",linenospecial=10,linespans="L",encoding="utf-8")) + +def py_guesslexer(c): + return guess_lexer(c,encoding="utf-8") + +def py_getlexer(l): + return get_lexer_by_name(l,encoding="utf-8") + +def py_getnamefromlexer(l): + return l.name + +END_INLINE_PYTHON + +$VERSION = 1.0.0; +@ISA = qw/Exporter/; +@EXPORT = qw//; +@EXPORT_OK = qw/PygmentsHighlight/; +%EXPORT_TAGS = (all => [@EXPORT_OK]); + +sub PygmentsHighlight { + my (%params) = @_; + return unless $params{code}; + my $lexer; + try { + $lexer = py_getlexer($params{lang}) if $params{lang}; + } catch {}; + try { + $lexer = py_guesslexer($params{code}) unless $lexer; + } catch {}; + $lexer = py_getlexer('text') unless $lexer; + return (py_getnamefromlexer($lexer),decode_utf8(py_highlight($params{code},$lexer))); +} + +1; +__END__ diff --git a/public/reset.css b/public/reset.css new file mode 100644 index 0000000..ac5cacd --- /dev/null +++ b/public/reset.css @@ -0,0 +1,26 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + diff --git a/public/solarized256.css b/public/solarized256.css new file mode 100644 index 0000000..d02c9ec --- /dev/null +++ b/public/solarized256.css @@ -0,0 +1,87 @@ +/* Solarized Dark + +For use with Jekyll and Pygments + +http://ethanschoonover.com/solarized + +SOLARIZED HEX ROLE +--------- -------- ------------------------------------------ +base03 #002b36 background +base01 #586e75 comments / secondary content +base1 #93a1a1 body text / default code / primary content +orange #cb4b16 constants +red #dc322f regex, special keywords +blue #268bd2 reserved keywords +cyan #2aa198 strings, numbers +green #859900 operators, other keywords +*/ + +.paste { background-color: #002b36; color: #93a1a1 } +.paste .c { color: #586e75 } /* Comment */ +.paste .err { color: #93a1a1 } /* Error */ +.paste .g { color: #93a1a1 } /* Generic */ +.paste .k { color: #859900 } /* Keyword */ +.paste .l { color: #93a1a1 } /* Literal */ +.paste .n { color: #93a1a1 } /* Name */ +.paste .o { color: #859900 } /* Operator */ +.paste .x { color: #cb4b16 } /* Other */ +.paste .p { color: #93a1a1 } /* Punctuation */ +.paste .cm { color: #586e75 } /* Comment.Multiline */ +.paste .cp { color: #859900 } /* Comment.Preproc */ +.paste .c1 { color: #586e75 } /* Comment.Single */ +.paste .cs { color: #859900 } /* Comment.Special */ +.paste .gd { color: #2aa198 } /* Generic.Deleted */ +.paste .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ +.paste .gr { color: #dc322f } /* Generic.Error */ +.paste .gh { color: #cb4b16 } /* Generic.Heading */ +.paste .gi { color: #859900 } /* Generic.Inserted */ +.paste .go { color: #93a1a1 } /* Generic.Output */ +.paste .gp { color: #93a1a1 } /* Generic.Prompt */ +.paste .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ +.paste .gu { color: #cb4b16 } /* Generic.Subheading */ +.paste .gt { color: #93a1a1 } /* Generic.Traceback */ +.paste .kc { color: #cb4b16 } /* Keyword.Constant */ +.paste .kd { color: #268bd2 } /* Keyword.Declaration */ +.paste .kn { color: #859900 } /* Keyword.Namespace */ +.paste .kp { color: #859900 } /* Keyword.Pseudo */ +.paste .kr { color: #268bd2 } /* Keyword.Reserved */ +.paste .kt { color: #dc322f } /* Keyword.Type */ +.paste .ld { color: #93a1a1 } /* Literal.Date */ +.paste .m { color: #2aa198 } /* Literal.Number */ +.paste .s { color: #2aa198 } /* Literal.String */ +.paste .na { color: #93a1a1 } /* Name.Attribute */ +.paste .nb { color: #B58900 } /* Name.Builtin */ +.paste .nc { color: #268bd2 } /* Name.Class */ +.paste .no { color: #cb4b16 } /* Name.Constant */ +.paste .nd { color: #268bd2 } /* Name.Decorator */ +.paste .ni { color: #cb4b16 } /* Name.Entity */ +.paste .ne { color: #cb4b16 } /* Name.Exception */ +.paste .nf { color: #268bd2 } /* Name.Function */ +.paste .nl { color: #93a1a1 } /* Name.Label */ +.paste .nn { color: #93a1a1 } /* Name.Namespace */ +.paste .nx { color: #93a1a1 } /* Name.Other */ +.paste .py { color: #93a1a1 } /* Name.Property */ +.paste .nt { color: #268bd2 } /* Name.Tag */ +.paste .nv { color: #268bd2 } /* Name.Variable */ +.paste .ow { color: #859900 } /* Operator.Word */ +.paste .w { color: #93a1a1 } /* Text.Whitespace */ +.paste .mf { color: #2aa198 } /* Literal.Number.Float */ +.paste .mh { color: #2aa198 } /* Literal.Number.Hex */ +.paste .mi { color: #2aa198 } /* Literal.Number.Integer */ +.paste .mo { color: #2aa198 } /* Literal.Number.Oct */ +.paste .sb { color: #586e75 } /* Literal.String.Backtick */ +.paste .sc { color: #2aa198 } /* Literal.String.Char */ +.paste .sd { color: #93a1a1 } /* Literal.String.Doc */ +.paste .s2 { color: #2aa198 } /* Literal.String.Double */ +.paste .se { color: #cb4b16 } /* Literal.String.Escape */ +.paste .sh { color: #93a1a1 } /* Literal.String.Heredoc */ +.paste .si { color: #2aa198 } /* Literal.String.Interpol */ +.paste .sx { color: #2aa198 } /* Literal.String.Other */ +.paste .sr { color: #dc322f } /* Literal.String.Regex */ +.paste .s1 { color: #2aa198 } /* Literal.String.Single */ +.paste .ss { color: #2aa198 } /* Literal.String.Symbol */ +.paste .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ +.paste .vc { color: #268bd2 } /* Name.Variable.Class */ +.paste .vg { color: #268bd2 } /* Name.Variable.Global */ +.paste .vi { color: #268bd2 } /* Name.Variable.Instance */ +.paste .il { color: #2aa198 } /* Literal.Number.Integer.Long */ diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..e2fcb89 --- /dev/null +++ b/public/style.css @@ -0,0 +1,62 @@ +@charset "UTF-8"; + +body { + font-family: 'lucida console', monospace; + font-size: 11px; + background:#002b36; + color: #586e75; +} + +textarea { + font-size: 12px; + font-family: 'lucida console', monospace; + padding: 0; + border: none; + border-top: 1px solid #335e69; + border-bottom: 1px solid #335e69; + background:#113c47; + color: #93a1a1; + width: 100%; +} + +h1 { + font-size: 1.4em; +} +a, a:hover, a:visited, a:focus { + text-decoration: none; + color: #ccf; +} +.highlighttable { + font-size: 12px; + line-height: 14px; + width: 100%; + border-spacing: 0; +} +td { + border: 2px solid #335e69; + padding: 2px 0; +} +td.code { + width: 100%; + border-right: none; + border-left: 1px solid #335e69; +} +td.linenos { + border-left: none; + border-right: 1px solid #335e69; +} +td.linenos > .linenodiv > pre > a { + display: inline-block; + padding-top: 2px; +} +.highlight > pre > span { + display: block; + width: 100%; + padding-top: 2px; +} +.highlight > pre > span:hover { + background-color: #113c47; +} +.selected { + background-color: #335e69; +} diff --git a/start b/start new file mode 100755 index 0000000..f75ab48 --- /dev/null +++ b/start @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +[ -z "$APP" ] && exit 1 +cd /home/sites/$APP +pkill -F /home/sites/$APP.pid 2>/dev/null +plackup -s Starman -D -p 12080 ./bin/app.pl --pid /home/sites/$APP.pid diff --git a/views/index.tt b/views/index.tt new file mode 100644 index 0000000..bbf750d --- /dev/null +++ b/views/index.tt @@ -0,0 +1,30 @@ +
+ + +
+ +
+
+ + +
+
diff --git a/views/layouts/main.tt b/views/layouts/main.tt new file mode 100644 index 0000000..869bd29 --- /dev/null +++ b/views/layouts/main.tt @@ -0,0 +1,19 @@ + + + + [% IF paste %][% paste.title || paste.id %] - [% END %]paste.were.space + + + + + + + +
+ [% content %] + + + diff --git a/views/show.tt b/views/show.tt new file mode 100644 index 0000000..1b01c34 --- /dev/null +++ b/views/show.tt @@ -0,0 +1,27 @@ +

[% paste.title %]

+Pasted at [% paste.ts.hms %], [% paste.ts.dmy %] +[% IF paste.expiration %] | Expires at [% paste.expiration.hms %], [% paste.expiration.dmy %][% END %] | Language: [% paste.language %] +
+
+
+ [% IF paste.html %] + [% paste.html %] + [% ELSE %] +
[% paste.code %]
+ [% END %] +
+ +