File Coverage

blib/lib/Web/Paste/Simple.pm
Criterion Covered Total %
statement 41 73 56.1
branch 0 14 0.0
condition 0 7 0.0
subroutine 14 22 63.6
pod 6 6 100.0
total 61 122 50.0


line stmt bran cond sub pod time code
1             package Web::Paste::Simple;
2              
3 1     1   31440 use 5.010;
  1         5  
  1         38  
4 1     1   3087 use Moo;
  1         27337  
  1         8  
5 1     1   3789 use MooX::Types::MooseLike::Base qw( Str CodeRef ArrayRef InstanceOf );
  1         27724  
  1         163  
6 1     1   14 use Carp qw( confess );
  1         2  
  1         62  
7 1     1   2087 use JSON qw( from_json to_json );
  1         32589  
  1         8  
8 1     1   3662 use HTML::HTML5::Entities qw( encode_entities_numeric );
  1         88083  
  1         116  
9 1     1   12 use constant read_only => 'ro';
  1         3  
  1         78  
10 1     1   1532 use aliased 'Text::Template';
  1         963  
  1         6  
11 1     1   7179 use aliased 'Data::UUID';
  1         3  
  1         7  
12 1     1   2649 use aliased 'Plack::Request';
  1         3  
  1         8  
13 1     1   224331 use aliased 'Plack::Response';
  1         4  
  1         26  
14 1     1   10288 use aliased 'Path::Class::Dir';
  1         2  
  1         151  
15 1     1   80078 use aliased 'Path::Class::File';
  1         3  
  1         8  
