package RapidApp::Builder; use strict; use warnings; # ABSTRACT: Plack-compatable, runtime-generated RapidApp loader use Moose; use MooseX::NonMoose; extends qw/ Plack::Component CatalystX::AppBuilder /; use Types::Standard qw(:all); use Class::Load 'is_class_loaded'; require Module::Locate; use RapidApp::Util ':all'; sub BUILD { my $self = shift; if(my $base_cnf = $self->base_config) { %{ $self->config } = %{ Catalyst::Utils::merge_hashes( $base_cnf, $self->config ) } } if (my $list = $self->inject_components) { $self->config->{ra_inject_components} ||= []; push @{ $self->config->{ra_inject_components} }, @$list; } # Save a reference to the builder in the config - exposed via $c->ra_builder $self->config->{_ra_builder} = $self; } has 'base_appname', is => 'ro', isa => Maybe[Str], default => sub { undef }; has 'appname', is => 'ro', isa => Str, lazy => 1, default => sub { my $self = shift; my $base = $self->base_appname or die "Must supply either 'appname' or 'base_appname'"; my ($class, $i) = ($base,0); # Aggressively ensure the class name is not already used $class = join('',$base,++$i) while ( is_class_loaded($class) || Module::Locate::locate($class) ); $class }; # -- base_plugins and base_config are optional, private attrs that are available # so authors can apply plugins/config values, but allow their users to still # set 'config' and 'plugins' in the constructor w/o clobbering the base value. has 'base_plugins', is => 'ro', isa => 'ArrayRef', lazy_build => 1; has 'base_config', is => 'ro', isa => 'Maybe[HashRef]', lazy_build => 1; sub _build_base_plugins { [] } sub _build_base_config { undef } # -- # Don't use any of the defaults from the superclass: sub _build_plugins {[]} # Default version should derive from Builder subclass sub _build_version { (shift)->VERSION } around 'plugins' => sub { my ($orig,$self,@args) = @_; my @plugins = ( 'RapidApp', @{ $self->base_plugins }, @{ $self->$orig(@args) } ); # Handle debug properly: unshift @plugins, '-Debug' if ($self->debug); [ uniq( @plugins ) ] }; has 'inject_components', is => 'ro', isa => Maybe[ArrayRef[ArrayRef[Str]]], lazy => 1, default => sub { undef }; has '_bootstrap_called', is => 'rw', isa => Bool, default => sub {0}, init_arg => undef; after 'bootstrap' => sub { (shift)->_bootstrap_called(1) }; sub ensure_bootstrapped { my $self = shift; $self->_bootstrap_called ? 1 : $self->bootstrap(@_) } has '_psgi_app', is => 'ro', lazy => 1, default => sub { my $self = shift; $self->ensure_bootstrapped(1); my $c = $self->appname; $c->apply_default_middlewares($c->psgi_app) }, init_arg => undef; sub psgi_app { (shift)->to_app(@_) } # Plack::Component methods: sub prepare_app { (shift)->ensure_bootstrapped } sub call { my ($self, $env) = @_; $self->_psgi_app->($env) } 1; __END__ =head1 NAME RapidApp::Builder - Plack-compatable, runtime-generated RapidApp loader =head1 SYNOPSIS use RapidApp::Builder; my $builder = RapidApp::Builder->new( debug => 1, appname => "My::App", plugins => [ ... ], config => { ... } ); # Plack app: $builder->to_app =head1 DESCRIPTION This module is an extension to both L and L and facilitates programatically creating/instantiating a RapidApp application without having to setup/bootstrap files on disk. As a L, it can also be used anywhere Plack is supported, and can subclassed in the same manner as any L class. ... =head1 CONFIGURATION =head2 appname Class name of the RapidApp/Catalyst app to be built. =head2 base_appname Alternative to C, but will append a number if the specified class already exists (loaded or unloaded, but found in @INC). For example, if set to C, if MyApp already exists, the appname is set to , if that exists it is set to C and so on. =head2 plugins List of Catalyst plugins to load. The plugin 'RapidApp' is always loaded, and '-Debug' is loaded when C is set. =head2 inject_components Optional list of components (i.e. Catalyst Models, Views and Controllers) to inject into the application. These should be specified as 2-value ArrayRefs with the class name to inject as the first argument, and the name to inject it as in the application (relative to the app namespace) as the second argument. For example, to inject a controller named 'Blah': inject_components => [ [ 'Some::Catalyst::Controller::Foo' => 'Controller::Blah' ] ] =head2 debug Boolean flag to enable debug output in the application. When set, adds C<-Debug> to the plugins list. =head2 version The C<$VERSION> string to use =head1 METHODS =head2 psgi_app Same as C =head2 to_app PSGI C<$app> CodeRef. Derives from L =head1 SEE ALSO =over =item * L =back =head1 AUTHOR Henry Van Styn =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2015 by IntelliTree Solutions llc. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut