package Squatting;
use strict;
no strict 'refs';
#use warnings;
#no warnings 'redefine';
use base 'Class::C3::Componentised';
use List::Util qw(first);
use URI::Escape;
use Carp;
use Data::Dump 'pp';
our $VERSION = '0.81';
require Squatting::Controller;
require Squatting::View;
# XXX - deprecated | use App ':controllers'
# XXX - deprecated | use App ':views'
# use App @PLUGINS
#
# No longer have to : use base 'Squatting';
# Simply saying : use Squatting;
# will muck with the calling packages @ISA.
sub import {
my $m = shift;
my $p = (caller)[0];
if ($m ne 'Squatting') {
return $m->load_components(grep /::/, @_);
}
push @{$p.'::ISA'}, 'Squatting';
# $url = R('Controller', @args, { cgi => vars }) # Generate URLs with the routing function
*{$p."::Controllers::R"} = *{$p."::Views::R"} = *{$p."::R"} = sub {
my ($controller, @args) = @_;
my $input;
if (@args && ref($args[-1]) eq 'HASH') {
$input = pop(@args);
}
my $c = ${$p."::Controllers::C"}{$controller};
croak "$controller controller not found in '\%$p\::Controllers::C" unless $c;
my $arity = @args;
my $path = first { my @m = /\(.*?\)/g; $arity == @m } @{$c->urls};
croak "couldn't find a matching URL path" unless $path;
while ($path =~ /\(.*?\)/) {
$path =~ s{\(.*?\)}{uri_escape(+shift(@args), "^A-Za-z0-9\-_.!~*’()/")}e;
}
if ($input) {
$path .= "?". join('&' =>
map {
my $k = $_;
ref($input->{$_}) eq 'ARRAY'
? map { "$k=".uri_escape($_) } @{$input->{$_}}
: "$_=".uri_escape($input->{$_})
} keys %$input);
}
$path;
};
# ($controller, \@regex_captures) = D($path) # Return controller and captures for a path
*{$p."::D"} = sub {
my $url = uri_unescape($_[0]);
my $C = \@{$p.'::Controllers::C'};
my ($c, @regex_captures);
for $c (@$C) {
for (@{$c->urls}) {
if (@regex_captures = ($url =~ qr{^$_$})) {
pop @regex_captures if ($#+ == 0);
return ($c, \@regex_captures);
}
}
}
($Squatting::Controller::r404, []);
};
*{$p."::Controllers::C"} = sub {
Squatting::Controller->new(@_, app => $p)
};
*{$p."::Views::V"} = sub {
Squatting::View->new(@_)
};
}
# Squatting plugins may be anywhere in Squatting::*::* but by convention
# (and for fun) you should use poetic diction in your package names.
#
# Squatting::On::Continuity
# Squatting::On::Catalyst
# Squatting::On::CGI
# Squatting::On::Jifty
#
# (ALL YOUR FRAMEWORK ARE BELONG TO US)
#
# Squatting::With::Impunity (What could we do w/ this name?)
# Squatting::With::Log4Perl (which is how we could add logging support)
#
# (etc)
sub component_base_class { __PACKAGE__ }
# 1
# App->mount($AnotherApp, $prefix) # Map another app on to a URL $prefix.
sub mount {
my ($app, $other, $prefix) = @_;
push @{$app."::O"}, $other;
push @{$app."::Controllers::C"}, map {
my $urls = $_->urls;
$_->urls = [ map { $prefix.$_ } @$urls ];
$_;
} @{$other."::Controllers::C"}
}
# 2
# App->relocate($prefix) # Map main app to a URL $prefix
sub relocate {
my ($app, $prefix) = @_;
for (@{$app."::Controllers::C"}) {
my $urls = $_->urls;
$_->urls = [ map { $prefix.$_ } @$urls ];
}
${$app."::CONFIG"}{relocated} = $prefix;
}
# 3
# App->init # Initialize $app
sub init {
$_->init for (@{$_[0]."::O"});
%{$_[0]."::Controllers::C"} = map { $_->name => $_ } @{$_[0]."::Controllers::C"};
%{$_[0]."::Views::V"} = map { $_->name => $_ } @{$_[0]."::Views::V"};
}
# App->service($controller, @args) # Handle an HTTP request
sub service {
my ($app, $c, @args) = grep { defined } @_;
my $method = lc $c->env->{REQUEST_METHOD};
my $content;
eval { $content = $c->$method(@args) };
warn "EXCEPTION: $@" if ($@);
my $cookies = $c->cookies;
$c->headers->{'Set-Cookie'} = join("; ",
map { CGI::Cookie->new( -name => $_, %{$cookies->{$_}} ) }
grep { ref $cookies->{$_} eq 'HASH' }
keys %$cookies) if (%$cookies);
$content;
}
1;
=head1 NAME
Squatting - A Camping-inspired Web Microframework for Perl
=head1 SYNOPSIS
Running an App:
$ squatting App
Please contact me at: http://localhost:4234/
Check out our ASCII art logo:
$ squatting --logo
What a basic App looks like:
# STEP 1 => Use Squatting for your App
{
package App; # <-- I hope it's obvious that this name can whatever you want.
use Squatting;
our %CONFIG; # <-- standard app config goes here
}
# STEP 2 => Define the App's Controllers
{
package App::Controllers;
# Setup a list of controller objects in @C using the C() function.
our @C = (
C(
Home => [ '/' ],
get => sub {
my ($self) = @_;
my $v = $self->v;
$v->{title} = 'A Simple Squatting Application';
$v->{message} = 'Hello, World!';
$self->render('home');
},
post => sub { }
),
);
}
# STEP 3 => Define the App's Views
{
package App::Views;
# Setup a list of view objects in @V using the V() function.
our @V = (
V(
'html',
layout => sub {
my ($self, $v, $content) = @_;
"
$v->{title}".
"$content";
},
home => sub {
my ($self, $v) = @_;
"$v->{message}
"
},
),
);
}
# Models?
# - The whole world is your model. ;-)
# - I have no interest in defining policy here.
# - Use whatever works for you.
=head1 DESCRIPTION
Squatting is a web microframework based on Camping.
It originally used L as its foundation,
but it has since been generalized such that it can
squat on top of any Perl-based web framework (in theory).
=head2 What does this mean?
=over 4
=item B
_why did a really good job designing Camping's API so that you could get the
B done with the B amount of code possible. I loved Camping's API
so much that I ported it to Perl.
=item B
The core of Squatting (which includes Squatting, Squatting::Controller, and
Squatting::View) can be squished into less than 4K of obfuscated perl. Also,
the number of Perl module dependencies has been kept down to a minimum.
=item B
Controllers are objects (not classes) that are made to look like HTTP
resources. Thus, they respond to methods like get(), post(), put(), and
delete().
=item B
Stateful continuation-based code can be surprisingly useful (especially for
COMET), so we try to make RESTless controllers easy to express as well. (B<*>)
=item B
Views are also objects (not classes) whose methods represent templates to be
rendered. An app can also have more than one view. Changing a Squatting app's
look and feel can be as simple as swapping out one view object for another.
=item B
You can take multiple Squatting apps and compose them into a single app. For
example, suppose you built a site and decided that you'd like to add a forum.
You could take a hypothetical forum app written in Squatting and just mount
it at an arbitrary path like /forum.
=item B
Already using another framework? No problem. You should be able to embed
Squatting apps into apps written in anything from CGI on up to Catalyst.
B
=item B
You may use any templating system you want, and you may use any ORM you
want. We only have a few rules on how the controller code and the view code
should be organized, but beyond that, you are free as you want to be.
=back
B<*> RESTless controllers currently only work when you're L.
=head1 API
=head2 Use as a Base Class for Squatting Applications
package App;
use Squatting;
our %CONFIG = ();
1;
Just C