16              
17             BEGIN {
18 1     1   177 $Web::Paste::Simple::AUTHORITY = 'cpan:TOBYINK';
19 1         1352 $Web::Paste::Simple::VERSION = '0.001';
20             }
21              
22             has uuid_gen => (
23             is => read_only,
24             isa => InstanceOf[UUID],
25             default => sub { UUID->new },
26             );
27              
28             has template => (
29             is => read_only,
30             isa => InstanceOf[Template],
31             lazy => 1,
32             default => sub {
33             return Template->new(
34             TYPE => 'FILEHANDLE',
35             SOURCE => \*DATA,
36             );
37             },
38             );
39              
40             has storage => (
41             is => read_only,
42             isa => InstanceOf[Dir],
43             default => sub { Dir->new('/tmp/perl-web-paste-simple/') },
44             );
45              
46             has codemirror => (
47             is => read_only,
48             isa => Str,
49             default => sub { 'http://buzzword.org.uk/2012/codemirror-2.36' },
50             );
51              
52             has app => (
53             is => read_only,
54             isa => CodeRef,
55             lazy => 1,
56             builder => '_build_app',
57             );
58              
59             has modes => (
60             is => read_only,
61             isa => ArrayRef[Str],
62             default => sub {
63             [qw(
64             htmlmixed xml css javascript
65             clike perl php ruby python lua haskell
66             diff sparql ntriples plsql
67             )]
68             },
69             );
70              
71             has default_mode => (
72             is => read_only,
73             isa => Str,
74             default => sub { 'perl' },
75             );
76              
77             sub _build_app
78             {
79 0     0     my $self = shift;
80            
81 0 0         $self->storage->mkpath unless -d $self->storage;
82 0 0         confess "@{[$self->storage]} is not writeable" unless -w $self->storage;
  0            
83            
84             return sub {
85 0     0     my $req = Request->new(shift);
86 0           $self->dispatch($req)->finalize;
87 0           };
88             }
89              
90             sub dispatch
91             {
92 0     0 1   my ($self, $req) = @_;
93            
94 0 0         if ($req->method eq 'POST') {
    0          
    0          
95 0           return $self->create_paste($req);
96             }
97             elsif ($req->path =~ m{^/([^.]+)}) {
98 0           return $self->retrieve_paste($req, $1);
99             }
100             elsif ($req->path eq '/') {
101 0           return $self->show_template($req, {});
102             }
103             else {
104 0           return $self->show_error("Bad URI", 404);
105             }
106             }
107              
108             sub make_paste_id
109             {
110 0     0 1   my $id = shift->uuid_gen->create_b64;
111 0           $id =~ tr{+/}{-_};
112 0           $id =~ s{=+$}{};
113 0           return $id;
114             }
115              
116             sub create_paste
117             {
118 0     0 1   my ($self, $req) = @_;
119 0           my $id = $self->make_paste_id;
120 0           $self->storage->file("$id.paste")->spew(
121 0           to_json( +{ %{$req->parameters} } ),
122             );
123 0           return Response->new(
124             302,
125             [
126             'Content-Type' => 'text/plain',
127             'Location' => $req->base . "/$id",
128             ],
129             "Yay!",
130             );
131             }
132              
133             sub retrieve_paste
134             {
135 0     0 1   my ($self, $req, $id) = @_;
136 0           my $file = $self->storage->file("$id.paste");
137 0 0         -r $file or return $self->show_error("Bad file", 404);
138 0           my $data = from_json($file->slurp);
139            
140 0 0         exists $req->parameters->{raw}
141             ? Response->new(200, ['Content-Type' => 'text/plain'], $data->{paste})
142             : $self->show_template($req, $data);
143             }
144              
145             sub show_template
146             {
147 0     0 1   my ($self, $req, $data) = @_;
148 0   0       my $page = $self->template->fill_in(
      0        
149             HASH => {
150             DATA => encode_entities_numeric($data->{paste} // ''),
151             MODE => encode_entities_numeric($data->{mode} // $self->default_mode),
152             MODES => $self->modes,
153             PACKAGE => ref($self),
154             VERSION => $self->VERSION,
155             CODEMIRROR => $self->codemirror,
156             APP => $self,
157             REQUEST => $req,
158             },
159             );
160 0           Response->new(200, ['Content-Type' => 'text/html'], $page);
161             }
162              
163             sub show_error
164             {
165 0     0 1   my ($self, $err, $code) = @_;
166 0   0       Response->new(($code//500), ['Content-Type' => 'text/plain'], "$err\n");
167             }
168              
169              
170             1;
171              
172             =head1 NAME
173              
174             Web::Paste::Simple - simple PSGI-based pastebin-like website
175              
176             =head1 SYNOPSIS
177              
178             #!/usr/bin/plackup
179             use Web::Paste::Simple;
180             Web::Paste::Simple->new(
181             storage => Path::Class::Dir->new(...),
182             codemirror => "...",
183             template => Text::Template->new(...),
184             )->app;
185              
186             =head1 DESCRIPTION
187              
188             Web::Paste::Simple is a lightweight PSGI app for operating a
189             pastebin-like website. It provides syntax highlighting via the
190             L Javascript library. It
191             should be fast enough for deployment via CGI.
192              
193             It does not provide any authentication facilities or similar,
194             instead relying on you to use subclassing/roles or L
195             middleware to accomplish such things.
196              
197             =head2 Constructor
198              
199             =over
200              
201             =item C<< new(%attrs) >>
202              
203             Standard Moose-style constructor.
204              
205             This class is not based on Moose though; instead it uses L.
206              
207             =back
208              
209             =head2 Attributes
210              
211             The following attributes are defined:
212              
213             =over
214              
215             =item C
216              
217             A L indicating the directory where pastes
218             should be stored. Pastes are kept indefinitely. Each is a single
219             file.
220              
221             =item C
222              
223             Path to the CodeMirror syntax highlighter as a string. For example,
224             if CodeMirror is available at C<< http://example.com/js/lib/codemirror.js >>
225             then this string should be C<< http://example.com/js >> with no trailing
226             slash.
227              
228             This defaults to an address on my server, but for production sites,
229             I set up your own copy: it only takes a couple of minutes;
230             just a matter of unzipping a single archive. I offer no guarantees
231             about the continued availability of my copy of CodeMirror.
232              
233             Nothing is actually done with this variable, but it's passed to the
234             template.
235              
236             =item C