/usr/local/CPAN/Cog/Cog/Config.pm
#TODO:
# - Support uri_base
# - Support uri_port
# - Support uri_path
# - Support daemon, logfile and pid
# - plugins can update config to map urls to code
# - Make all config options 'foo' respect $COG_FOO
package Cog::Config;
use Mouse;
use File::ShareDir;
use Cwd qw(abs_path);
use IO::All;
# use XXX;
### These options are set by user in config file:
# Common webapp options
has site_name => (is => 'ro');
has home_page_id => (is => 'ro');
has html_title => (is => 'ro');
# Server options
has server_port => (is => 'ro', default => '');
has proxymap => (is => 'ro');
has cache_urls => (is => 'ro');
### These fields are part of the Cog framework:
# Bootstrapping config values (root directories)
has app_class => (
is => 'ro',
required => 1,
);
has app_root => (
is => 'ro',
lazy => 1,
default => ($ENV{COG_APP_ROOT} || 'cog'),
);
has store_root => (
is => 'ro',
lazy => 1,
builder => sub { abs_path('.') },
);
has content_root => (
is => 'ro',
lazy => 1,
builder => sub { abs_path('.') },
);
# Cog singleton object references
has webapp => (
is => 'ro',
lazy => 1,
builder => sub { $_[0]->object_builder('webapp', 'Cog::WebApp') },
);
has runner => (
is => 'ro',
lazy => 1,
builder => sub {
$_[0]->classes->{runner} = $_[0]->webapp->runner_class;
$_[0]->object_builder('runner', 'Cog::WebApp::Runner');
},
);
has maker => (
is => 'ro',
lazy => 1,
builder => sub { $_[0]->object_builder('maker', 'Cog::Maker') },
);
has store => (
is => 'ro',
lazy => 1,
builder => sub { $_[0]->object_builder('store', 'Cog::Store') },
);
has content => (
is => 'ro',
lazy => 1,
builder => sub { $_[0]->object_builder('content', 'Cog::Content') },
);
has view => (
is => 'ro',
lazy => 1,
builder => sub { $_[0]->object_builder('view', 'Cog::View') },
);
sub object_builder {
my ($self, $type, $base) = @_;
my $class = $self->classes->{$type};
unless (UNIVERSAL::isa($class, $base)) {
eval "require $class; 1" or die $@;
}
return $class->new();
}
# App Command Values
has command_script => (is => 'ro');
has command_args => (is => 'ro', default => sub{[]});
# App & WebApp definitions
has url_map => (is => 'ro', default => sub{[]});
has post_map => (is => 'ro', default => sub{[]});
has js_files => (is => 'ro', default => sub{[]});
has css_files => (is => 'ro', default => sub{[]});
has image_files => (is => 'ro', default => sub{[]});
has template_files => (is => 'ro', default => sub{[]});
has site_navigation => (is => 'ro', default => sub{[]});
has files_map => (is => 'ro', builder => '_build_files_map', lazy => 1);
has classes => (is => 'rw');
has all_js_file => (is => 'rw');
has all_css_file => (is => 'rw');
# App readiness
has is_init => (is => 'ro', default => 0);
has is_config => (is => 'ro', default => 0);
has is_ready => (is => 'ro', default => 0);
# Private accessors
has _plugins => (is => 'ro', default => sub{[]});
has _class_share_map => (is => 'ro', default => sub{{}});
# Build the config object scanning through all the classes and merging
# their capabilites together appropriately.
#
# This is the hard part...
sub BUILD {
my $self = shift;
my $root = $self->app_root;
$self->{is_init} = 1
if -d "$root/static";
$self->{is_config} = 1
if -e "$root/config.yaml";
$self->{is_ready} = 1
if -d "$root/static";
$self->build_plugin_list();
$self->build_class_share_map();
$self->build_list('url_map', 'lol');
$self->build_list('post_map', 'lol');
$self->build_list('site_navigation', 'lol');
$self->build_list('js_files');
$self->build_list('css_files');
$self->build_list('image_files');
$self->build_list('template_files');
$self->find_classes();
return $self;
}
sub build_plugin_list {
my $self = shift;
my $list = [];
my $expanded = {};
$self->expand_list($list, $self->app_class, $expanded);
$self->{_plugins} = $list;
}
sub expand_list {
my ($self, $list, $plugin, $expanded) = @_;
return if $expanded->{$plugin};
$expanded->{$plugin} = 1;
eval "use $plugin";
die "use $plugin; error: $@"
if $@ and $@ !~ /Can't locate/;
unshift @$list, $plugin;
my $adds = [];
my $parent;
{
no strict 'refs';
$parent = ${"${plugin}::ISA"}[0];
}
if ($plugin->isa('Cog::App')) {
if ($plugin->webapp_class) {
push @$adds, $plugin->webapp_class;
}
push @$adds, $parent
unless $parent =~ /^(Cog::Base|Cog::Plugin)$/;
}
elsif ($plugin->isa('Cog::WebApp')) {
push @$adds, $parent
unless $parent =~ /^(Cog::Base|Cog::Plugin)$/;
}
push @$adds, @{$plugin->plugins};
for my $add (@$adds) {
$self->expand_list($list, $add, $expanded);
}
}
sub build_list {
my $self = shift;
my $name = shift;
my $list_list = shift || 0;
my $finals = $self->$name;
my $list = [];
my $plugins = $self->_plugins;
my $method = $list_list ? 'add_to_list_list' : 'add_to_list';
for my $plugin (@$plugins) {
my $function = "${plugin}::$name";
next unless defined(&$function);
no strict 'refs';
$self->$method($list, &$function());
}
$self->$method($list, $finals);
$self->{$name} = $list;
}
sub add_to_list {
my ($self, $list, $adds) = @_;
my $point = @$list;
for my $add (@$adds) {
if ($add eq '()') {
$point = @$list = ();
}
elsif ($add eq '^^') {
$point = 0;
}
elsif ($add eq '$$') {
$point = @$list;
}
elsif ($add eq '++') {
$point++ if $point < @$list;
}
elsif ($add eq '--') {
$point-- if $point > 0;
}
elsif ($add =~ s/^(\-\-|\+\+) *//) {
my $indicator = $1;
for ($point = 0; $point < @$list; $point++) {
if ($add eq $list->[$point]) {
splice(@$list, $point, 1)
if $indicator eq '--';
$point++
if $indicator eq '++';
last;
}
}
}
else {
splice(@$list, $point++, 0, $add);
}
}
}
sub add_to_list_list {
my ($self, $list, $adds) = @_;
my $point = @$list;
for my $add (@$adds) {
if (not ref $add and $add eq '()') {
$point = @$list = ();
}
else {
splice(@$list, $point++, 0, $add);
}
}
}
sub build_class_share_map {
my $self = shift;
my $plugins = $self->_plugins;
my $class_share_map = $self->_class_share_map;
for my $plugin (@$plugins) {
my $dir = $self->find_share_dir($plugin)
or die "Can't find share dir for $plugin";
$class_share_map->{$plugin} = $dir
if $dir;
}
}
sub find_share_dir {
my $self = shift;
my $plugin = shift;
my $dist = $plugin->DISTNAME;
my $modpath = "$dist.pm";
$modpath =~ s!-!/!g;
while (1) {
my $dir = $INC{$modpath} or last;
$dir =~ s!(blib/)?lib/\Q$modpath\E$!! or last;
$dir .= "share";
return $dir if -e $dir;
last;
}
my $dir = eval { File::ShareDir::dist_dir($dist) };
return $dir if $dir;
return;
}
sub find_classes {
my $self = shift;
my $classes = {};
for my $plugin (@{$self->_plugins}) {
next unless $plugin->can('cog_classes');
$classes = +{ %$classes, %{$plugin->cog_classes} };
}
$self->classes($classes);
}
sub _build_files_map {
my $self = shift;
my $hash = {};
my $plugins = $self->_plugins;
for my $plugin (@$plugins) {
my $dir = $self->_class_share_map->{$plugin} or next;
for (io->dir($dir)->All_Files) {
next if "$_" =~ /\.sw[p]$/;
my $full = $_->pathname;
my $short = $full;
$short =~ s!^\Q$dir\E/?!! or die;
$hash->{$short} = $full;
}
}
return $hash;
}
1;