• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

examples/H20-Mar-2017-220183

lib/H20-Mar-2017-3,6291,944

maint/H20-Mar-2017-118

t/H20-Mar-2017-2,0281,640

ChangesH A D20-Mar-20174.3 KiB146110

MANIFESTH A D20-Mar-20171.4 KiB5756

META.jsonH A D20-Mar-20171.8 KiB6766

META.ymlH A D20-Mar-2017971 3736

Makefile.PLH A D20-Mar-20173 KiB10788

READMEH A D20-Mar-201727 KiB850627

README

1NAME
2    Web::Simple - A quick and easy way to build simple web applications
3
4SYNOPSIS
5      #!/usr/bin/env perl
6
7      package HelloWorld;
8      use Web::Simple;
9
10      sub dispatch_request {
11        GET => sub {
12          [ 200, [ 'Content-type', 'text/plain' ], [ 'Hello world!' ] ]
13        },
14        '' => sub {
15          [ 405, [ 'Content-type', 'text/plain' ], [ 'Method not allowed' ] ]
16        }
17      }
18
19      HelloWorld->run_if_script;
20
21    If you save this file into your cgi-bin as "hello-world.cgi" and then
22    visit:
23
24      http://my.server.name/cgi-bin/hello-world.cgi/
25
26    you'll get the "Hello world!" string output to your browser. At the same
27    time this file will also act as a class module, so you can save it as
28    HelloWorld.pm and use it as-is in test scripts or other deployment
29    mechanisms.
30
31    Note that you should retain the ->run_if_script even if your app is a
32    module, since this additionally makes it valid as a .psgi file, which
33    can be extremely useful during development.
34
35    For more complex examples and non-CGI deployment, see
36    Web::Simple::Deployment. To get help with Web::Simple, please connect to
37    the irc.perl.org IRC network and join #web-simple.
38
39DESCRIPTION
40    The philosophy of Web::Simple is to keep to an absolute bare minimum for
41    everything. It is not designed to be used for large scale applications;
42    the Catalyst web framework already works very nicely for that and is a
43    far more mature, well supported piece of software.
44
45    However, if you have an application that only does a couple of things,
46    and want to not have to think about complexities of deployment, then
47    Web::Simple might be just the thing for you.
48
49    The only public interface the Web::Simple module itself provides is an
50    "import" based one:
51
52      use Web::Simple 'NameOfApplication';
53
54    This sets up your package (in this case "NameOfApplication" is your
55    package) so that it inherits from Web::Simple::Application and imports
56    strictures, as well as installs a "PSGI_ENV" constant for convenience,
57    as well as some other subroutines.
58
59    Importing strictures will automatically make your code use the "strict"
60    and "warnings" pragma, so you can skip the usual:
61
62      use strict;
63      use warnings FATAL => 'all';
64
65    provided you 'use Web::Simple' at the top of the file. Note that we turn
66    on *fatal* warnings so if you have any warnings at any point from the
67    file that you did 'use Web::Simple' in, then your application will die.
68    This is, so far, considered a feature.
69
70    When we inherit from Web::Simple::Application we also use Moo, which is
71    the the equivalent of:
72
73      {
74        package NameOfApplication;
75        use Moo;
76        extends 'Web::Simple::Application';
77      }
78
79    So you can use Moo features in your application, such as creating
80    attributes using the "has" subroutine, etc. Please see the documentation
81    for Moo for more information.
82
83    It also exports the following subroutines for use in dispatchers:
84
85      response_filter { ... };
86
87      redispatch_to '/somewhere';
88
89    Finally, import sets
90
91      $INC{"NameOfApplication.pm"} = 'Set by "use Web::Simple;" invocation';
92
93    so that perl will not attempt to load the application again even if
94
95      require NameOfApplication;
96
97    is encountered in other code.
98
99    One important thing to remember when using
100
101      NameOfApplication->run_if_script;
102
103    At the end of your app is that this call will create an instance of your
104    app for you automatically, regardless of context. An easier way to think
105    of this would be if the method were more verbosely named
106
107     NameOfApplication->run_request_if_script_else_turn_coderef_for_psgi;
108
109DISPATCH STRATEGY
110    Web::Simple despite being straightforward to use, has a powerful system
111    for matching all sorts of incoming URLs to one or more subroutines.
112    These subroutines can be simple actions to take for a given URL, or
113    something more complicated, including entire Plack applications,
114    Plack::Middleware and nested subdispatchers.
115
116  Examples
117     sub dispatch_request {
118       (
119         # matches: GET /user/1.htm?show_details=1
120         #          GET /user/1.htm
121         'GET + /user/* + ?show_details~ + .htm|.html|.xhtml' => sub {
122           my ($self, $user_id, $show_details) = @_;
123           ...
124         },
125         # matches: POST /user?username=frew
126         #          POST /user?username=mst&first_name=matt&last_name=trout
127         'POST + /user + ?username=&*' => sub {
128            my ($self, $username, $misc_params) = @_;
129           ...
130         },
131         # matches: DELETE /user/1/friend/2
132         'DELETE + /user/*/friend/*' => sub {
133           my ($self, $user_id, $friend_id) = @_;
134           ...
135         },
136         # matches: PUT /user/1?first_name=Matt&last_name=Trout
137         'PUT + /user/* + ?first_name~&last_name~' => sub {
138           my ($self, $user_id, $first_name, $last_name) = @_;
139           ...
140         },
141         '/user/*/...' => sub {
142           my $user_id = $_[1];
143           (
144             # matches: PUT /user/1/role/1
145             'PUT + /role/*' => sub {
146               my $role_id = $_[1];
147               ...
148             },
149             # matches: DELETE /user/1/role/1
150             'DELETE + /role/*' => sub {
151               my $role_id = $_[1];
152               ...
153             },
154           );
155         },
156       );
157     }
158
159  The dispatch cycle
160    At the beginning of a request, your app's dispatch_request method is
161    called with the PSGI $env as an argument. You can handle the request
162    entirely in here and return a PSGI response arrayref if you want:
163
164      sub dispatch_request {
165        my ($self, $env) = @_;
166        [ 404, [ 'Content-type' => 'text/plain' ], [ 'Amnesia == fail' ] ]
167      }
168
169    However, generally, instead of that, you return a set of route/target
170    pairs:
171
172      sub dispatch_request {
173        my $self = shift;
174        (
175          '/' => sub { redispatch_to '/index.html' },
176          '/user/*' => sub { $self->show_user($_[1]) },
177          'POST + %*' => 'handle_post',
178          ...
179        );
180      }
181
182    Well, a sub is a valid PSGI response too (for ultimate streaming and
183    async cleverness). If you want to return a PSGI sub you have to wrap it
184    into an array ref.
185
186      sub dispatch_request {
187        [ sub {
188            my $respond = shift;
189            # This is pure PSGI here, so read perldoc PSGI
190        } ]
191      }
192
193    If you return a string followed by a subroutine or method name, the
194    string is treated as a match specification - and if the test is passed,
195    the subroutine is called as a method and passed any matched arguments
196    (see below for more details).
197
198    You can also return a plain subroutine which will be called with just
199    $env - remember that in this case if you need $self you must close over
200    it.
201
202    If you return a normal object, Web::Simple will simply return it upwards
203    on the assumption that a response_filter (or some arbitrary
204    Plack::Middleware) somewhere will convert it to something useful. This
205    allows:
206
207      sub dispatch_request {
208        my $self = shift;
209        (
210          '.html' => sub { response_filter { $self->render_zoom($_[0]) } },
211          '/user/*' => sub { $self->users->get($_[1]) },
212        );
213      }
214
215    An alternative to using string + suborutine to declare a route is to use
216    the sub prototype -
217
218      sub dispatch_request {
219        my $self = shift;
220        (
221          sub (.html) { response_filter { $self->render_zoom($_[0]) } },
222          sub (/user/) { $self->users->get($_[1]) },
223          $self->can('handle_post'), # if declared as 'sub handle_post (...) {'
224        )
225      }
226
227    This can be useful sugar, especially if you want to keep method-based
228    dispatchers' route specifications on the methods.
229
230    to render a user object to HTML, if there is an incoming URL such as:
231
232      http://myweb.org/user/111.html
233
234    This works because as we descend down the dispachers, we first match
235    "sub (.html)", which adds a "response_filter" (basically a specialized
236    routine that follows the Plack::Middleware specification), and then
237    later we also match "sub (/user/*)" which gets a user and returns that
238    as the response. This user object 'bubbles up' through all the wrapping
239    middleware until it hits the "response_filter" we defined, after which
240    the return is converted to a true html response.
241
242    However, two types of objects are treated specially - a
243    "Plack::Component" object will have its "to_app" method called and be
244    used as a dispatcher:
245
246      sub dispatch_request {
247        my $self = shift;
248        (
249          '/static/...' => sub { Plack::App::File->new(...) },
250          ...
251        );
252      }
253
254    A Plack::Middleware object will be used as a filter for the rest of the
255    dispatch being returned into:
256
257      ## responds to /admin/track_usage AND /admin/delete_accounts
258
259      sub dispatch_request {
260        my $self = shift;
261        (
262          '/admin/**' => sub {
263            Plack::Middleware::Session->new(%opts);
264          },
265          '/admin/track_usage' => sub {
266            ## something that needs a session
267          },
268          '/admin/delete_accounts' => sub {
269            ## something else that needs a session
270          },
271        );
272      }
273
274    Note that this is for the dispatch being returned to, so if you want to
275    provide it inline you need to do:
276
277      ## ALSO responds to /admin/track_usage AND /admin/delete_accounts
278
279      sub dispatch_request {
280        my $self = shift;
281        (
282          '/admin/...' => sub {
283            (
284              sub {
285                Plack::Middleware::Session->new(%opts);
286              },
287              '/track_usage' => sub {
288                ## something that needs a session
289              },
290              '/delete_accounts' => sub {
291                ## something else that needs a session
292              },
293            );
294          }
295        );
296      }
297
298    And that's it - but remember that all this happens recursively - it's
299    dispatchers all the way down. A URL incoming pattern will run all
300    matching dispatchers and then hit all added filters or
301    Plack::Middleware.
302
303  Web::Simple match specifications
304   Method matches
305      'GET' => sub {
306
307    A match specification beginning with a capital letter matches HTTP
308    requests with that request method.
309
310   Path matches
311      '/login' => sub {
312
313    A match specification beginning with a / is a path match. In the
314    simplest case it matches a specific path. To match a path with a
315    wildcard part, you can do:
316
317      '/user/*' => sub {
318        $self->handle_user($_[1])
319
320    This will match /user/<anything> where <anything> does not include a
321    literal / character. The matched part becomes part of the match
322    arguments. You can also match more than one part:
323
324      '/user/*/*' => sub {
325        my ($self, $user_1, $user_2) = @_;
326
327      '/domain/*/user/*' => sub {
328        my ($self, $domain, $user) = @_;
329
330    and so on. To match an arbitrary number of parts, use "**":
331
332      '/page/**' => sub {
333        my ($self, $match) = @_;
334
335    This will result in a single element for the entire match. Note that you
336    can do
337
338      '/page/**/edit' => sub {
339
340    to match an arbitrary number of parts up to but not including some final
341    part.
342
343    Note: Since Web::Simple handles a concept of file extensions, "*" and
344    "**" matchers will not by default match things after a final dot, and
345    this can be modified by using "*.*" and "**.*" in the final position,
346    e.g.:
347
348      /one/*       matches /one/two.three    and captures "two"
349      /one/*.*     matches /one/two.three    and captures "two.three"
350      /**          matches /one/two.three    and captures "one/two"
351      /**.*        matches /one/two.three    and captures "one/two.three"
352
353    Finally,
354
355      '/foo/...' => sub {
356
357    Will match "/foo/" on the beginning of the path and strip it. This is
358    designed to be used to construct nested dispatch structures, but can
359    also prove useful for having e.g. an optional language specification at
360    the start of a path.
361
362    Note that the '...' is a "maybe something here, maybe not" so the above
363    specification will match like this:
364
365      /foo         # no match
366      /foo/        # match and strip path to '/'
367      /foo/bar/baz # match and strip path to '/bar/baz'
368
369    Almost the same,
370
371      '/foo...' => sub {
372
373    Will match on "/foo/bar/baz", but also include "/foo". Otherwise it
374    operates the same way as "/foo/...".
375
376      /foo         # match and strip path to ''
377      /foo/        # match and strip path to '/'
378      /foo/bar/baz # match and strip path to '/bar/baz'
379
380    Please note the difference between "sub(/foo/...)" and "sub(/foo...)".
381    In the first case, this is expecting to find something after "/foo" (and
382    fails to match if nothing is found), while in the second case we can
383    match both "/foo" and "/foo/more/to/come". The following are roughly the
384    same:
385
386      '/foo'     => sub { 'I match /foo' },
387      '/foo/...' => sub {
388        (
389          '/bar' => sub { 'I match /foo/bar' },
390          '/*'   => sub { 'I match /foo/{id}' },
391        );
392      }
393
394    Versus
395
396      '/foo...' => sub {
397        (
398          '~'    => sub { 'I match /foo' },
399          '/bar' => sub { 'I match /foo/bar' },
400          '/*'   => sub { 'I match /foo/{id}' },
401        );
402      }
403
404    You may prefer the latter example should you wish to take advantage of
405    subdispatchers to scope common activities. For example:
406
407      '/user...' => sub {
408        my $user_rs = $schema->resultset('User');
409        (
410          '~' => sub { $user_rs },
411          '/*' => sub { $user_rs->find($_[1]) },
412        );
413      }
414
415    You should note the special case path match "sub (~)" which is only
416    meaningful when it is contained in this type of path match. It matches
417    to an empty path.
418
419   Naming your patch matches
420    Any "*", "**", "*.*", or "**.*" match can be followed with ":name" to
421    make it into a named match, so:
422
423      '/*:one/*:two/*:three/*:four' => sub {
424        "I match /1/2/3/4 capturing { one => 1, two =>  2, three => 3, four => 4 }"
425      }
426
427      '/**.*:allofit' => sub {
428        "I match anything capturing { allofit => \$whole_path }"
429      }
430
431    In the specific case of a simple single-* match, the * may be omitted,
432    to allow you to write:
433
434      '/:one/:two/:three/:four' => sub {
435        "I match /1/2/3/4 capturing { one => 1, two =>  2, three => 3, four => 4 }"
436      }
437
438   "/foo" and "/foo/" are different specs
439    As you may have noticed with the difference between '/foo/...' and
440    '/foo...', trailing slashes in path specs are significant. This is
441    intentional and necessary to retain the ability to use relative links on
442    websites. Let's demonstrate on this link:
443
444      <a href="bar">bar</a>
445
446    If the user loads the url "/foo/" and clicks on this link, they will be
447    sent to "/foo/bar". However when they are on the url "/foo" and click
448    this link, then they will be sent to "/bar".
449
450    This makes it necessary to be explicit about the trailing slash.
451
452   Extension matches
453      '.html' => sub {
454
455    will match .html from the path (assuming the subroutine itself returns
456    something, of course). This is normally used for rendering - e.g.:
457
458      '.html' => sub {
459        response_filter { $self->render_html($_[1]) }
460      }
461
462    Additionally,
463
464      '.*' => sub {
465
466    will match any extension and supplies the extension as a match argument.
467
468   Query and body parameter matches
469    Query and body parameters can be match via
470
471      '?<param spec>' => sub { # match URI query
472      '%<param spec>' => sub { # match body params
473
474    The body spec will match if the request content is either
475    application/x-www-form-urlencoded or multipart/form-data - the latter of
476    which is required for uploads - see below.
477
478    The param spec is elements of one of the following forms:
479
480      param~        # optional parameter
481      param=        # required parameter
482      @param~       # optional multiple parameter
483      @param=       # required multiple parameter
484      :param~       # optional parameter in hashref
485      :param=       # required parameter in hashref
486      :@param~      # optional multiple in hashref
487      :@param=      # required multiple in hashref
488      *             # include all other parameters in hashref
489      @*            # include all other parameters as multiple in hashref
490
491    separated by the "&" character. The arguments added to the request are
492    one per non-":"/"*" parameter (scalar for normal, arrayref for
493    multiple), plus if any ":"/"*" specs exist a hashref containing those
494    values. If a parameter has no value, i.e. appears as '?foo&', a value of
495    1 will be captured.
496
497    Please note that if you specify a multiple type parameter match, you are
498    ensured of getting an arrayref for the value, EVEN if the current
499    incoming request has only one value. However if a parameter is specified
500    as single and multiple values are found, the last one will be used.
501
502    For example to match a "page" parameter with an optional "order_by"
503    parameter one would write:
504
505      '?page=&order_by~' => sub {
506        my ($self, $page, $order_by) = @_;
507        return unless $page =~ /^\d+$/;
508        $order_by ||= 'id';
509        response_filter {
510          $_[1]->search_rs({}, { page => $page, order_by => $order_by });
511        }
512      }
513
514    to implement paging and ordering against a DBIx::Class::ResultSet
515    object.
516
517    Another Example: To get all parameters as a hashref of arrayrefs, write:
518
519      '?@*' => sub {
520        my ($self, $params) = @_;
521        ...
522
523    To get two parameters as a hashref, write:
524
525      '?:user~&:domain~' => sub {
526        my ($self, $params) = @_; # params contains only 'user' and 'domain' keys
527
528    You can also mix these, so:
529
530      '?foo=&@bar~&:coffee=&@*' => sub {
531         my ($self, $foo, $bar, $params) = @_;
532
533    where $bar is an arrayref (possibly an empty one), and $params contains
534    arrayref values for all parameters not mentioned and a scalar value for
535    the 'coffee' parameter.
536
537    Note, in the case where you combine arrayref, single parameter and named
538    hashref style, the arrayref and single parameters will appear in @_ in
539    the order you defined them in the prototype, but all hashrefs will merge
540    into a single $params, as in the example above.
541
542   Upload matches
543      '*foo=' => sub { # param specifier can be anything valid for query or body
544
545    The upload match system functions exactly like a query/body match,
546    except that the values returned (if any) are "Web::Dispatch::Upload"
547    objects.
548
549    Note that this match type will succeed in two circumstances where you
550    might not expect it to - first, when the field exists but is not an
551    upload field and second, when the field exists but the form is not an
552    upload form (i.e. content type "application/x-www-form-urlencoded"
553    rather than "multipart/form-data"). In either of these cases, what
554    you'll get back is a "Web::Dispatch::NotAnUpload" object, which will
555    "die" with an error pointing out the problem if you try and use it. To
556    be sure you have a real upload object, call
557
558      $upload->is_upload # returns 1 on a valid upload, 0 on a non-upload field
559
560    and to get the reason why such an object is not an upload, call
561
562      $upload->reason # returns a reason or '' on a valid upload.
563
564    Other than these two methods, the upload object provides the same
565    interface as Plack::Request::Upload with the addition of a stringify to
566    the temporary filename to make copying it somewhere else easier to
567    handle.
568
569   Combining matches
570    Matches may be combined with the + character - e.g.
571
572      'GET + /user/*' => sub {
573
574    to create an AND match. They may also be combined with the | character -
575    e.g.
576
577      'GET|POST' => sub {
578
579    to create an OR match. Matches can be nested with () - e.g.
580
581      '(GET|POST + /user/*)' => sub {
582
583    and negated with ! - e.g.
584
585      '!/user/foo + /user/*' => sub {
586
587    ! binds to the immediate rightmost match specification, so if you want
588    to negate a combination you will need to use
589
590      '!(POST|PUT|DELETE)' => sub {
591
592    and | binds tighter than +, so
593
594      '(GET|POST) + /user/*' => sub {
595
596    and
597
598      'GET|POST + /user/*' => sub {
599
600    are equivalent, but
601
602      '(GET + /admin/...) | (POST + /admin/...)' => sub {
603
604    and
605
606      'GET + /admin/... | POST + /admin/...' => sub {
607
608    are not - the latter is equivalent to
609
610      'GET + (/admin/...|POST) + /admin/...' => sub {
611
612    which will never match!
613
614   Whitespace
615    Note that for legibility you are permitted to use whitespace:
616
617      'GET + /user/*' => sub {
618
619    but it will be ignored. This is because the perl parser strips
620    whitespace from subroutine prototypes, so this is equivalent to
621
622      'GET+/user/*' => sub {
623
624   Accessing parameters via %_
625    If your dispatch specification causes your dispatch subroutine to
626    receive a hash reference as its first argument, the contained named
627    parameters will be accessible via %_.
628
629    This can be used to access your path matches, if they are named:
630
631      'GET + /foo/:path_part' => sub {
632        [ 200,
633          ['Content-type' => 'text/plain'],
634          ["We are in $_{path_part}"],
635        ];
636      }
637
638    Or, if your first argument would be a hash reference containing named
639    query parameters:
640
641      'GET + /foo + ?:some_param=' => sub {
642        [ 200,
643          ['Content-type' => 'text/plain'],
644          ["We received $_{some_param} as parameter"],
645        ];
646      }
647
648    Of course this also works when all you are doing is slurping the whole
649    set of parameters by their name:
650
651      'GET + /foo + ?*' => sub {
652        [ 200,
653          ['Content-type' => 'text/plain'],
654          [exists($_{foo}) ? "Received a foo: $_{foo}" : "No foo!"],
655        ],
656      }
657
658    Note that only the first hash reference will be available via %_. If you
659    receive additional hash references, you will need to access them as
660    usual.
661
662   Accessing the PSGI env hash
663    In some cases you may wish to get the raw PSGI env hash - to do this,
664    you can either use a plain sub:
665
666      sub {
667        my ($env) = @_;
668        ...
669      }
670
671    or use the "PSGI_ENV" constant exported to retrieve it from @_:
672
673      'GET + /foo + ?some_param=' => sub {
674        my $param = $_[1];
675        my $env = $_[PSGI_ENV];
676      }
677
678    but note that if you're trying to add a middleware, you should simply
679    use Web::Simple's direct support for doing so.
680
681EXPORTED SUBROUTINES
682  response_filter
683      response_filter {
684        # Hide errors from the user because we hates them, preciousss
685        if (ref($_[0]) eq 'ARRAY' && $_[0]->[0] == 500) {
686          $_[0] = [ 200, @{$_[0]}[1..$#{$_[0]}] ];
687        }
688        return $_[0];
689      };
690
691    The response_filter subroutine is designed for use inside dispatch
692    subroutines.
693
694    It creates and returns a special dispatcher that always matches, and
695    calls the block passed to it as a filter on the result of running the
696    rest of the current dispatch chain.
697
698    Thus the filter above runs further dispatch as normal, but if the result
699    of dispatch is a 500 (Internal Server Error) response, changes this to a
700    200 (OK) response without altering the headers or body.
701
702  redispatch_to
703      redispatch_to '/other/url';
704
705    The redispatch_to subroutine is designed for use inside dispatch
706    subroutines.
707
708    It creates and returns a special dispatcher that always matches, and
709    instead of continuing dispatch re-delegates it to the start of the
710    dispatch process, but with the path of the request altered to the
711    supplied URL.
712
713    Thus if you receive a POST to "/some/url" and return a redispatch to
714    "/other/url", the dispatch behaviour will be exactly as if the same POST
715    request had been made to "/other/url" instead.
716
717    Note, this is not the same as returning an HTTP 3xx redirect as a
718    response; rather it is a much more efficient internal process.
719
720CHANGES BETWEEN RELEASES
721  Changes between 0.004 and 0.005
722    *   dispatch {} replaced by declaring a dispatch_request method
723
724        dispatch {} has gone away - instead, you write:
725
726          sub dispatch_request {
727            my $self = shift;
728            (
729              'GET /foo/' => sub { ... },
730              ...
731            );
732          }
733
734        Note that this method is still returning the dispatch code - just
735        like "dispatch" did.
736
737        Also note that you need the "my $self = shift" since the magic $self
738        variable went away.
739
740    *   the magic $self variable went away.
741
742        Just add "my $self = shift;" while writing your "sub
743        dispatch_request {" like a normal perl method.
744
745    *   subdispatch deleted - all dispatchers can now subdispatch
746
747        In earlier releases you needed to write:
748
749          subdispatch sub (/foo/...) {
750            ...
751            [
752              sub (GET /bar/) { ... },
753              ...
754            ]
755          }
756
757        As of 0.005, you can instead write simply:
758
759          sub (/foo/...) {
760            ...
761            (
762              sub (GET /bar/) { ... },
763              ...
764            )
765          }
766
767  Changes since Antiquated Perl
768    *   filter_response renamed to response_filter
769
770        This is a pure rename; a global search and replace should fix it.
771
772    *   dispatch [] changed to dispatch {}
773
774        Simply changing
775
776          dispatch [ sub(...) { ... }, ... ];
777
778        to
779
780          dispatch { sub(...) { ... }, ... };
781
782        should work fine.
783
784DEVELOPMENT HISTORY
785    Web::Simple was originally written to form part of my Antiquated Perl
786    talk for Italian Perl Workshop 2009, but in writing the bloggery example
787    I realised that having a bare minimum system for writing web
788    applications that doesn't drive me insane was rather nice and decided to
789    spend my attempt at nanowrimo for 2009 improving and documenting it to
790    the point where others could use it.
791
792    The Antiquated Perl talk can be found at
793    <http://www.shadowcat.co.uk/archive/conference-video/> and the slides
794    are reproduced in this distribution under Web::Simple::AntiquatedPerl.
795
796COMMUNITY AND SUPPORT
797  IRC channel
798    irc.perl.org #web-simple
799
800  No mailing list yet
801    Because mst's non-work email is a bombsite so he'd never read it anyway.
802
803  Git repository
804    Gitweb is on http://git.shadowcat.co.uk/ and the clone URL is:
805
806      git clone git://git.shadowcat.co.uk/catagits/Web-Simple.git
807
808AUTHOR
809    Matt S. Trout (mst) <mst@shadowcat.co.uk>
810
811CONTRIBUTORS
812    Devin Austin (dhoss) <dhoss@cpan.org>
813
814    Arthur Axel 'fREW' Schmidt <frioux@gmail.com>
815
816    gregor herrmann (gregoa) <gregoa@debian.org>
817
818    John Napiorkowski (jnap) <jjn1056@yahoo.com>
819
820    Josh McMichael <jmcmicha@linus222.gsc.wustl.edu>
821
822    Justin Hunter (arcanez) <justin.d.hunter@gmail.com>
823
824    Kjetil Kjernsmo <kjetil@kjernsmo.net>
825
826    markie <markie@nulletch64.dreamhost.com>
827
828    Christian Walde (Mithaldu) <walde.christian@googlemail.com>
829
830    nperez <nperez@cpan.org>
831
832    Robin Edwards <robin.ge@gmail.com>
833
834    Andrew Rodland (hobbs) <andrew@cleverdomain.org>
835
836    Robert Sedlacek (phaylon) <r.sedlacek@shadowcat.co.uk>
837
838    Hakim Cassimally (osfameron) <osfameron@cpan.org>
839
840    Karen Etheridge (ether) <ether@cpan.org>
841
842COPYRIGHT
843    Copyright (c) 2011 the Web::Simple "AUTHOR" and "CONTRIBUTORS" as listed
844    above.
845
846LICENSE
847    This library is free software and may be distributed under the same
848    terms as perl itself.
849
850