Poet-0.13/0000775000076500000240000000000012034257703011566 5ustar swartzstaffPoet-0.13/bin/0000775000076500000240000000000012034257703012336 5ustar swartzstaffPoet-0.13/bin/poet0000755000076500000240000000127112034257703013232 0ustar swartzstaff#!/usr/bin/perl use Poet::App; Poet::App->run; =pod =head1 NAME poet - main Poet command-line interface =head1 SYNOPSIS poet new [-d dir] [-q] poet script =head1 DESCRIPTION For general help, run C. For help with a specific command, run C. =head1 SEE ALSO Poet =head1 AUTHOR Jonathan Swartz =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/Changes0000644000076500000240000000466112034257703013066 0ustar swartzstaffRevision history for Poet ** denotes an incompatible change 0.13 Oct 7, 2012 * Improvements - Add standard error pages and 500 error response for errors in live mode * Fixes - Retain response object ($m->res) when creating subrequests via $m->go and $m->visit 0.12 Aug 15, 2012 * Fixes - Fix overriding of Conf and Import - Require Mason::Plugin::Cache 0.04, for $m->cache - Fix pod typos - RT #77676 (Florian) - Add missing make_immutable calls * Documentation - Add Poet::Plack::Request and Poet::Plack::Response to list of subclasses (Tomohiro Hosaka) 0.11 Jun 5, 2012 * Improvements - Change $env to $poet to avoid confusion for existing PSGI developers. $env still works and will be supported indefinitely for backward compatibility. RT #77592 (Matt Trout) 0.10 Jun 4, 2012 * Improvements - Add $conf->get_secure, for sensitive configuration entries - Add $conf->generate_dynamic_config, for generating configuration files for external software 0.09 May 15, 2012 * Improvements - Referring to an unknown conf key in ${interpolated} syntax throws error instead of inserting blank string * Fixes - Allow for older File::Path versions - RT #77206 (Gavin Carr) - Fix PSGIHandler test from using the same component paths for multiple test cases 0.08 May 9, 2012 * Fixes - Handle Log::Any::Adapter::Log4perl being missing as well - Use #!/usr/bin/perl for poet, MakeMaker will adjust on install - Handle .svn and .git in share/generate.skel * Documentation - Fix blog article date/time issue in tutorial (Pat McNamee) 0.07 May 6, 2012 * Fixes - Fix for Moose 2.06 - RT #76793 (doy) 0.06 May 6, 2012 * Fixes - Require Mason 2.19 * Documentation - Improve clarity of tutorial (Horia Cristescu) - Include tutorial files in distro, in eg/blog (Debi) 0.05 May 1, 2012 * Fixes - Generate the appropriate shebang line in bin/*.pl via Config * Improvements - Various changes to scaffolding produced by 'poet new' 0.04 Apr 26, 2012 * Improvements - DWIM with lowercase and/or underscored argument to "poet new" * Fixes - Fix #! line on get.pl (Pedro Melo) * Documentation - Various documentation and generated environment tweaks * Backend - Remove some files from pause indexing - Switch to Test::Class::Most for all tests 0.02 Apr 23, 2012 - Add JSON::XS dependency, remove use of JSON.pm - Fix $m->cache to use Poet::Cache / MyApp::Cache 0.01 Apr 20, 2012 - Initial version Poet-0.13/eg/0000775000076500000240000000000012034257703012161 5ustar swartzstaffPoet-0.13/eg/blog/0000775000076500000240000000000012034257703013104 5ustar swartzstaffPoet-0.13/eg/blog/bin/0000775000076500000240000000000012034257703013654 5ustar swartzstaffPoet-0.13/eg/blog/bin/app.psgi0000644000076500000240000000140612034257703015317 0ustar swartzstaffuse Poet::Script qw($conf $env); use Plack::Builder; use Plack::Session::Store::Cache; use strict; use warnings; # Load modules configured in server.load_modules # $env->app_class('Server')->load_startup_modules(); builder { # Add Plack middleware here # if ( $conf->is_development ) { enable "Plack::Middleware::StackTrace"; enable "Plack::Middleware::Debug"; } enable "Plack::Middleware::Static", path => qr{^/static/}, root => $env->root_dir; enable "Plack::Middleware::Session", store => Plack::Session::Store::Cache->new( cache => $env->app_class('Cache')->new( namespace => 'session' ) ); sub { my $psgi_env = shift; $env->app_class('Mason')->handle_psgi($psgi_env); }; };Poet-0.13/eg/blog/bin/get.pl0000755000076500000240000000207512034257703014775 0ustar swartzstaff#!/usr/local/bin/perl use Poet::Script qw($env); use Poet::Mechanize; use warnings; use strict; my $url = shift(@ARGV) or die "usage: $0 url"; my $mech = Poet::Mechanize->new(); $mech->get($url); if ( $mech->success ) { print $mech->content; } else { printf( "error getting '%s': %d\n%s", $url, $mech->status, $mech->content ? $mech->content . "\n" : '' ); } __END__ =pod =head1 NAME get.pl - Get a URL via command line without a running server =head1 SYNOPSIS get.pl url =head1 DESCRIPTION Runs a request through your Poet application in a single process without actually requiring a running server. The request will use the same psgi.app and pass through all the same middleware, etc. Uses L. The url scheme and host are optional, so either of these will work: get.pl /action get.pl http://localhost/action Because the request runs in a single process, it's easy to run through a debugger: perl -d get.pl /action or profiler: perl -d:NYTProf get.pl /action nytprofhtml =cutPoet-0.13/eg/blog/bin/install.sh0000755000076500000240000000036312034257703015661 0ustar swartzstaff#!/bin/bash # Requires cpanm: http://search.cpan.org/perldoc?App::cpanminus cpanm -S --notest DateTime DBD::SQLite Poet Rose::DB::Object # Load schema into database cd `dirname $0` mkdir -p ../data sqlite3 ../data/blog.db < ../db/schema.sql Poet-0.13/eg/blog/bin/purge_old_entries.pl0000755000076500000240000000047612034257703017732 0ustar swartzstaff#!/usr/local/bin/perl use Poet::Script qw($conf); use Blog::Article; use strict; use warnings; my $days_to_keep = $conf->get( 'blog.days_to_keep' => 365 ); my $min_date = DateTime->now->subtract( days => $days_to_keep ); Blog::Article::Manager->delete_articles( where => [ create_time => { lt => $min_date } ] ); Poet-0.13/eg/blog/bin/run.pl0000755000076500000240000000071112034257703015015 0ustar swartzstaff#!/usr/local/bin/perl # # Runs plackup with appropriate options # use Poet::Script qw($conf $env); use IPC::System::Simple qw(run); use strict; use warnings; my $app_psgi = $env->bin_path("app.psgi"); my $server = $env->app_class('Server'); # Get plackup options based on config (e.g. server.port) and layer # my @options = $server->get_plackup_options(); my @cmd = ("plackup", @options, $app_psgi); print "Running " . join(", ", @cmd) . "\n"; run(@cmd);Poet-0.13/eg/blog/comps/0000775000076500000240000000000012034257703014225 5ustar swartzstaffPoet-0.13/eg/blog/comps/all_articles.mi0000644000076500000240000000057312034257703017215 0ustar swartzstaff% if (@articles) { Showing <% scalar(@articles) %> article<% @articles > 1 ? "s" : "" %>.
    % foreach my $article (@articles) {
  • <& article/display.mi, article => $article &>
  • % }
% } % else {

No articles yet.

% } <%init> my @articles = @{ Blog::Article::Manager->get_articles (sort_by => 'create_time DESC') }; Poet-0.13/eg/blog/comps/article/0000775000076500000240000000000012034257703015650 5ustar swartzstaffPoet-0.13/eg/blog/comps/article/display.mi0000644000076500000240000000041412034257703017641 0ustar swartzstaff<%class> use Date::Format; my $date_fmt = "%A, %B %d, %Y %I:%M %p"; has 'article' => (required => 1);

<% $.article->title %>

<% $.article->create_time->strftime($date_fmt) %>

<% $.article->content %>
Poet-0.13/eg/blog/comps/article/publish.mp0000644000076500000240000000104512034257703017652 0ustar swartzstaffhas 'content'; has 'title'; method handle () { my $session = $m->session; if ( !$.content || !$.title ) { $session->{message} = "Content and title required."; $session->{form_data} = $.args; $m->redirect('/new_article'); } my $article = Blog::Article->new( title => $.title, content => $.content, create_time => DateTime->now( time_zone => 'local' ) ); $article->save; $session->{message} = sprintf( "Article '%s' saved.", $.title ); $m->redirect('/'); } Poet-0.13/eg/blog/comps/Base.mc0000644000076500000240000000047112034257703015420 0ustar swartzstaff<%augment wrap> My Blog % if (my $message = delete($m->session->{message})) {
<% $message %>
% } <% inner() %> Poet-0.13/eg/blog/comps/index.mc0000644000076500000240000000014012034257703015646 0ustar swartzstaff

Welcome to my blog.

<& all_articles.mi &> Add an article Poet-0.13/eg/blog/comps/new_article.mc0000644000076500000240000000053312034257703017041 0ustar swartzstaff

Add an article

% $.FillInForm($form_data) {{

Title:

Text:

% }} <%init> my $form_data = delete($m->session->{form_data}); Poet-0.13/eg/blog/conf/0000775000076500000240000000000012034257703014031 5ustar swartzstaffPoet-0.13/eg/blog/conf/layer/0000775000076500000240000000000012034257703015145 5ustar swartzstaffPoet-0.13/eg/blog/conf/layer/development.cfg0000644000076500000240000000007412034257703020147 0ustar swartzstaff# Contains configuration specific to the development layer. Poet-0.13/eg/blog/conf/local.cfg0000644000076500000240000000027512034257703015606 0ustar swartzstaff# Contains configuration local to this environment. # This file should not be checked into version control. layer: development server.port: 5000 server.load_modules: - Blog::Article Poet-0.13/eg/blog/db/0000775000076500000240000000000012034257703013471 5ustar swartzstaffPoet-0.13/eg/blog/db/schema.sql0000644000076500000240000000024612034257703015452 0ustar swartzstaffcreate table if not exists articles ( id integer primary key autoincrement, content string not null, create_time timestamp not null, title string not null ); Poet-0.13/eg/blog/lib/0000775000076500000240000000000012034257703013652 5ustar swartzstaffPoet-0.13/eg/blog/lib/Blog/0000775000076500000240000000000012034257703014535 5ustar swartzstaffPoet-0.13/eg/blog/lib/Blog/Article.pm0000644000076500000240000000037212034257703016456 0ustar swartzstaffpackage Blog::Article; use Blog::DB; use strict; use warnings; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup( table => 'articles', auto => 1, ); __PACKAGE__->meta->make_manager_class('articles'); sub init_db { Blog::DB->new } 1; Poet-0.13/eg/blog/lib/Blog/DB.pm0000644000076500000240000000034112034257703015354 0ustar swartzstaffpackage Blog::DB; use Poet qw($env); use strict; use warnings; use base qw(Rose::DB); __PACKAGE__->use_private_registry; __PACKAGE__->register_db( driver => 'sqlite', database => $env->data_path("blog.db"), ); 1; Poet-0.13/eg/blog/logs/0000775000076500000240000000000012034257703014050 5ustar swartzstaffPoet-0.13/eg/blog/logs/poet.log0000644000076500000240000000000012034257703015506 0ustar swartzstaffPoet-0.13/eg/blog/README0000644000076500000240000000023012034257703013755 0ustar swartzstaffThe micro-blog demo built in Poet::Manual::Tutorial. Run bin/install.sh to install CPAN modules and the database. Run bin/run.pl to start the server. Poet-0.13/eg/blog/static/0000775000076500000240000000000012034257703014373 5ustar swartzstaffPoet-0.13/eg/blog/static/css/0000775000076500000240000000000012034257703015163 5ustar swartzstaffPoet-0.13/eg/blog/static/css/style.css0000644000076500000240000000044412034257703017035 0ustar swartzstaffbody { color: #000; font-family: "Lucida Grande","Lucida Sans Unicode",Arial,Verdana,sans-serif; font-size: 14px; background: #F7F7F7; } table td, table th { padding: 5px; height: 100%; font-size: 14px; } h3 { font-size: 18px; } li { padding-top: 5px; }Poet-0.13/INSTALL0000644000076500000240000000157612034257703012626 0ustar swartzstaff This is the Perl distribution Poet. Installing Poet is straightforward. ## Installation with cpanm If you have cpanm, you only need one line: % cpanm Poet If you are installing into a system-wide directory, you may need to pass the "-S" flag to cpanm, which uses sudo to install the module: % cpanm -S Poet ## Installing with the CPAN shell Alternatively, if your CPAN shell is set up, you should just be able to do: % cpan Poet ## Manual installation As a last resort, you can manually install it. Download the tarball, untar it, then build it: % perl Makefile.PL % make && make test Then install it: % make install If you are installing into a system-wide directory, you may need to run: % sudo make install ## Documentation Poet documentation is available as POD. You can run perldoc from a shell to read the documentation: % perldoc Poet Poet-0.13/lib/0000775000076500000240000000000012034257703012334 5ustar swartzstaffPoet-0.13/lib/Poet/0000775000076500000240000000000012034257703013243 5ustar swartzstaffPoet-0.13/lib/Poet/App/0000775000076500000240000000000012034257703013763 5ustar swartzstaffPoet-0.13/lib/Poet/App/Command/0000775000076500000240000000000012034257703015341 5ustar swartzstaffPoet-0.13/lib/Poet/App/Command/new.pm0000644000076500000240000000352512034257703016473 0ustar swartzstaffpackage Poet::App::Command::new; BEGIN { $Poet::App::Command::new::VERSION = '0.13'; } use Poet::Moose; use Poet::Types; extends 'Poet::App::Command'; has 'app_name' => ( isa => 'Poet::Types::AppName', is => 'rw', traits => [ 'NoGetopt' ] ); has 'dir' => ( isa => 'Str', traits => ['Getopt'], cmd_aliases => 'd', lazy_build => 1, documentation => 'Directory to create; will adapt from app-name if ommitted' ); has 'quiet' => ( isa => 'Bool', traits => ['Getopt'], cmd_aliases => 'q', documentation => 'Suppress most messages' ); my $description = 'Generates a new Poet environment for an app with the provided name, which should be suitable for use in Perl classnames (e.g. "MyFirstApp"). If not provided, a directory is chosen by lowercasing and underscoring the app name. % poet new MyApp my_app/.poet_root my_app/bin/app.psgi my_app/bin/get.pl ... Now run \'my_app/bin/run.pl\' to start your server. Options:'; method abstract () { "Create a new Poet installation" } method description () { $description } method usage_desc () { "poet new [-d dir] [-q] " } method _build_dir () { return $self->app_name_to_dir( $self->app_name ); } method app_name_to_dir ($app_name) { my $dir; if ( $app_name =~ /^[A-Z]+$/ ) { $dir = lc($app_name); } else { $dir = lcfirst($app_name); $dir =~ s/([A-Z])/"_" . lc($1)/ge; } return $dir; } method execute ($opt, $args) { $self->usage_error("takes one argument (app name)") unless @$args == 1; my $app_name = ucfirst( $args->[0] ); $app_name =~ s/_([a-z])/uc($1)/ge; $self->app_name($app_name); require Poet::Environment::Generator; Poet::Environment::Generator->generate_environment_directory( root_dir => $self->dir, app_name => $self->app_name, quiet => $self->quiet ); } 1; Poet-0.13/lib/Poet/App/Command/script.pm0000644000076500000240000000160112034257703017177 0ustar swartzstaffpackage Poet::App::Command::script; BEGIN { $Poet::App::Command::script::VERSION = '0.13'; } use File::Spec::Functions qw(rel2abs); use Poet::Tools qw(write_file); use Poet::Moose; use Poet::Types; extends 'Poet::App::Command'; method abstract () { "Create a Poet script"; } method usage_desc () { return "poet script "; } method execute ($opt, $args) { $self->usage_error("takes one argument (script name)") unless @$args == 1; my ($path) = @$args; my $poet = $self->initialize_environment(); $path =~ s|^bin/||; $path = rel2abs( $path, $poet->bin_dir() ); die "'$path' already exists, will not overwrite" if -e $path; write_file( $path, $self->script_template() ); chmod( 0775, $path ); print "$path\n"; } method script_template () { '#!/usr/local/bin/perl use Poet::Script qw($conf $poet); use strict; use warnings; '; } 1; Poet-0.13/lib/Poet/App/Command.pm0000644000076500000240000000046012034257703015675 0ustar swartzstaffpackage Poet::App::Command; BEGIN { $Poet::App::Command::VERSION = '0.13'; } use Poet::Moose; use Cwd qw(getcwd); use strict; use warnings; method initialize_environment () { require Poet::Script; Poet::Script::initialize_with_root_dir( getcwd() ); } extends 'MooseX::App::Cmd::Command'; 1; Poet-0.13/lib/Poet/App.pm0000644000076500000240000000022112034257703014312 0ustar swartzstaffpackage Poet::App; BEGIN { $Poet::App::VERSION = '0.13'; } use Moose; extends qw(MooseX::App::Cmd); __PACKAGE__->meta->make_immutable(); 1; Poet-0.13/lib/Poet/Cache.pm0000644000076500000240000001071712034257703014610 0ustar swartzstaffpackage Poet::Cache; BEGIN { $Poet::Cache::VERSION = '0.13'; } ## no critic (Moose::RequireMakeImmutable) use Poet qw($conf $poet); use Method::Signatures::Simple; use Moose; extends 'CHI'; method initialize_caching () { my $default_config = { defaults => { driver => 'File', root_dir => $poet->data_path("cache") } }; my $config = $conf->get_hash( 'cache' => $default_config ); __PACKAGE__->config($config); } 1; __END__ =pod =head1 NAME Poet::Cache -- Poet caching with CHI =head1 SYNOPSIS # In a conf file... cache: defaults: driver: Memcached servers: ["10.0.0.15:11211", "10.0.0.15:11212"] # In a script... use Poet::Script qw($cache); # In a module... use Poet qw($cache); # In a component... my $cache = $m->cache; # For an arbitrary namespace... my $cache = Poet::Cache->new(namespace => 'Some::Namespace') # then... my $customer = $cache->get($name); if ( !defined $customer ) { $customer = get_customer_from_db($name); $cache->set( $name, $customer, "10 minutes" ); } my $customer2 = $cache->compute($name2, "10 minutes", sub { get_customer_from_db($name2) }); =head1 DESCRIPTION Poet::Cache is a subclass of L. CHI provides a unified caching API over a variety of storage backends, such as memory, plain files, memory mapped files, memcached, and DBI. Each package and Mason component uses its own CHI L so that caches remain separate. =head1 CONFIGURATION The Poet configuration entry 'cache', if any, will be passed to Lconfig()|CHI/SUBCLASSING AND CONFIGURING CHI>. This can go in any L, e.g. C or C. Here's a simple configuration that caches everything to files under C. This is also the default if no configuration is present. cache: defaults: driver: File root_dir: ${root}/data/cache Here's a more involved configuration that defines several "storage types" and assigns each namespace a storage type. cache: defaults: expires_variance: 0.2 storage: file: driver: File root_dir: ${root}/data/cache memcached: driver: Memcached servers: ["10.0.0.15:11211", "10.0.0.15:11212"] compress_threshold: 4096 namespace: /some/component: { storage: file, expires_in: 5min } /some/other/component: { storage: memcached, expires_in: 1h } Some::Library: { storage: memcached, expires_in: 10min } Given the configuration above, and the code package Some::Library; use Poet qw($cache); this C<$cache> will be created with properties driver: Memcached servers: ["10.0.0.15:11211", "10.0.0.15:11212"] compress_threshold: 4096 expires_in: 10min =head1 USAGE =head2 Obtaining cache handle =over =item * In a script (namespace will be 'main'): use Poet::Script qw($cache); =item * In a module C (namespace will be 'MyApp::Foo'): use Poet qw($cache); =item * In a component C (namespace will be '/foo/bar'): my $cache = $m->cache; =item * Manually for an arbitrary namespace: my $cache = Poet::Cache->new(namespace => 'Some::Namespace'); # or my $cache = MyApp::Cache->new(category => 'Some::Namespace'); =back =head2 Using cache handle my $customer = $cache->get($name); if ( !defined $customer ) { $customer = get_customer_from_db($name); $cache->set( $name, $customer, "10 minutes" ); } my $customer2 = $cache->compute($name2, "10 minutes", sub { get_customer_from_db($name2) }); See L and L for more details. =head1 MODIFIABLE METHODS These methods are not intended to be called externally, but may be useful to override or modify with method modifiers in L. =over =item initialize_caching Called once when the Poet environment is initialized. By default, calls C<< __PACKAGE__->config >> with the configuration entry 'cache'. =back =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 Poet-0.13/lib/Poet/Conf.pm0000644000076500000240000004716512034257703014501 0ustar swartzstaffpackage Poet::Conf; BEGIN { $Poet::Conf::VERSION = '0.13'; } use Carp; use Cwd qw(realpath); use Data::Rmap qw(rmap_scalar); use File::Spec::Functions qw(catfile); use Guard; use Poet::Moose; use Poet::Tools qw(read_file); use Storable qw(dclone); use Try::Tiny; use YAML::XS; use strict; use warnings; has 'conf_dir' => ( required => 1 ); has 'data' => ( init_arg => undef ); has 'is_development' => ( init_arg => undef, lazy_build => 1 ); has 'is_live' => ( init_arg => undef, lazy_build => 1 ); has 'layer' => ( init_arg => undef, lazy_build => 1 ); has 'root_dir' => ( required => 1 ); our %get_cache; method BUILD () { $self->{data} = $self->read_conf_data(); } method initial_conf_data () { return ( root_dir => $self->root_dir, root => $self->root_dir ); } method read_conf_data () { my %data = $self->initial_conf_data(); # Collect list of conf files in appropriate order # my @conf_files = $self->ordered_conf_files(); # Stores the file where each global/* key is declared. # my %global_keys; foreach my $file (@conf_files) { if ( defined $file && -f $file ) { # Read conf file into hash # my $new_data = $self->read_conf_file($file); # Make sure no keys are defined in multiple global conf files # if ( $file =~ m{/global/} ) { foreach my $key ( keys(%$new_data) ) { if ( my $previous_file = $global_keys{$key} ) { die sprintf( "top-level key '%s' defined in both '%s' and '%s' - global conf files must be mutually exclusive", $key, $previous_file, $file ); } else { $global_keys{$key} = $file; } } } # Merge new hash into current data # $self->merge_conf_data( \%data, $new_data, $file ); } $self->_flush_get_cache(); } return \%data; } method _build_layer () { my $conf_dir = $self->conf_dir; my $local_cfg_file = catfile( $conf_dir, "local.cfg" ); my $local_cfg = ( -f $local_cfg_file ) ? $self->read_conf_file($local_cfg_file) : {}; my $layer = $local_cfg->{layer} || die "must specify layer in '$local_cfg_file'"; die "invalid layer '$layer' - no such file '$conf_dir/layer/$layer.cfg'" unless -f "$conf_dir/layer/$layer.cfg"; return $layer; } method _build_is_development () { return $self->layer eq 'development'; } method _build_is_live () { return !$self->is_development; } method ordered_conf_files () { my $conf_dir = $self->conf_dir(); my $layer = $self->layer(); return ( "$conf_dir/global.cfg", glob("$conf_dir/global/*.cfg"), ( $self->is_live ? ("$conf_dir/layer/live.cfg") : () ), "$conf_dir/layer/$layer.cfg", "$conf_dir/local.cfg", $ENV{POET_EXTRA_CONF_FILE}, ); } method read_conf_file ($file) { # Read a yaml file into a hash, adding a dummy key pair to handle empty # files or files with nothing but comments, and checking for errors. # Return the hash. # my $dummy = "__yaml_init"; my $yaml = read_file($file) . "\n\n$dummy: 0"; my $hash; try { $hash = YAML::XS::Load($yaml); } catch { die "error parsing conf file '$file': $_"; }; die "'$file' did not parse to a hash" unless ref($hash) eq 'HASH'; delete( $hash->{$dummy} ); return $hash; } method merge_conf_data ($current_data, $new_data, $file) { while ( my ( $key, $value ) = each(%$new_data) ) { my $orig_key = $key; my $assign_to_hash = $current_data; while ( $key =~ /\./ ) { my ( $first, $rest ) = split( /\./, $key, 2 ); if ( !defined( $assign_to_hash->{$first} ) ) { $assign_to_hash->{$first} = {}; } $assign_to_hash = $assign_to_hash->{$first}; if ( ref($assign_to_hash) ne 'HASH' ) { die sprintf( "error assigning to '%s' in '%s'; '%s' already has non-hash value", $orig_key, $file, substr( $orig_key, 0, -1 * length($rest) - 1 ) ); } $key = $rest; } $assign_to_hash->{$key} = $value; } } # Memoize get, since conf can normally not change at runtime. This will # benefit all get() and get_*() calls. Clear cache on set_local. # method _flush_get_cache () { %get_cache = (); } method get ($key, $default) { croak "key required" if !defined($key); return $get_cache{$key} if exists( $get_cache{$key} ); my $orig_key = $key; my @firsts; if ( $key =~ /\./ ) { return $self->_get_dotted_key( $key, $default ); } my $value = $self->data->{$key}; rmap_scalar { $_ = $self->interpolate_value($_) if defined && !ref } $value; $get_cache{$key} = $value; return defined($value) ? $value : $default; } method _get_dotted_key ($key, $default) { my ( $rest, $last ) = ( $key =~ /^(.*)\.([^\.]+)$/ ); my $value = $self->get_hash($rest)->{$last}; return defined($value) ? $value : $default; } method interpolate_value ($value) { while ( $value =~ /(\$ \{ ([\w\.\-]+) \} )/x ) { my $var_decl = $1; my $var_key = $2; my $var_value = $self->get_or_die($var_key); $var_value = '' if !defined($var_value); $value =~ s/\Q$var_decl\E/$var_value/; } return $value; } method get_or_die ($key) { if ( defined( my $value = $self->get($key) ) ) { return $value; } else { croak "could not get conf for '$key'"; } } method get_list ($key, $default) { if ( defined( my $value = $self->get($key) ) ) { if ( ref($value) eq 'ARRAY' ) { return $value; } else { my $error = sprintf( "list value expected for conf key '%s', got non-list '%s'", $key, $value ); croak($error); } } elsif ( defined $default ) { return $default; } else { return []; } } method get_hash ($key, $default) { if ( defined( my $value = $self->get($key) ) ) { if ( ref($value) eq 'HASH' ) { return $value; } else { my $error = sprintf( "hash value expected for conf key '%s', got non-hash '%s'", $key, $value ); croak($error); } } elsif ( defined $default ) { return $default; } else { return {}; } } method get_boolean ($key) { my $value = $self->get($key) || 0; return ( !ref($value) && $value =~ /^(1|t|true|y|yes)$/i ) ? 1 : ( !ref($value) && $value =~ /^(0|f|false|n|no)$/i ) ? 0 : croak( sprintf( "boolean value expected for conf key '%s', got non-boolean '%s'", $key, $value ) ); } method set_local ($pairs) { if ( !defined(wantarray) ) { warn "result of set_local must be assigned!"; } croak "set_local expects hashref" unless ref($pairs) eq 'HASH'; # Make a deep copy of current data, then merge in the new pairs # my $orig_data = dclone( $self->{data} ); $self->merge_conf_data( $self->{data}, $pairs, "set_local" ); $self->conf_has_changed(); # Restore original data when $guard goes out of scope # my $guard = guard { $self->{data} = $orig_data; $self->conf_has_changed() }; return $guard; } # Get key from secure conf, fallback to normal conf. Maintain separate # secure_conf hash for each Conf object (e.g. for testing). # my %secure_confs; method get_secure ($key) { die "key required" unless defined($key); return $self->_get_secure_conf->{$key} || $self->get($key); } method _get_secure_conf () { if ( !$secure_confs{"$self"} ) { my $secure_conf_file = $self->get( 'conf.secure_conf_file' => $self->conf_dir . "/secure.cfg" ); $secure_confs{"$self"} = ( -f $secure_conf_file ) ? $self->read_conf_file($secure_conf_file) : {}; } return $secure_confs{"$self"}; } method generate_dynamic_conf () { require MasonX::ProcessDir; my $poet = Poet::Environment->current_env; my $source_dir = $self->conf_dir . "/dynamic"; my $dest_dir = $poet->data_path("conf/dynamic"); my $pd = MasonX::ProcessDir->new( source_dir => $source_dir, dest_dir => $dest_dir, ignore_files => sub { $_[0] =~ /Base\.|\.mi$|gen\.pl|README/ }, mason_options => {}, @_ ); $pd->process_dir(); } method get_keys () { return keys( %{ $self->{data} } ); } method as_hash () { return { map { ( $_, $self->get($_) ) } $self->get_keys() }; } method as_string () { return YAML::XS::Dump( $self->as_hash ); } # Things we need to do whenever the conf changes. # method conf_has_changed () { $self->_flush_get_cache(); } __PACKAGE__->meta->make_immutable(); 1; =pod =head1 NAME Poet::Conf -- Poet configuration =head1 SYNOPSIS # In a script... use Poet::Script qw($conf); # In a module... use Poet qw($conf); # $conf is automatically available in Mason components # then... my $value = $conf->get('key', 'default'); my $value = $conf->get_or_die('key'); my $listref = $conf->get_list('key', ['default']); my $hashref = $conf->get_hash('key', {'default' => 5}); my $bool = $conf->get_boolean('key'); my @keys = grep { /^foo\./ } $conf->get_keys; my $hash = $conf->as_hash; print $conf->as_string; { my $lex = $conf->set_local({'key' => 'new_value'}); # key has new_value inside this scope only } =head1 DESCRIPTION The Poet::Conf object gives access to the current environment's configuration, read from configuration files in the conf/ subdirectory. =head1 CONFIGURATION FILES Poet configuration files are found in the conf/ subdirectory of the environment root: conf/ global.cfg global/ something.cfg something_else.cfg ... layer/ development.cfg production.cfg ... local.cfg $ENV{POET_EXTRA_CONF_FILE} The files are read and merged in the following order, with later files taking precedence over earlier files. None of the files have to exist except C. =over =item * C contains various settings for the environment, typically checked into version control. Having a single file is fine for a simple site and a single developer, but if this gets too unwieldy, see global/ below. =item * The C directory contains multiple .cfg files, all of which are read in alphabetical order. This is an alternative to C when the latter gets too crowded and you have multiple developers making simultaneous changes. It is an error for two global files to set the same key. =item * The C directory contains version-controlled files specific to layers, e.g. C and C. Only one of these files will be active at a time, depending on the current layer (as set in C). =item * C contains settings for this particular instance of the environment. It is not checked into version control. local.cfg must exist and must contain at least the layer, e.g. layer: development =item * If C<$ENV{POET_EXTRA_CONF_FILE}> is defined when configuration initializes, it is read as an extra conf file whose values override all others. =back =head1 CONFIGURATION FORMAT Basic conf file format is L, e.g. cache: defaults: driver: Memcached servers: ["10.0.0.15:11211", "10.0.0.15:11212"] log: defaults: level: info output: poet.log layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n" =head2 Interpolation - referring to other entries Conf entries can refer to other entries via the syntax C<${key}>. For example: # conf file foo: 5 bar: "The number ${foo}" baz: ${bar}00 # then $conf->get('foo') => 5 $conf->get('bar') => "The number 5" $conf->get('baz') => "The number 500" The key must exist or a fatal error will occur. There is a single built-in entry, C, containing the root directory of the environment that you can use in other entries, e.g. cache: defaults: driver: File root_dir: ${root_dir}/data/cache =head2 Dot notation for hash access Conf entries can use dot (".") notation to refer to hash entries. e.g. this foo.bar.baz: 5 is the same as foo: bar: baz: 5 The dot notation is especially useful for I individual hash elements from higher precedence config files. For example, if in C you have cache: defaults: driver: File root_dir: $root/data/cache depth: 3 and in local.cfg you have cache.defaults.depth: 2 then only C will be overridden; the C and C will remain as they were set in C. If instead local.cfg had cache: defaults: depth: 3 then this would completely replace the entire hash under C. =head1 OBTAINING $conf SINGLETON In a script: use Poet::Script qw($conf); In a module: use Poet qw($conf); C<$conf> is automatically available in components. You can also get it via my $conf = Poet::Environment->current_env->conf; =head1 METHODS =head2 Methods for getting conf values =over =item get (key[, default]) my $value = $conf->get('key' => 'default'); Get I from configuration. If I is unavailable, return the I, or undef if no default is given. The return value may be a scalar, list reference, or hash reference, though we recommend using L and L if you expect a list or hash. I can contain dot notation to refer to hash entries. e.g. these are equivalent: $conf->get('foo.bar.baz'); $conf->get_hash('foo')->{bar}->{baz}; =item get_or_die (key) my $value = $conf->get_or_die('key'); Get I from configuration. If I is unavailable, throw a fatal error. =item get_list (key[, default]) my $listref = $conf->get_list('key', ['default']); Get I from configuration. If the value is not a list reference, throw an error. If I is unavailable, return the I, or an empty list reference if no default is given. =item get_hash (key[, default]) my $hashref = $conf->get_hash('key', {'default' => 5}); Get I from configuration. If the value is not a hash reference, throw an error. If I is unavailable, return the I, or an empty hash reference if no default is given. =item get_boolean (key) my $bool = $conf->get_boolean('key'); Get I from configuration. Return 1 if the value represents true ("1", "t", "true", "y", "yes") and 0 if the value represents false ("0", "f", "false", "n", "no") or is not present in configuration. These are case insensitive matches. Throws an error if there is a value that is a reference or does not match one of the valid options. =item get_secure (key) my $password = $conf->get_secure('secret_password'); Get I from a separate, non-version-controlled, secure config file; if it cannot be found, then fallback to normal config. Useful for passwords, encryption keys, etc. that might be ok in normal config on development, but ought to be secure on production. The location of the secure config file is determined by config entry conf.secure_conf_file; it defaults to C. The file is in plain YAML format, with no interpolation or dot notation. =back =head2 Other methods =over =item layer Returns the current layer, as determined from C. =item is_development Boolean; returns true iff the current layer is 'development'. =item is_live Boolean; the opposte of L. =item get_keys my @keys = sort $conf->get_keys; Return a list of all keys in configuration. =item as_hash my $hash = $conf->as_hash; Return a hash reference mapping keys to their value as returned by C<< $conf->get >>. =item as_string print $conf->as_string; Return a printable representation of the keys and values. =item set_local my $lex = $conf->set_local({key => 'value', ...}); Temporarily set each I to I. The original value will be restored when $lex goes out of scope. This is intended for specialized use in unit tests and development tools, NOT for production code. Setting and resetting of configuration values will make it much more difficult to read and debug code! =item generate_dynamic_config $conf->generate_dynamic_config(); This method can be used to dynamically generate configuration files for external software (e.g. Apache, nginx, logrotate). It uses L to process Mason templates in C and generate destination files in C. For example, if C contains an Apache configuration file with Mason dynamic elements, this method will generate a static configuration file in C, which you can then feed directly into Apache. =back =head1 MODIFIABLE METHODS These methods are not intended to be called externally, but may be useful to override or modify with method modifiers in L. Their APIs will be kept as stable as possible. =over =item read_conf_data This is the main method that finds and parses conf files and returns a hash of conf keys to values. You can modify this to dynamically compute certain conf keys: override 'read_conf_data' => sub { my $hash = super(); $hash->{complex_key} = ...; return $hash; }; or to completely override how Poet gets its configuration: override 'read_conf_data' => sub { return { some_conf_key => 'some conf value', ... }; }; =item initial_conf_data Returns a hash with initial configuration data before any conf files have been merged in. By default, just contains ( root => '/path/to/root' ) =item _build_layer Determines the current layer before L is called. By default, looks for a C key in C. =item _build_is_development Determines the value of L, and subsequently its opposite L. By default, true iff layer == 'development'. =item ordered_conf_files Returns a list of conf files to read in order from lowest to highest precedence. You can modify this to insert an additional file, e.g. override 'ordered_conf_files' => sub { my @list = super(); return (@list, '/path/to/important.cfg'); }; =item read_conf_file ($file) Read a single conf I<$file> and return its hash representation. You can modify this to use a conf format other than YAML, e.g. use Config::INI; override 'read_conf_file' => sub { my ($self, $file) = @_; return Config::INI::Reader->read_file($file); }; =item merge_conf_data ($current_data, $new_data, $file) Merge I<$new_data> from I<$file> into I<$current_data>. I<$new_data> and I<$current_data> are both hashrefs, and I<$current_data> will be the empty hash for the first file. By default, this just uses Perl's built-in hash merging with values from I<$new_data> taking precedence. =back =head1 CREDITS The ideas of merging multiple conf files and variable interpolation came from L. =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Environment/0000775000076500000240000000000012034257703015547 5ustar swartzstaffPoet-0.13/lib/Poet/Environment/Generator.pm0000644000076500000240000000425112034257703020033 0ustar swartzstaffpackage Poet::Environment::Generator; BEGIN { $Poet::Environment::Generator::VERSION = '0.13'; } use Cwd qw(realpath); use File::Find; use File::ShareDir; use Mason; use Method::Signatures::Simple; use Poet::Tools qw(basename dirname mkpath read_dir trim write_file); use strict; use warnings; method generate_environment_directory ($class: %params) { my $root_dir = $params{root_dir} or die "must specify root_dir"; my $app_name = $params{app_name} || basename($root_dir); my $quiet = $params{quiet}; my $style = $params{style} || 'standard'; my $msg = sub { print "$_[0]\n" unless $quiet; }; die "invalid app_name '$app_name' - must be a valid Perl identifier" unless $app_name =~ qr/[[:alpha:]_]\w*/; die "cannot generate environment in $root_dir - directory exists and is non-empty" if ( -d $root_dir && @{ read_dir($root_dir) } ); my $share_dir = realpath( $ENV{POET_SHARE_DIR} || File::ShareDir::dist_dir('Poet') ); die "cannot find Poet share dir '$share_dir'" unless -d $share_dir; my $comp_root = "$share_dir/generate.skel"; my $interp = Mason->new( comp_root => $comp_root, autoextend_request_path => 0, top_level_regex => qr/./, allow_globals => [qw($app_name $root_dir)], ); $interp->set_global( '$app_name' => $app_name ); $interp->set_global( '$root_dir' => $root_dir ); my @paths = $interp->all_paths() or die "could not find template components"; foreach my $path (@paths) { next if $path =~ m{/\.}; # .svn, .git, etc. my $output = trim( $interp->run($path)->output ); ( my $dest = $path ) =~ s{/DOT_}{/.}g; $dest = $root_dir . $dest; $dest =~ s|$root_dir/lib/MyApp|$root_dir/lib/$app_name|; mkpath( dirname($dest), 0, 0775 ); if ( $path =~ /EMPTY$/ ) { $msg->( dirname($dest) ); } else { $msg->($dest); write_file( $dest, $output ); } } find( sub { chmod( 0775, $_ ) if /\.pl$/ }, $root_dir ); $msg->("\nNow run '$root_dir/bin/run.pl' to start your server."); return $root_dir; } 1; Poet-0.13/lib/Poet/Environment.pm0000644000076500000240000001444712034257703016115 0ustar swartzstaffpackage Poet::Environment; BEGIN { $Poet::Environment::VERSION = '0.13'; } use Carp; use File::Slurp; use Poet::Moose; use Poet::Tools qw(can_load catdir); has 'app_name' => ( required => 1 ); has 'conf' => (); has 'importer' => (); has 'log_manager' => (); has 'root_dir' => ( required => 1 ); my ($current_env); method subdirs () { [qw(bin comps conf data db lib logs static t)] } method app_class ($class_name) { my $app_class_name = join( "::", $self->app_name, $class_name ); my $env_class_name = join( "::", "Poet", $class_name ); return can_load($app_class_name) ? $app_class_name : can_load($env_class_name) ? $env_class_name : die "cannot load $app_class_name or $class_name"; } method generate_subdir_methods ($class:) { foreach my $subdir ( 'root', @{ $class->subdirs() } ) { my $dir_method = $subdir . "_dir"; has $dir_method => () if $subdir ne 'root'; my $path_method = $subdir eq 'root' ? "path" : $subdir . "_path"; __PACKAGE__->meta->add_method( $path_method, sub { my ( $self, $relpath ) = @_; croak "$path_method expects relative path as argument" unless defined($relpath); return $self->$dir_method . "/" . $relpath; } ); } } method initialize_current_environment ($class: %params) { if ( defined($current_env) ) { die sprintf( "initialize_current_environment called when current_env already set (%s)", $current_env->root_dir() ); } $current_env = $params{env} || $class->new(%params); # Initialize logging and caching # $current_env->app_class('Log')->initialize_logging(); $current_env->app_class('Cache')->initialize_caching(); return $current_env; } method current_env ($class:) { return $current_env; } method BUILD () { my $root_dir = $self->root_dir(); # Unshift lib dir onto @INC # my $lib_dir = "$root_dir/lib"; unless ( $INC[0] eq $lib_dir ) { unshift( @INC, $lib_dir ); } # Initialize configuration # $self->{conf} = $self->app_class('Conf') ->new( root_dir => $root_dir, conf_dir => catdir( $root_dir, "conf" ) ); my $conf = $self->{conf}; # Initialize importer # $self->{importer} = $self->app_class('Import')->new( env => $self ); # Determine where our standard subdirectories (bin, comps, etc.) # are. Can override in configuration with env.bin_dir, env.comps_dir, # etc. Otherwise use obvious defaults. # foreach my $subdir ( @{ $self->subdirs() } ) { my $method = $subdir . "_dir"; $self->{$method} = $conf->get( "env.$method" => "$root_dir/$subdir" ); } } __PACKAGE__->generate_subdir_methods(); __PACKAGE__->meta->make_immutable(); 1; =pod =head1 NAME Poet::Environment -- Poet environment =head1 SYNOPSIS # In a script... use Poet::Script qw($poet); # In a module... use Poet qw($poet); # $poet is automatically available in Mason components # then... my $root_dir = $poet->root_dir; my $file = $poet->path("some/file.txt"); my $path_to_script = $poet->bin_path("foo/bar.pl"); my $path_to_lib = $poet->lib_path("Foo/Bar.pm"); =head1 DESCRIPTION The Poet::Environment object contains information about the current environment and its directory paths. =head1 PATH METHODS =over =item root_dir Returns the root directory of the environment, i.e. where I<.poet_root> is located. =item path (subpath) Returns the root directory with a relative I added. e.g. if the Poet environment root is C, then $poet->path("somefile.txt"); ==> returns /my/env/root/somefile.txt =item bin_dir =item comps_dir =item conf_dir =item data_dir =item db_dir =item lib_dir =item logs_dir =item static_dir Returns the specified subdirectory, which by default will be just below the root dirctory. e.g. if the Poet environment root is C, then $poet->conf_dir ==> returns /my/env/root/conf $poet->lib_dir ==> returns /my/env/root/lib =item bin_path (subpath) =item comps_path (subpath) =item conf_path (subpath) =item data_path (subpath) =item db_path (subpath) =item lib_path (subpath) =item logs_path (subpath) =item static_path (subpath) Returns the specified subdirectory with a relative I added. e.g. if the Poet environment root is C, then $poet->conf_path("log4perl.conf"); ==> returns /my/env/root/conf/log4perl.conf $poet->lib_path("Data/Type.pm"); ==> returns /my/env/root/lib/Data/Type.pm =back =head1 OTHER METHODS =over =item app_class Returns the full class name to use for the specified class, depending on whether there is a subclass in the environment. e.g. $poet->app_class('Cache') will return C if that module exists, and otherwise C. This is used internally by Poet to implement L. =item app_name Returns the app name, e.g. 'MyApp', found in .poet_root. =item conf Returns the L object associated with the environment. Usually you'd access this by importing C<$conf>. =item current_env A class method that returns the current (singleton) environment for the process. Usually you'd access this by importing C<$poet>. =back =head1 OBTAINING $poet SINGLETON In a script: use Poet::Script qw($poet); In a module: use Poet qw($poet); C<$poet> is automatically available in components. You can also get it via my $poet = Poet::Environment->current_env; =head1 CONFIGURING ENVIRONMENT SUBDIRECTORIES Any subdirectories other than conf_dir can be overridden in configuration. e.g. # Override bin_dir env.bin_dir: /some/other/bin/dir With this configuration in place, $poet->bin_dir ==> returns /some/other/bin/dir $poet->bin_path("foo/bar.pl") ==> returns /some/other/bin/dir/foo/bar.pl =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Import.pm0000644000076500000240000001424412034257703015056 0ustar swartzstaffpackage Poet::Import; BEGIN { $Poet::Import::VERSION = '0.13'; } use Carp; use Poet::Moose; use Try::Tiny; use strict; use warnings; our @CARP_NOT = qw(Poet Poet::Script); has 'default_tags' => ( init_arg => undef, lazy_build => 1 ); has 'env' => ( required => 1, weak_ref => 1 ); has 'valid_vars' => ( init_arg => undef, lazy_build => 1 ); method _build_valid_vars () { my @provide_methods = grep { /^provide_var_/ } $self->meta->get_method_list; return [ sort( map { substr( $_, 12 ) } @provide_methods ) ]; } method _build_default_tags () { return ['debug']; } method export_to_level ($level, @params) { my ($caller) = caller($level); $self->export_to_class($caller); foreach my $param (@params) { if ( substr( $param, 0, 1 ) eq '$' ) { $self->export_var_to_level( substr( $param, 1 ), $level + 1 ); } elsif ( substr( $param, 0, 1 ) eq ':' ) { $self->export_tag_to_level( substr( $param, 1 ), $level + 1 ); } } foreach my $tag ( @{ $self->default_tags } ) { $self->export_tag_to_level( $tag, $level + 1 ); } } method export_to_class ($class) { } method export_var_to_level ($var, $level) { my $provide_method = "provide_var_" . $var; if ( $self->can($provide_method) ) { my ($caller) = caller($level); my $value = $self->$provide_method($caller); no strict 'refs'; *{ $caller . "\::$var" } = \$value; } else { croak sprintf( "unknown import var '\$$var': valid import vars are %s", join( ", ", map { "'\$$_'" } grep { $_ ne 'env' } @{ $self->valid_vars } ) ); } } method export_tag_to_level ($tag, $level) { my $util_class; try { $util_class = $self->env->app_class( "Util::" . ucfirst($tag) ); } catch { croak "problem with import tag ':$tag' ($_)"; }; $util_class->export_to_level( $level + 1, $util_class, ':all' ); } method provide_var_cache ($caller) { $self->env->app_class('Cache')->new( namespace => $caller ); } method provide_var_conf ($caller) { $self->env->conf(); } method provide_var_env ($caller) { $self->env; } method provide_var_log ($caller) { $self->env->app_class('Log')->get_logger( category => $caller ); } method provide_var_poet ($caller) { $self->env; } 1; =pod =head1 NAME Poet::Import -- Import Poet quick vars and utilities =head1 SYNOPSIS # In a script... use Poet::Script qw($conf $poet $log :file); # In a module... use Poet qw($conf $poet $log :file); =head1 DESCRIPTION Poet makes it easy to import certain variables (known as "quick vars") and utility sets into any script or module in your environment. In a script: use Poet::Script qw(...); and in a module: use Poet qw(...); where C<...> contains one or more quick var names (e.g. C<$conf>, C<$poet>) and/or utility tags (e.g. C<:file>, C<:web>). (Note that C is also necessary for initializing the environment, even if you don't care to import anything, whereas C has no effect other than importing.) =head1 QUICK VARS Here is the built-in list of quick vars you can import. Some of the variables are singletons, and some of them are specific to each package they are imported into. =over =item $poet The global environment object, provided by L. This provides information such as the root directory and paths to subdirectories. For backward compatibility this is also available as C<$env>. =item $conf The global configuration object, provided by L. =item $cache The cache for the current package, provided by L. =item $log The logger for the current package, provided by L. =back =head1 UTILITIES =head2 Default utilities The utilities in L are always imported, with no tag necessary. =head2 :file This tag imports all the utilities in L. =head2 :web This tag imports all the utilities in L. It is automatically included in all Mason components. =head1 MASON COMPONENTS Every Mason component automatically gets this on top: use Poet qw($conf $poet :web); C<$m-Ecache> and C<$m-Elog> will get you the cache and log objects for a particular Mason component. =head1 CUSTOMIZING =head2 Adding variables To add your own variable, define a method called provide_var_I in C. For example to add a variable C<$dbh>: package MyApp::Import; use Poet::Moose; extends 'Poet::Import'; method provide_var_dbh ($caller) { # Generate and return a dbh. # $caller is the package importing the variable. # $poet is the current Poet environment. } C can return a single global value, or a dynamic value depending on C<$caller>. Now your scripts and libraries can do use Poet::Script qw($dbh); use Poet qw($dbh); =head2 Adding utility tags To add your own utility tag, define a class C that exports a set of functions via the ':all' tag. For example: package MyApp::Util::Hash; use Hash::Util qw(hash_seed all_keys); use Hash::MoreUtils qw(slice slice_def slice_exists); our @EXPORT_OK = qw(hash_seed all_keys slice slice_def slice_exists); our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK ); 1; Now your scripts and libraries can do use Poet::Script qw(:hash); use Poet qw(:hash); =head2 Other exports To export other general things to the calling class, you can override C, which takes the calling class as its argument. e.g. package MyApp::Import; use Poet::Moose; extends 'Poet::Import'; before 'export_to_class' => sub { my ($self, $class) = @_; no strict 'refs'; %{$class . "::some_name"} = ...; } =over =back =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Log.pm0000644000076500000240000002123512034257703014323 0ustar swartzstaffpackage Poet::Log; BEGIN { $Poet::Log::VERSION = '0.13'; } use Poet qw($conf $poet); use File::Spec::Functions qw(rel2abs); use Log::Any::Adapter; use Method::Signatures::Simple; use Poet::Tools qw(can_load dirname mkpath read_file write_file); use strict; use warnings; method get_logger ($class: %params) { my $category = $params{category} || caller(); return Log::Any->get_logger( category => $category ); } method initialize_logging ($class:) { if ( can_load('Log::Log4perl') && can_load('Log::Any::Adapter') && can_load('Log::Any::Adapter::Log4perl') ) { unless ( Log::Log4perl->initialized() ) { my $config_string = $class->generate_log4perl_config(); Log::Log4perl->init( \$config_string ); } Log::Any::Adapter->set('Log4perl'); } else { write_file( $poet->logs_path("poet.log.ERROR"), sprintf( "[%s] Could not load Log::Log4perl or Log::Any::Adapter::Log4perl. Install them to enable logging, or modify logging for your application (see Poet::Manual::Subclassing).\n", scalar(localtime) ) ); } } method generate_log4perl_config ($class:) { my %log_config = %{ $conf->get_hash('log') }; if ( my $log4perl_conf = $log_config{log4perl_conf} ) { $log4perl_conf = rel2abs( $log4perl_conf, $poet->conf_dir ); return read_file($log4perl_conf); } my %defaults = ( level => 'info', output => 'poet.log', layout => '%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n', %{ $log_config{'defaults'} || {} } ); my %classes = %{ $log_config{'class'} || {} }; foreach my $set ( values(%classes) ) { foreach my $key (qw(level output layout)) { $set->{$key} = $defaults{$key} if !exists( $set->{$key} ); } } foreach my $set ( \%defaults, values(%classes) ) { if ( $set->{output} =~ /^(?:stderr|stdout)$/ ) { $set->{appender_class} = "Log::Log4perl::Appender::Screen"; $set->{stderr} = 0 if $set->{output} eq 'stdout'; } else { $set->{appender_class} = "Log::Log4perl::Appender::File"; $set->{filename} = rel2abs( $set->{output}, $poet->logs_dir ); mkpath( dirname( $set->{filename} ), 0, 0775 ); } } return join( "\n", $class->_generate_lines( 'log4perl.logger', 'default', \%defaults ), map { $class->_generate_lines( "log4perl.logger.$_", $class->_flatten_class_name($_), $classes{$_} ) } sort( keys(%classes) ), ); } method _generate_lines ($class: $logger, $appender, $set) { my $full_appender = "log4perl.appender.$appender"; my @pairs = ( [ $logger => join( ", ", uc( $set->{level} ), $appender ) ], [ $full_appender => $set->{appender_class} ], [ "$full_appender.layout" => 'Log::Log4perl::Layout::PatternLayout' ], [ "$full_appender.layout.ConversionPattern" => $set->{layout} ] ); foreach my $key (qw(filename stderr)) { if ( exists( $set->{$key} ) ) { push( @pairs, [ "$full_appender.$key" => $set->{$key} ] ); } } my $lines = join( "\n", map { join( " = ", @$_ ) } @pairs ) . "\n"; return $lines; } method _flatten_class_name ($class: $class_name) { $class_name =~ s/(::|\.)/_/g; return $class_name; } 1; =pod =head1 NAME Poet::Log -- Poet logging =head1 SYNOPSIS # In a conf file... log: defaults: level: info output: poet.log layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n" category: CHI: level: debug output: chi.log layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} %m - %P%n" MyApp::Foo: output: stdout # In a script... use Poet::Script qw($log); # In a module... use Poet qw($log); # In a component... my $log = $m->log; # For an arbitrary category... my $log = Poet::Log->get_logger(category => 'MyApp::Bar'); # then... $log->error("an error occurred"); $log->debugf("arguments are: %s", \@_) if $log->is_debug(); =head1 DESCRIPTION Poet uses L and L for logging, with simplified configuration for the common case. Log::Any is a logging abstraction that allows CPAN modules to log without knowing about which logging framework is in use. It supports standard logging methods (C<$log-Edebug>, C<$log-Eis_debug>) along with sprintf variants (C<$log-Edebugf>). Log4perl is a powerful logging package that provides just about any logging-related feature you'd want. One of its only drawbacks is its somewhat cumbersome configuration. So, we provide a way to configure Log4perl simply through L if you just want common features. Note: Log4perl is not a strict dependency for Poet. Log messages will simply not get logged until you install it or until you L for your app. =head1 CONFIGURATION The configurations below can go in any L, e.g. C or C. Here's a simple configuration that caches everything to C at C level. This is also the default if no configuration is present. log: defaults: level: info output: poet.log layout: %d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n Here's a more involved configuration that maintains the same default, but adds several I that are logged differently: log: defaults: level: info output: poet.log layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n" category: CHI: level: debug output: chi.log layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} %m - %P%n" MyApp::Foo: output: stdout For the default and for each category, you can specify three different settings: =over =item * level - one of the valid log4perl levels (fatal, error, warn, info, debug, trace) =item * output - can be a relative filename (which will be placed in the Poet log directory), an absolute filename, or the special names "stdout" or "stderr" =item * layout - a valid log4perl L string. =back If a setting isn't defined for a specific category then it falls back to the default. In this example, C will inherit the default level and layout. Notice that we use '::' instead of '.' to specify hierarchical category names, because '.' would interfere with L. Finally, if you must use a full L, you can specify it this way: log: log4perl_conf: /path/to/log4perl.conf =head1 USAGE =head2 Obtaining log handle =over =item * In a script (log category will be 'main'): use Poet::Script qw($log); =item * In a module C (log category will be 'MyApp::Foo'): use Poet qw($log); =item * In a component C (log category will be 'Mason::Component::foo::bar'): my $log = $m->log; =item * Manually for an arbitrary log category: my $log = Poet::Log->get_logger(category => 'Some::Category'); # or my $log = MyApp::Log->get_logger(category => 'Some::Category'); =back =head2 Using log handle $log->error("an error occurred"); $log->debugf("arguments are: %s", \@_) if $log->is_debug(); See C for more details. =head1 MODIFIABLE METHODS These methods are not intended to be called externally, but may be useful to override or modify with method modifiers in L. Their APIs will be kept as stable as possible. =over =item initialize_logging Called once when the Poet environment is initialized. By default, initializes log4perl with the results of L and then calls C<< Log::Any::Adapter->set('Log4perl') >>. You can modify this to initialize log4perl in your own way, or use a different Log::Any adapter, or use a completely different logging system. =item generate_log4perl_config Returns a log4perl config string based on Poet configuration. You can modify this to construct and return your own config. =back =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Manual/0000775000076500000240000000000012034257703014460 5ustar swartzstaffPoet-0.13/lib/Poet/Manual/Configuring.pod0000644000076500000240000000655612034257703017450 0ustar swartzstaff __END__ =pod =head1 NAME Poet::Manual::Configuring - Built-in Poet configuration options =head1 DESCRIPTION This is a list of configuration keys used by Poet itself. These may be placed in any L, e.g. C or C. Entries like C can be listed either in dot notation foo.bar: 5 or as part of a hash: foo: bar: 5 See L for details. =over =item cache The entire hash under this entry will be passed to Lconfig()|CHI/SUBCLASSING AND CONFIGURING CHI>. See L for examples. e.g. cache: defaults: expires_variance: 0.2 storage: file: driver: File root_dir: ${root}/data/cache memcached: driver: Memcached servers: ["10.0.0.15:11211", "10.0.0.15:11212"] compress_threshold: 4096 namespace: /some/component: { storage: file, expires_in: 5min } /some/other/component: { storage: memcached, expires_in: 1h } Some::Library: { storage: memcached, expires_in: 10min } =item env.bin_dir, env.comps_dir, etc. These entries affect what is returned from C<$poet-Ebin_dir>, C<$poet-Ebin_path>, C<$poet-Ecomps_dir>, etc., and thus where various Poet resources are kept. See L. For example, to move data and logs into external directories outside the environment: env: data_dir: /some/external/data/dir logs_dir: /some/external/logs/dir =item log.defaults, log.category Specify the log level, output location, and layout string for logging, in the default case and for particular categories respectively. See L for examples. e.g. log: defaults: level: info output: poet.log layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n" category: CHI: level: debug output: chi.log layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} %m - %P%n" MyApp::Foo: output: stdout =item log.log4perl_conf Bypass Poet's simplified logging configuration and specify a log4perl conf file directly. e.g. log: log4perl_conf: /path/to/log4perl.conf =item mason The hash under this entry will be treated as options that are passed to Cnew> for the main Mason instance, overriding any default options. See L. e.g. mason: static_source: 1 static_source_touch_file: ${root}/data/purge.dat =item server.default_content_type Content type for requests that don't explicitly set one. Defaults to C. =item server.host The IP address to listen on. =item server.load_modules A list of modules to load on server startup, e.g. server.load_modules: - DBI - List::Util - MyApp::Foo - MyApp::Bar =item server.port The port to listen on. =back =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 Poet-0.13/lib/Poet/Manual/Intro.pod0000644000076500000240000003370512034257703016265 0ustar swartzstaff __END__ =pod =head1 NAME Poet::Manual::Intro - A gentle introduction to Poet =head1 WHAT IS POET? Poet is a modern Perl web framework for Mason developers. It uses L/L for server integration, L for request routing and templating, and a selection of best-of-breed CPAN modules for caching, logging and configuration. =head1 INSTALLATION If you don't yet have cpanminus (C), get it L. Then run cpanm -S --notest Poet Omit the "-S" if you don't have root, in which case cpanminus will install Poet and prereqs into C<~/perl5>. Omit the "--notest" if you want to run all the installation tests. Note that this will take about four times as long. =head1 SETUP You should now have a C app installed: % which poet /usr/local/bin/poet Run this to create your initial environment: % poet new MyApp my_app/.poet_root my_app/bin/app.psgi my_app/bin/get.pl ... Now run 'my_app/bin/run.pl' to start your server. The app name must be a valid Perl class name, generally either C (C) or an all-uppercase acronym (C). The directory, if not specified with -d, will be formed from the app name, but lowercased and underscored. =head1 ENVIRONMENT In Poet, your entire web site lives within a single directory hierarchy called the I. It contains subdirectories for configuration, libraries, Mason components (templates), static files, etc. Here are the subdirectories that are generated for you. If you don't need some of these directories, feel free to delete them. The only ones really required by Poet are C, C and C. =over =item * C - executable scripts =item * C - Mason components (templates) =item * C - configuration files =item * C - data not checked into version control, such as cache and object files =item * C - database related files such as your schema =item * C - app-specific libraries and Poet subclasses =item * C - logs from web server and from explicit logging statements =item * C - static web files - css, images, js =item * C - unit tests =back =head2 Initializing Poet Any web server or script must initialize Poet before it can use any of Poet's features. This is accomplished by with C: #!/usr/local/bin/perl use Poet::Script qw($conf $poet); You'll see this in C, for example, the script you use to start your webserver. The C line does several things: =over =item * Searches upwards from the script for the environment root (as marked by the C<.poet_root> file). =item * Reads and parses your configuration. =item * Unshifts onto @INC the lib/ subdirectory of your environment, so that you can C your application modules. =item * Imports the specified I - in this case C<$conf> and C<$poet> - as well as some default utilities into the script namespace. More information about these L. =back =head2 Relocatable environments Ideally your environment will be I -- if you move your environment to a different root, or checkout a second copy of it in a different root, things should just work. To achieve this you should never refer to exact environment directory paths in your code; instead you'll call Poet methods that return them. e.g. instead of this: system("/path/to/environment/bin/myscript.pl"); you'll do this: system($poet->bin_path("myscript.pl")); (More information about this C<$poet> variable L.) =head2 App name Your app name, e.g. C, is where you are expected to put app-specific subclasses. For example if you have a L schema, you'd create it under C in the file C. The app name is also where Poet will look for L of its own classes. =head1 CONFIGURATION AND LAYERS Poet configuration files are kept in the C subdirectory. The files are in L form and are merged in a particular order to create a single configuration hash. (If you want to use something other than YAML you can L.) Configuration files come in three varieties: C, C, and C, in order from least to highest precedence. =over =item Global I configuration applies to all instances of your environment, and is typically checked into version control. Your generated environment includes a C. This is simplest to start out with, but as a site scales in size and number of developers, you can split out your global configuration into multiple files in C. =item Layer I allow you to have different configuration for different contexts. With all but the simplest site you'll have at least two layers: I (the internal environment where you develop) and I (the live site). Later on, you might want a I environment (which looks like production except for a different data source, etc.). In general you can have as many layers as you like. Each layer has a corresponding configuration file in C; only one layer file will be loaded per environment. We recommend that these files all be checked into version control as well, so that changes to each layer are tracked. Note: I is analagous to Plack's I concept. And in fact, C passes the layer to plackup's <-E> option. =item Local C contains configuration local to this specific environment instance. This is where the current layer must be defined. It is also the only configuration file that must exist, and the only one that should I be checked into version control. You can also specify an extra local file via C<$ENV{POET_EXTRA_CONF_FILE}>. =back There are a variety of ways to access configuration: my $value = $conf->get('key', 'default'); my $value = $conf->get_or_die('key'); my $listref = $conf->get_list('key', ['default']); my $hashref = $conf->get_hash('key', {'default' => 5}); my $bool = $conf->get_boolean('key'); See L for more information about this C<$conf> variable, and see L for more information on specifying and accessing configuration. =head2 Development versus live mode Although you may have as many layers as you like, Poet also maintains a more limited binary notion of I versus I. By default, you're in development mode iff layer equals 'development', and in live mode otherwise. You can use these boolean methods to determine which mode you're in at runtime: $conf->is_development $conf->is_live These are mutually exclusive (exactly one is always true). Poet uses development/live mode to determine things like =over =item * whether to turn on L =item * whether to L =item * whether to enable L =back You can customize how mode is determined by L. =head1 SERVER RUNNER AND MIDDLEWARE C generates C and C for you. C is a wrapper around L, which starts your server. It has a few sensible defaults, such as setting up autorestart in L and using an access log in L. It will also take a few options from configuration, e.g. server: host: 127.0.0.1 port: 5000 If you are using something other than plackup (e.g. L) then you'll have to adapt this into your own startup script. C defines your PSGI app. It's the place to add L, or change the configuration of the default middleware. For example, to enable basic authentication with an C file, add this to app.psgi: enable "Auth::Htpasswd", file => $poet->conf_path('.htpasswd'); =head1 QUICK VARS AND UTILITIES Poet makes it easy to import certain variables (known as "quick vars") and utility sets into any module or script in your environment. You've seen two examples of quick vars above: C<$conf>, the global configuration variable, and C<$poet>, the global environment object. In a script, this looks like: #!/usr/local/bin/perl use Poet::Script qw($conf :file); In a module, this looks like: package MyApp::Foo; use Poet qw($cache $poet); And every Mason component automatically gets this on top: use Poet qw($conf $poet :web); L are automatically imported without having to specify a tag. See L for a complete list of quick vars and utility sets. =head1 HANDLING HTTP REQUESTS HTTP requests are handled with L/L and L. A persistent L is created at server startup, with L set to the C subdirectory. (See L for other default settings and how to configure them.) When an HTTP request comes in, Poet =over =item * Constructs a L object from the plack environment. This is a thin subclass of L and provides information such as the URL and incoming HTTP headers. It is made available in Mason components as C<< $m->req >>. =item * Constructs an empty L object. This is a thin subclass of L, and you may use it to set things such as the HTTP status and headers. It is made available in Mason components as C<< $m->res >>. =item * Calls C<$interp-Erun> with the URL and the GET/POST parameters. So for example, a URL like /foo/bar?a=5&b=6 would result in $interp->run("/foo/bar", a=>5, b=>6); From there Mason will choose a component to dispatch to - see L and L. =back =head2 Generating content with Mason Mason is a full-featured templating system and beyond our ability to summarize here. Recommended reading: =over =item * L, especially starting L =item * L =item * L =back =head2 Success and failure results If the Mason request finishes successfully, the Mason output becomes the plack response body, the status code is set to 200 if it hasn't already been set, and the content type is set to C (or the specified L) if it hasn't already been set. If the top-level component path cannot be found, the status code is set to 404. If any other kind of runtime error occurs in L, it will be nicely displayed in the browser via L middleware. Outside of development it will be logged and sent as a 500 error response. You can call C<< $m->redirect >> and C<< $m->not_found >> to generate redirect and not_found results from a component. These are documented in L. =head2 Multiple values for parameters If there are multiple values for a GET or POST parameter, generally only the last value will be kept, as per L. However, if the corresponding attribute in the page component is declared an C, then all values will be kept and passed in as an arrayref. For example, if the page component C has these declarations: <%class> has 'a'; has 'b' => (isa => "Int"); has 'c' => (isa => "ArrayRef"); has 'd' => (isa => "ArrayRef[Int]"); then this URL /foo/bar?a=1&a=2&b=3&b=4&c=5&c=6&d=7&d=8 would result in $interp->run("/foo/bar", a=>2, b=>4, c=>[5,6], d => [7,8]); You can always get the original Hash::MultiValue object from C<< $m->request_args >>. e.g. my $hmv = $m->request_args; # get all values for 'e' $hmv->get_all('e'); =head1 LOGGING Poet uses the L engine for logging, but with a much simpler configuration for the common cases. (If you want to use something other than Log4perl you can L.) Once you have a L<$log|/QUICK VARS AND UTILITIES> variable, logging looks like: $log->error("an error occurred"); $log->debugf("arguments are: %s", \@_) if $log->is_debug(); By default, all logs go to C with a reasonable set of metadata such as timestamp. See L for more information. =head1 CACHING Poet uses L for caching, providing access to a wide variety of cache backends (memory, files, memcached, etc.) You can configure caching differently for different namespaces. Once you have a L<$cache|/QUICK VARS AND UTILITIES> variable, caching looks like: my $customer = $cache->get($name); if ( !defined $customer ) { $customer = get_customer_from_db($name); $cache->set( $name, $customer, "10 minutes" ); } or my $customer2 = $cache->compute($name2, "10 minutes", sub { get_customer_from_db($name2) }); By default, everything is cached in files under C. See L for more information. =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 Poet-0.13/lib/Poet/Manual/Subclassing.pod0000644000076500000240000000660212034257703017443 0ustar swartzstaff __END__ =pod =head1 NAME Poet::Manual::Subclassing - Customizing Poet with subclasses =head1 DESCRIPTION You can subclass the following Poet classes for your application: Poet::Cache Poet::Conf Poet::Log Poet::Mason Poet::Import Poet::Plack::Request Poet::Plack::Response and arrange things so that Poet always uses your subclass instead of its default class. Place Poet subclasses under C in your environment, where C is your app name and C is the class you are subclassing minus the C prefix. A few of these subclasses are generated for you by C. For example, to subclass C: package MyApp::Cache; use Poet::Moose; extends 'Poet::Cache'; # put your modifications here 1; (Note: L is Moose plus a few Poet standards. You could also use plain C here.) Poet will automatically detect, load and use any such subclasses. Internally it uses the L environment method whenever it needs a classname, e.g. # Do something with MyApp::Cache or Poet::Cache $poet->app_class('Cache')->... =head2 Subclassing Mason As long as you have even a bare-bones C subclass, e.g. package MyApp::Mason; use Poet::Moose; extends 'Poet::Mason'; 1; then your Mason subclasses will be autodetected as well, e.g. package MyApp::Mason::Interp; use Moose; extends 'Mason::Interp'; # put your modifications here 1; C will create the bare-bones subclass for you; it is otherwise harmless. See L for more information. =head1 EXAMPLES =head2 Use INI instead of YAML for config files package MyApp::Conf; use Config::INI; use Moose; extends 'Poet::Conf'; override 'read_conf_file' => sub { my ($self, $file) = @_; return Config::INI::Reader->read_file($file); }; =head2 Perform tasks before and after each Mason request package MyApp::Mason::Request; use Moose; extends 'Mason::Request'; override 'run' => sub { my $self = shift; # Perform tasks before request my $result = super(); # Perform tasks after request return $result; }; =head2 Add Perl code to the top of every compiled component package MyApp::Mason::Compilation; use Moose; extends 'Mason::Compilation'; override 'output_class_header' => sub { return join("\n", super(), 'use Foo;', 'use Bar qw(baz);'); }; =head2 Use Log::Dispatch instead of Log4perl for logging package MyApp::Log; use Log::Any::Adapter; use Log::Dispatch; use Moose; extends 'Poet::Log'; override 'initialize_logging' => sub { my $log = Log::Dispatch->new( ... ); Log::Any::Adapter->set('Dispatch', dispatcher => $log); }; =head2 Add your own $dbh quick var package MyApp::Import use DBI; use Poet::Moose; extends 'Poet::Import'; method provide_dbh ($caller, $poet) { $dbh = DBI->connect(...); } =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 Poet-0.13/lib/Poet/Manual/Tutorial.pod0000644000076500000240000005021712034257703016772 0ustar swartzstaff __END__ =pod =head1 NAME Poet::Manual::Tutorial - Poet tutorial =head1 DESCRIPTION This tutorial provides a tour of Poet by showing how to build a sample web application - specifically a micro-blog, which seems to be a popular "hello world" for web frameworks. :) Thanks to L and L for the inspiration. =head1 INSTALLATION First we install Poet and a few other support modules. If you don't yet have cpanminus (C), get it L. Then run cpanm -S --notest DateTime DBD::SQLite Poet Rose::DB::Object Omit the "-S" if you don't have root, in which case cpanminus will install Poet and prereqs into C<~/perl5>. Omit the "--notest" if you want to run all the installation tests. Note that this will take about four times as long. =head1 SETUP You should now have a C app installed: % which poet /usr/local/bin/poet Run this to create the initial environment: % poet new Blog blog/.poet_root blog/bin/app.psgi blog/bin/get.pl ... Now run 'blog/bin/run.pl' to start your server. The name of the app, C, will be used in app-specific class names. It is also used for the default directory name (C), though you can move that wherever you want. Run this to start your server: % blog/bin/run.pl and you should see something like Running plackup --Reload ... --env development --port 5000 Watching ... for file updates. HTTP::Server::PSGI: Accepting connections at http://0:5000/ and you should be able to hit that URL to see the Poet welcome page. In Poet, your entire web site lives within a single directory hierarchy called the I. It contains subdirectories for configuration, libraries, Mason components (templates), static files, etc. From now on, every file we create in this tutorial is assumed to be under the environment root. =head1 DATA LAYER (MODEL) For any website it's a good idea to have a well-defined, object-oriented I through which you retrieve and change data. Poet and Mason don't have much to say about how you do this, so we'll make some minimal reasonable choices here and move on. For this demo we'll represent blog articles with a single sqlite table. Create a file C with: create table if not exists articles ( id integer primary key autoincrement, content string not null, create_time timestamp not null, title string not null ); Then run % cd blog % sqlite3 -batch data/blog.db < db/schema.sql We'll use L to provide a nice object-oriented API to our data (L would work as well). Create a file C to tell Rose how to connect to our database: lib/Blog/DB.pm: package Blog::DB; use Poet qw($poet); use strict; use warnings; use base qw(Rose::DB); __PACKAGE__->use_private_registry; __PACKAGE__->register_db( driver => 'sqlite', database => $poet->data_path("blog.db"), ); 1; and a file C to represent the articles table: lib/Blog/Article.pm: package Blog::Article; use Blog::DB; use strict; use warnings; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup( table => 'articles', auto => 1, ); __PACKAGE__->meta->make_manager_class('articles'); sub init_db { Blog::DB->new } 1; Basically this gives us =over =item * a C class with a constructor for inserting articles and instance methods for each of the columns, and =item * a C class (autogenerated) for searching for and retrieving multiple articles =back See L for more information. =head1 QUICK VARS AND UTILITIES In C above, we have use Poet qw($poet); followed by database => $poet->data_path("blog.db"), C<$poet> is the global L object, providing information about the environment and its directory paths. We use it here to get the full path to our sqlite database, without having to hardcode our environment root. More generally C<$poet> is one of several special Poet "quick vars" that can be imported into any package, just by including it on the C line. Another important one is C<$conf>, which gives you access to configuration: use Poet qw($conf $poet); ... my $value = $conf->get('key', 'default'); You can also import sets of utilities in the same way, e.g. ':file' for file utilities and ':web' for web-related utilities. See L for the full list of Poet vars and utility sets. =head1 CONFIGURATION Poet configuration files are kept in the C subdirectory. The files are in L form and are merged in a particular order to create a single configuration hash. For this tutorial we can ignore everything but C. It currently contains: layer: development server.port: 5000 This says that you are in development mode (so that you'll see errors directly in the browser, etc.) and running on port 5000. You'll need to add one more entry: server.load_modules: - Blog::Article This says to load C, our model, on server startup. See L for More information on configuration. =head1 MASON PAGES AND COMPONENTS Mason is the templating engine that you'll use to render pages (the I), and is also responsible for routing URLs to specific pieces of code (the I). So it's not surprising that most of the rest of this tutorial will focus on Mason. Mason's basic building block is the I - a file with a mix of Perl and HTML. All components lives under the subdirectory C; this is known in Mason parlance as the L. Given a URL, Mason will dispatch to a L. This component decides the overall page layout, and then may call other components to fill in the details. C generated a few starter components for us, but we're not going to use those, so let's clear them by running rm -fR comps; mkdir comps Now here's our first component to serve the home page, C: comps/index.mc: 1 2 3 4 My Blog: Home 5 6 7 8

Welcome to my blog.

9 10 <& all_articles.mi &> 11 12 Add an article 13 14 15 Any component with a C<.mc> extension is considered a top-level component. C is a special path - it will match the URI of its directory, in this case '/'. (For more special paths and details on how Mason finds page components, see L.) Most of this component contains just HTML, which will be output exactly as written. The single piece of special Mason syntax here is 10 <& all_articles.mi &> This is a L - it invokes another component, whose output is inserted in place. =head2 %-lines, substitution tags, <%init> blocks Next we create C. Because it has an C<.mi> extension rather than C<.mc>, it is an L rather than a top-level component, and cannot be reached by an external URL. It can only be reached via a component call from another component. comps/all_articles.mi 1 % if (@articles) { 2 Showing <% scalar(@articles) %> article<% @articles > 1 ? "s" : "" %>. 3
    4 % foreach my $article (@articles) { 5
  • <& article/display.mi, article => $article &>
  • 6 % } 7
8 % } 9 % else { 10

No articles yet.

11 % } 12 13 <%init> 14 my @articles = @{ Blog::Article::Manager->get_articles 15 (sort_by => 'create_time DESC') }; 16 Three new pieces of syntax here: =over =item Init block The L%initE|Mason::Manual::Syntax/E%initE> block on lines 13-16 specifies a block of Perl code to run first when this component is called. In this case it fetches and sorts the list of articles into a lexical variable C<< @articles >>. =item %-lines L<%-lines|Mason::Manual::Syntax/PERL LINES> - lines beginning with a single '%' - are treated as Perl rather than HTML. They are especially good for loops and conditionals. =item Substitution tags This line 2 Showing <% scalar(@articles) %> article<% @articles > 1 ? "s" : "" %>. shows two L. Code within C<< <% >> and C<< %> >> is treated as a Perl expression, and the result of the expression is output in place. =back We see another component call here, C<< article/display.mi >>, which displays a single article; we pass the article object in a name/value argument pair. Components can be in different directories and component paths can be relative or absolute. =head2 Attributes Next we create C. (It is in a new subdirectory, showing that you can freely organize components among different directories.) comps/article/display.mi: 1 <%class> 2 use Date::Format; 3 my $date_fmt = "%A, %B %d, %Y %I:%M %p"; 4 has 'article' => (required => 1); 5 6 7
8

<% $.article->title %>

9

<% $.article->create_time->strftime($date_fmt) %>

10 <% $.article->content %> 11
The L%classE|Mason::Manual::Syntax/E%classE> block on lines 1-4 specifies a block of Perl code to place near the top of the generated component class, outside of any methods. This is the place to use modules, declare permanent constants/variables, declare attributes with 'has', and define helper methods. Most components of any complexity will probably have a C<< <%class> >> section. On line 4 we declare a single incoming attribute, C
. It is I, meaning that if C had forgotten to pass it, we'd get a fatal error. Throughout this component, we refer to the article attribute via the expression $.article This not-quite-valid-Perl syntax is transformed behind the scenes to $self->article and is one of the rare cases in Mason where we create new syntax on top of Perl, because we want attributes and method calls to be as convenient as possible. The transformation itself is performed by the L, which is in the L list but can be omitted if the source filtering offends you. :) =head2 Content wrapping, autobases, inheritance, method modifiers Now we have to handle the URL C, linked from the home page. We do this with our second page component, C. It contains only HTML (for now). comps/new_article.mc: 1 2 3 4 My Blog: Home 5 6 7 8

Add an article

9 10
11

Title:

12

Text:

13 14

15
16 17 18 Notice that C and C have the same outer HTML template; other pages will as well. It's going to be tedious to repeat this everywhere. And of course, we don't have to. We take the common pieces out of the C and C and place them into a new component called C: comps/Base.mc: 1 <%augment wrap> 2 3 4 5 My Blog 6 7 8 <% inner() %> 9 10 11 When any page in our hierarchy is rendered, C will get control first. It will render the upper portion of the template (lines 2-7), then call the specific page component (line 8), then render the lower portion of the template (lines 9-10). Now, we can remove everything but the inside of the C<< >> tag from C and C. comps/index.mc:

Welcome to my blog.

<& all_articles.mi &> Add an article comps/new_article.mc

Add an article

Title:

Text:

More details on how content wrapping works L. =head2 Form handling, pure-perl components C posts to C
. Let's create a component to handle that, called C. It will not output anything, but will simply take action and redirect. comps/article/publish.mp: 1 has 'content'; 2 has 'title'; 3 4 method handle () { 5 my $session = $m->session; 6 if ( !$.content || !$.title ) { 7 $session->{message} = "Content and title required."; 8 $session->{form_data} = $.args; 9 $m->redirect('/new_article'); 10 } 11 my $article = Blog::Article->new( 12 title => $.title, 13 content => $.content, 14 create_time => DateTime->now( time_zone => 'local' ) 15 ); 16 $article->save; 17 $session->{message} = sprintf( "Article '%s' saved.", $.title ); 18 $m->redirect('/'); 19 } The C<.mp> extension indicates that this is a L component. Other than the 'package' and 'use Moose' lines that are generated by Mason, it looks just like a regular Perl class. You could accomplish the same thing with a C<.mc> component containing a single C<< <%class> >> block, but this is easier and more self-documenting. On lines 1 and 2 we declare incoming attributes. Because this is a top-level page component, the attributes will be populated with our POST parameters. On line 4 we define a C method to validate the POST parameters, create the article, and redirect. C is one of the L that Mason calls initially on all top-level page components; the default just renders the component's HTML as we've seen before. Defining C is the way to take an action without rendering anything, which is perfect for form actions. (It's always better to redirect after a form action than to display content directly.) The C keyword comes from L, which is imported into components by default; see L. On line 5 we grab the Plack session via C<< $m->session >>. This is one of a handful of L only available in Poet. On lines 7 and 17, we set a message in the session that we want to display on the next page. Rather than just making this work for a specific page, let's add generic code to the template in C: comps/Base.mc: 7 => 8 % if (my $message = delete($m->session->{message})) { => 9
<% $message %>
=> 10 % } 11 <% inner() %> 12 Now, any page can place a message in the session, and it'll appear on just the next page. On line 8, we place the POST data in the session so that we can repopulate the form with it - we'll do that in the next chapter. C<< $.args >> is a special component attribute that contains all the arguments passed to the component. =head2 Filters We need to change C<< comps/new_article.mc >> to repopulate the form with the submitted values when validation fails. comps/new_article.mc: 1

Add an article

2 ==> 3 % $.FillInForm($form_data) {{ 4
5

Title:

6

Text:

7 8

9
==> 10 % }} 11 ==> 12 <%init> ==> 13 my $form_data = delete($m->session->{form_data}); ==> 14 On lines 3 and 10 we surround the form with a I. A filter takes a content block as input and returns a new content block which is output in its place. In this case, the C filter uses L to fill in the form from the values in C<$form_data>. Mason has a few built-in filters, and others are provided in plugins; for example C is provided in the L. Another common filter provided by this plugin is C, or C for short. We ought to use this in C when displaying the article title, in case it has any HTML-unfriendly characters in it:

<% $.article->title |H %>

See L for more information about using, and creating, filters. =head1 POET SCRIPTS Up til now all our code has been in Mason components. Now let's say we want to create a I to purge blog entries older than a configurable number of days. The script, of course, will need access to the same Poet features as our components. Run this from anywhere inside your environment: % poet script purge_old_entries.pl ...bin/purge_old_entries.pl Poet created a stub script for us inside C. Let's take a look: #!/usr/local/bin/perl use Poet::Script qw($conf $poet); use strict; use warnings; Line 2 of the script initializes the Poet environment. This means Poet does several things: =over =item * Searches upwards from the script for the environment root (as marked by the C<.poet_root> file). =item * Reads and parses your configuration. =item * Unshifts onto @INC the lib/ subdirectory of your environment, so that you can C your application modules. =item * Imports the specified quick vars - in this case C<$conf> and C<$poet> - into the script namespace. See L. =back Poet initialization has to happen exactly once per process, before any Poet features are used. In fact, take a look at C -- which was generated for you initially -- and you'll see that it does 'use Poet::Script' as well. This initializes Poet for the entire web environment. Now we can fill out our purge script: #!/usr/local/bin/perl use Poet::Script qw($conf); use Blog::Article; use strict; use warnings; my $days_to_keep = $conf->get( 'blog.days_to_keep' => 365 ); my $min_date = DateTime->now->subtract( days => $days_to_keep ); Blog::Article::Manager->delete_articles( where => [ create_time => { lt => $min_date } ] ); In line 2, we've eliminated the unneeded C<$poet>. In line 7, we get C<$days_to_keep> from configuration, giving it a reasonable default if there's nothing in configuration. Finally in lines 8-10 we delete articles less than the minimum date. =head1 FILES FROM THIS TUTORIAL The final set of files for our blog demo are in the C directory of the Poet distribution, or you can view them at L. =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 Poet-0.13/lib/Poet/Manual.pod0000644000076500000240000000241012034257703015157 0ustar swartzstaff __END__ =pod =head1 NAME Poet::Manual - Index of Poet documentation =head1 MANUALS =over =item L Provides a tour of Poet by showing how to build a micro-blog website. =item L An introduction to Poet features with links to other documentation. =item L A list of Poet configuration options. =item L A guide to customizing Poet behavior via subclassing. =back =head1 REFERENCE DOCUMENTATION =over =item L =item L =item L =item L =item L =item L =item L =item L =item L =item L =back =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 Poet-0.13/lib/Poet/Mason/0000775000076500000240000000000012034257703014320 5ustar swartzstaffPoet-0.13/lib/Poet/Mason/Plugin/0000775000076500000240000000000012034257703015556 5ustar swartzstaffPoet-0.13/lib/Poet/Mason/Plugin/Compilation.pm0000644000076500000240000000047412034257703020375 0ustar swartzstaffpackage Poet::Mason::Plugin::Compilation; BEGIN { $Poet::Mason::Plugin::Compilation::VERSION = '0.13'; } use Mason::PluginRole; # Add 'use Poet qw($conf $poet :web)' at the top of every component # override 'output_class_header' => sub { return join( "\n", super(), 'use Poet qw($conf $poet :web);' ); }; 1; Poet-0.13/lib/Poet/Mason/Plugin/Request.pm0000644000076500000240000000414212034257703017543 0ustar swartzstaffpackage Poet::Mason::Plugin::Request; BEGIN { $Poet::Mason::Plugin::Request::VERSION = '0.13'; } use Mason::PluginRole; use Poet qw($conf $poet); use Poet::Plack::Response; use JSON::XS; use Try::Tiny; has 'req' => ( is => 'ro', required => 1, isa => 'Object' ); has 'res' => ( is => 'ro', required => 1, isa => 'Object' ); around 'run' => sub { my $orig = shift; my $self = shift; my $result = $self->$orig(@_); $self->res->status(200) if !$self->res->status; $self->res->content_type( $conf->get( 'server.default_content_type' => 'text/html' ) ) if !$self->res->content_type(); $self->res->content( $result->output ); return $result; }; around 'construct_page_component' => sub { my $orig = shift; my $self = shift; my ( $compc, $args ) = @_; if ( blessed($args) && $args->can('get_all') ) { my $orig_args = $args; $args = $orig_args->as_hashref; # TODO: cache this my @array_attrs = map { $_->name } grep { $_->has_type_constraint && $_->type_constraint->is_a_type_of('ArrayRef') } $compc->meta->get_all_attributes; foreach my $attr (@array_attrs) { $args->{$attr} = [ $orig_args->get_all($attr) ]; } } $self->$orig( $compc, $args ); }; override 'catch_abort' => sub { my ( $self, $code ) = @_; my $retval; try { $retval = $code->(); } catch { my $err = $_; if ( $self->aborted($err) ) { $retval = $err->aborted_value; } else { local $SIG{__DIE__} = undef; die $err; } }; return $retval; }; before 'abort' => sub { my ( $self, $retval ) = @_; $self->res->status($retval) if defined($retval); }; method redirect () { $self->res->redirect(@_); $self->clear_and_abort(); } method not_found () { $self->clear_and_abort(404); } method session () { $self->req->session; } method send_json ($data) { $self->clear_buffer; $self->print( JSON::XS::encode_json($data) ); $self->res->content_type("application/json"); $self->abort(); } 1; Poet-0.13/lib/Poet/Mason/Plugin.pm0000644000076500000240000000023412034257703016111 0ustar swartzstaffpackage Poet::Mason::Plugin; BEGIN { $Poet::Mason::Plugin::VERSION = '0.13'; } use Moose; with 'Mason::Plugin'; __PACKAGE__->meta->make_immutable(); 1; Poet-0.13/lib/Poet/Mason.pm0000644000076500000240000001570012034257703014657 0ustar swartzstaffpackage Poet::Mason; BEGIN { $Poet::Mason::VERSION = '0.13'; } ## no critic (Moose::RequireMakeImmutable) use Poet qw($conf $poet); use List::MoreUtils qw(uniq); use Method::Signatures::Simple; use Moose; use Try::Tiny; extends 'Mason'; my $instance; method instance ($class:) { $instance ||= $class->new(); $instance; } method new ($class:) { return $class->SUPER::new( $class->get_options, @_ ); } method get_options ($class:) { my %defaults = ( cache_root_class => $poet->app_class('Cache'), comp_root => $poet->comps_dir, data_dir => $poet->data_dir, plugins => [ $class->get_plugins ], ); my %configured = %{ $conf->get_hash("mason") }; my $extra_plugins = $conf->get_list("mason.extra_plugins"); delete( $configured{extra_plugins} ); my %options = ( %defaults, %configured ); $options{plugins} = [ uniq( @{ $options{plugins} }, '+Poet::Mason::Plugin', @$extra_plugins ) ]; return %options; } method get_plugins ($class:) { return ( 'HTMLFilters', 'RouterSimple', 'Cache' ); } method handle_psgi ($class: $psgi_env) { my $req = $poet->app_class('Plack::Request')->new($psgi_env); my $res = $poet->app_class('Plack::Response')->new(); my $response = try { my $interp = $poet->app_class('Mason')->instance; my $m = $interp->_make_request( req => $req, res => $res ); $m->run( $class->_psgi_comp_path($req), $class->_psgi_parameters($req) ); $m->res; } catch { my $err = $_; if ( blessed($err) && $err->isa('Mason::Exception::TopLevelNotFound') ) { $poet->app_class('Plack::Response')->new(404); } else { # Prevent Plack::Middleware::Debug from capturing this stack point local $SIG{__DIE__} = undef; die $err; } }; return $response->finalize; } method _psgi_comp_path ($class: $req) { my $comp_path = $req->path; $comp_path = "/$comp_path" if substr( $comp_path, 0, 1 ) ne '/'; return $comp_path; } method _psgi_parameters ($class: $req) { return $req->parameters; } 1; =pod =head1 NAME Poet::Mason -- Mason settings and enhancements for Poet =head1 SYNOPSIS # In a conf file... mason: plugins: - Cache - TidyObjectFiles - +My::Mason::Plugin static_source: 1 static_source_touch_file: ${root}/data/purge.dat # Get the main Mason instance my $mason = Poet::Mason->instance(); # Create a new Mason object my $mason = Poet::Mason->new(...); =head1 DESCRIPTION This is a Poet-specific L subclass. It sets up sane default settings, maintains a main Mason instance for handling web requests, and adds Poet-specific methods to C<$m> (the Mason request object). =head1 CLASS METHODS =over =item get_options Returns a hash of Mason options by combining L and L. =item instance Returns the main Mason instance used for web requests, which is created with options from L. =item new Returns a new main Mason object, using options from L. Unless you specifically need a new object, you probably want to call L. =back =head1 DEFAULT SETTINGS =over =item * C is set to L<$poet-Ecomps_dir|Poet::Environment/comps_dir>, by default the C subdirectory under the environment root. =item * C is set to L<$poet-Edata_dir|Poet::Environment/data_dir>, by default the C subdirectory under the environment root. =item * C is set to include L, L and L. =item * C (a parameter of the C plugin) is set to C if it exists (replacing C with your L), otherwise C. =back =head1 CONFIGURATION The Poet configuration entry 'mason', if any, will be treated as a hash of options that supplements and/or overrides the defaults above. If the hash contains 'extra_plugins', these will be added to the default plugins. e.g. mason: static_source: 1 static_source_touch_file: ${root}/data/purge.dat extra_plugins: - AnotherFavoritePlugin =head1 QUICK VARS AND UTILITIES Poet inserts the following line at the top of of every compiled Mason component: use Poet qw($conf $poet :web); which means that L<$conf|Poet::Conf>, L<$poet|Poet::Environment>, and L are available from every component. =head1 NEW REQUEST METHODS Under Poet these additional web-related methods are available in the L, accessible in components via C<$m> or elsewhere via Ccurrent_request>. =over =item req () A reference to the L object. e.g. my $user_agent = $m->req->headers->header('User-Agent'); =item res () A reference to the L object. e.g. $m->res->content_type('text/plain'); =item abort (status) =item clear_and_abort (status) These methods are overridden to set the response status before aborting, if I is provided. e.g. to send back a FORBIDDEN result: $m->clear_and_abort(403); This is equivalent to $m->res->status(403); $m->clear_and_abort(); If a status is not provided, the methods work just as before. =item redirect (url[, status]) Sets headers and status for redirect, then clears the Mason buffer and aborts the request. e.g. $m->redirect("http://somesite.com", 302); is equivalent to $m->res->redirect("http://somesite.com", 302); $m->clear_and_abort(); =item not_found () Sets the status to 404, then clears the Mason buffer and aborts the request. e.g. $m->not_found(); is equivalent to $m->clear_and_abort(404); =item session A shortcut for C<$m-Ereq-Esession>, the L. This is simply a persistent hash that you can read from and write to. It is tied to the user's browser session via cookies and stored in a file cache in the data directory (by default). my $value = $m->session->{key}; $m->session->{key} = { some_complex => ['value'] }; =item send_json ($data) Output the JSON-encoded I<$data>, set the content type to "application/json", and abort. e.g. method handle { my $data; # compute data somehow $m->send_json($data); } C is a shortcut for $m->clear_buffer; $m->print(JSON::XS::encode_json($data)); $m->res->content_type("application/json"); $m->abort(); =back =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Mechanize.pm0000644000076500000240000000065512034257703015510 0ustar swartzstaffpackage Poet::Mechanize; BEGIN { $Poet::Mechanize::VERSION = '0.13'; } use Poet::Environment; use Plack::Util; use base qw(Test::WWW::Mechanize::PSGI); use strict; use warnings; sub new { my ( $class, %params ) = @_; my $poet = delete( $params{'env'} ) || Poet::Environment->current_env; my $app = Plack::Util::load_psgi( $poet->bin_path("app.psgi") ); return $class->SUPER::new( app => $app, %params ); } 1; Poet-0.13/lib/Poet/Moose.pm0000644000076500000240000000252712034257703014667 0ustar swartzstaffpackage Poet::Moose; BEGIN { $Poet::Moose::VERSION = '0.13'; } ## no critic (Moose::RequireMakeImmutable) use Moose (); use MooseX::HasDefaults::RO (); use MooseX::StrictConstructor (); use Method::Signatures::Simple (); use Moose::Exporter; use strict; use warnings; Moose::Exporter->setup_import_methods( also => ['Moose'] ); sub init_meta { my $class = shift; my %params = @_; my $for_class = $params{for_class}; Method::Signatures::Simple->import( into => $for_class ); Moose->init_meta(@_); MooseX::StrictConstructor->import( { into => $for_class } ); MooseX::HasDefaults::RO->import( { into => $for_class } ); } 1; =pod =head1 NAME Poet::Moose - Poet Moose policies =head1 SYNOPSIS # instead of use Moose; use Poet::Moose; =head1 DESCRIPTION Sets certain Moose behaviors for Poet's internal classes. Using this module is equivalent to use Moose; use MooseX::HasDefaults::RO; use MooseX::StrictConstructor; use Method::Signatures::Simple; =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Plack/0000775000076500000240000000000012034257703014275 5ustar swartzstaffPoet-0.13/lib/Poet/Plack/Request.pm0000644000076500000240000000123312034257703016260 0ustar swartzstaffpackage Poet::Plack::Request; BEGIN { $Poet::Plack::Request::VERSION = '0.13'; } use Poet::Moose; extends 'Plack::Request'; 1; =pod =head1 NAME Poet::Plack::Request - Poet's subclass of Plack::Request =head1 DESCRIPTION This is a Poet-specific subclass of L, reserved for future additions and overrides. =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Plack/Response.pm0000644000076500000240000000124212034257703016426 0ustar swartzstaffpackage Poet::Plack::Response; BEGIN { $Poet::Plack::Response::VERSION = '0.13'; } use Poet::Moose; extends 'Plack::Response'; 1; =pod =head1 NAME Poet::Plack::Response - Poet's subclass of Plack::Response =head1 DESCRIPTION This is a Poet-specific subclass of L, reserved for future additions and overrides. =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Script.pm0000644000076500000240000000504712034257703015051 0ustar swartzstaffpackage Poet::Script; BEGIN { $Poet::Script::VERSION = '0.13'; } use Cwd qw(realpath); use File::Spec::Functions qw(rel2abs); use Method::Signatures::Simple; use Poet::Environment; use Poet::Tools qw(can_load dirname read_file); use strict; use warnings; method import ($pkg:) { unless ( Poet::Environment->current_env ) { my $root_dir = determine_root_dir(); my $poet = initialize_with_root_dir($root_dir); } Poet::Environment->current_env->importer->export_to_level( 1, @_ ); } func initialize_with_root_dir ($root_dir) { my ($app_name) = ( read_file("$root_dir/.poet_root") =~ /app_name: (.*)/ ) or die "cannot find app_name in $root_dir/.poet_root"; return Poet::Environment->initialize_current_environment( root_dir => $root_dir, app_name => $app_name ); } func determine_root_dir () { # Search for .poet_root upwards from current directory, using rel2abs # first, then realpath. # my $path1 = dirname( rel2abs($0) ); my $path2 = dirname( realpath($0) ); my $root_dir = search_upward($path1) || search_upward($path2); unless ( defined $root_dir ) { die sprintf( "could not find .poet_root upwards from %s", ( $path1 eq $path2 ) ? "'$path1'" : "'$path1' or '$path2'" ); } return $root_dir; } func search_upward ($path) { my $count = 0; while ( realpath($path) ne '/' && $count++ < 10 ) { if ( -f "$path/.poet_root" ) { return realpath($path); last; } $path = dirname($path); } return undef; } 1; __END__ =pod =head1 NAME Poet::Script -- Intialize Poet for a script =head1 SYNOPSIS # In a script... use Poet::Script qw($cache $conf $poet $log :file); =head1 DESCRIPTION This module is used to initialize Poet for a script. It does the following: =over =item * Determines the environment root by looking upwards from the directory of the current script until it finds the Poet marker file (C<.poet_root>). =item * Reads and parses configuration files. =item * Shifts the C subdirectory of the environment root onto C<@INC>. =item * Imports the specified I and utility sets into the current package - see L. =back =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 Poet-0.13/lib/Poet/Server.pm0000644000076500000240000000223112034257703015043 0ustar swartzstaffpackage Poet::Server; BEGIN { $Poet::Server::VERSION = '0.13'; } use Poet qw($conf $poet); use Method::Signatures::Simple; use Class::MOP; use strict; use warnings; method get_plackup_options () { my @options; # Pass -E with the layer name, e.g. "development" or "production" # push( @options, '-E', $conf->layer ); if ( defined( my $port = $conf->get('server.port') ) ) { push( @options, '--port', $port ); } if ( defined( my $host = $conf->get('server.host') ) ) { push( @options, '--host', $host ); } if ( $conf->is_development ) { # In development mode, reload server when conf or lib file changes # push( @options, '-R', join( ",", $poet->conf_dir, $poet->lib_dir ) ); } else { # In live mode, use access log instead of STDERR # push( @options, '--access_log', $poet->logs_path("access.log") ); } return @options; } my $loaded_startup_modules; method load_startup_modules () { return if $loaded_startup_modules++; foreach my $module ( @{ $conf->get_list('server.load_modules') } ) { Class::MOP::load_class($module); } } 1; Poet-0.13/lib/Poet/t/0000775000076500000240000000000012034257703013506 5ustar swartzstaffPoet-0.13/lib/Poet/t/App.pm0000644000076500000240000000063012034257703014561 0ustar swartzstaffpackage Poet::t::App; BEGIN { $Poet::t::App::VERSION = '0.13'; } use Test::Class::Most parent => 'Poet::Test::Class'; sub test_app_name_to_dir : Tests { require Poet::App::Command::new; my $try = sub { return Poet::App::Command::new->app_name_to_dir( $_[0] ); }; is( $try->("FooBar"), "foo_bar" ); is( $try->("HM"), "hm" ); is( $try->("foo_bar"), "foo_bar" ); } 1; Poet-0.13/lib/Poet/t/Conf.pm0000644000076500000240000001722512034257703014736 0ustar swartzstaffpackage Poet::t::Conf; BEGIN { $Poet::t::Conf::VERSION = '0.13'; } use Poet::Tools qw(read_file tempdir_simple write_file); use IPC::System::Simple qw(run); use Test::Class::Most parent => 'Poet::Test::Class'; require Poet; my $conf_files = { 'global/first.cfg' => { a => 10, b => 6, c => 11, d => 12 }, 'global/second.cfg' => { e => 20 }, 'global/third-not-a-config' => { f => 30 }, 'layer/personal.cfg' => { c => 40, g => '_${b}', h => 51 }, 'layer/development.cfg' => { f => 30 }, 'local.cfg' => { h => '__${e}', layer => 'personal' }, }; my $expected_values = { a => 10, b => 6, c => 40, d => 12, e => 20, f => undef, g => "_6", h => "__20", }; sub test_global : Tests { my $self = shift; my $poet = $self->temp_env( conf_files => $conf_files ); my $conf = $poet->conf(); while ( my ( $key, $value ) = each(%$expected_values) ) { is( $conf->get($key), $value, "$key = " . ( defined($value) ? $value : 'undef' ) ); } } sub test_duplicate : Tests { my $self = shift; throws_ok( sub { $self->temp_env( conf_files => { %$conf_files, 'global/fourth.cfg' => { j => 71, c => 72 } } ); }, qr/key 'c' defined in both '.*(first|fourth)\.cfg' and '.*(first|fourth)\.cfg'/, 'cannot define same key in multiple global config files' ); } sub test_set_local : Tests { my $self = shift; my $poet = $self->temp_env( conf_files => { 'global/foo.cfg' => { a => 5, b => 6, c => 7 } } ); my $conf = $poet->conf(); is( $conf->get('a'), 5, 'a = 5' ); is( $conf->get( 'b' => 0 ), 6, 'b = 6' ); is( $conf->get('c'), 7, 'c = 7' ); { my $lex = $conf->set_local( { a => 15, c => 17 } ); is( $conf->get('a'), 15, 'a = 15 (set_local)' ); is( $conf->get('b'), 6, 'b = 6 (set_local)' ); is( $conf->get('c'), 17, 'c = 17 (set_local)' ); { my $lex = $conf->set_local( { c => 27 } ); is( $conf->get('c'), 27, 'c = 27 (set_local depth 2)' ); my $lex2 = $conf->set_local( { c => 37 } ); $lex2 = 'shiny'; is( $conf->get('c'), 27, 'c = 27 (did not hold on to lexical)' ); } is( $conf->get('c'), 17, 'c = 17 (set_local)' ); } is( $conf->get('a'), 5, 'a = 5 (restored)' ); is( $conf->get('b'), 6, 'b = 6 (restored)' ); is( $conf->get('c'), 7, 'c = 7 (restored)' ); } sub test_dot_notation : Tests { my $self = shift; my $conf_files = { 'layer/personal.cfg' => ' a: b: c: 1 d: 2 e: f: g: 3 h: 4 ', 'local.cfg' => ' layer: personal a.b.c: 6 e: f: g: 7 ' }; my $poet = $self->temp_env( conf_files => $conf_files ); my %expected_values = ( 'a' => { 'b' => { c => 6, d => 2 } }, 'a.b' => { c => 6, d => 2 }, 'a.b.c' => 6, 'a.b.d' => 2, 'e' => { f => { g => 7 } }, 'e.f' => { g => 7 }, 'e.f.g' => 7, 'x.y.z' => undef ); my $conf = $poet->conf(); my $check_expected = sub { my $desc = shift; foreach my $key ( sort( keys(%expected_values) ) ) { my $value = $expected_values{$key}; cmp_deeply( $conf->get($key), $value, "$key - $desc" ); } }; $check_expected->('initial'); throws_ok( sub { $conf->get('a.b.c.z') }, qr/hash value expected for conf key 'a.b.c', got non-hash '6'/, "a.b.c.z" ); $conf_files->{'layer/personal.cfg'} = { 'a' => { 'b' => 17 } }; throws_ok( sub { $poet = $self->temp_env( conf_files => $conf_files ) }, qr/error assigning to 'a.b.c' in .*; 'a.b' already has non-hash value/, "e.f: 17" ); { my $lex = $conf->set_local( { 'a.b.c' => 16 } ); local $expected_values{'a'} = { 'b' => { c => 16, d => 2 } }, local $expected_values{'a.b'} = { c => 16, d => 2 }; local $expected_values{'a.b.c'} = 16; $check_expected->('set_local'); } $check_expected->('after set_local'); } sub test_types : Tests { my $self = shift; my $truth = { c => 't', d => 'true', e => 'y', f => 'yes' }; my $falsity = { c => 'f', d => 'false', e => 'n', f => 'no' }; my $conf_files = { 'global.cfg' => { scalar => 5, list => [ 1, 2, 3 ], hash => { size => 'large', flavor => 'chocolate' }, truth => $truth, falsity => $falsity, } }; my $poet = $self->temp_env( conf_files => $conf_files ); my $conf = $poet->conf(); cmp_deeply( $conf->get_list('list'), [ 1, 2, 3 ], 'list ok' ); foreach my $key (qw(scalar hash)) { throws_ok( sub { $conf->get_list($key) }, qr/list value expected/ ); } cmp_deeply( $conf->get_hash('hash'), { size => 'large', flavor => 'chocolate' }, 'hash ok' ); foreach my $key (qw(scalar list)) { throws_ok( sub { $conf->get_hash($key) }, qr/hash value expected/ ); } foreach my $key (qw(c d e f)) { is( $conf->get_boolean("truth.$key"), 1, "$key = true" ); is( $conf->get_boolean("falsity.$key"), 0, "$key = false" ); } foreach my $key (qw(scalar list hash)) { throws_ok( sub { $conf->get_boolean($key) }, qr/boolean value expected/ ); } } sub test_layer_required : Tests { my $self = shift; throws_ok( sub { $self->temp_env( conf_files => { 'local.cfg' => {} } ) }, qr/must specify layer/, 'no layer' ); } sub test_interpolation : Tests { my $self = shift; my $poet = $self->temp_env( conf => { layer => 'development', 'a.b' => 'bar', 'c' => '/foo/${a.b}/baz', 'd' => '/foo/${huh}/baz', 'deep' => { 'e' => 5, 'f' => [ '${c}', ['${a.b}'] ] } } ); my $conf = $poet->conf(); is( $conf->get('c'), '/foo/bar/baz', 'substitution' ); throws_ok { $conf->get('d') } qr/could not get conf for 'huh'/, 'bad substitution'; cmp_deeply( $conf->get('deep'), { e => 5, f => [ '/foo/bar/baz', ['bar'] ] }, 'deep' ); } sub test_dynamic_conf : Tests { my $self = shift; my $poet = $self->temp_env(); write_file( $poet->conf_path("dynamic/foo.mc"), "<% 2+2 %>" ); ok( !-d $poet->data_path("conf/dynamic"), "data/conf/dynamic does not exist" ); run( $poet->conf_path("dynamic/gen.pl") ); ok( -d $poet->data_path("conf/dynamic"), "data/conf/dynamic exists" ); ok( -f $poet->data_path("conf/dynamic/foo"), "conf/dynamic/foo exists" ); is( read_file( $poet->data_path("conf/dynamic/foo") ), 4, "foo has correct content" ); ok( !-f $poet->data_path("conf/dynamic/gen.pl"), "conf/dynamic/gen.pl does not exist" ); } sub test_get_secure : Tests { my $self = shift; my $tempdir = tempdir_simple('poet-conf-XXXX'); my $secure_file = "$tempdir/supersecret.cfg"; write_file( $secure_file, "foo: 7\nbar: 8\n" ); my $poet = $self->temp_env( conf_files => { 'local.cfg' => { layer => 'development', 'foo' => 0, 'baz' => 9, 'conf.secure_conf_file' => $secure_file } } ); my $conf = $poet->conf; is( $conf->get_secure('foo'), 7, "foo=0" ); is( $conf->get_secure('bar'), 8, "bar=8" ); is( $conf->get_secure('baz'), 9, "baz=9" ); is( $conf->get_secure('blargh'), undef, "blargh=undef" ); is( $conf->get('bar'), undef, "bar=undef" ); ok( ( !grep { /bar/ } $conf->get_keys() ), "no bar in keys" ); } 1; Poet-0.13/lib/Poet/t/Environment.pm0000644000076500000240000000340612034257703016351 0ustar swartzstaffpackage Poet::t::Environment; BEGIN { $Poet::t::Environment::VERSION = '0.13'; } use Test::Class::Most parent => 'Poet::Test::Class'; use Poet::Tools qw(mkpath tempdir_simple write_file); use Poet::Environment::Generator; sub test_environment : Tests { my $self = shift; my $app_name = 'TheTestApp'; my $poet = $self->temp_env( app_name => $app_name ); my $root_dir = $poet->root_dir; foreach my $subdir (qw(bin conf lib)) { my $subdir_method = $subdir . "_dir"; is( $poet->$subdir_method, "$root_dir/$subdir", $subdir_method ); ok( -d $poet->$subdir_method, "$subdir exists" ); ok( -d $poet->path($subdir), "$subdir exists" ); } is( $poet->conf->layer, 'development', "layer" ); foreach my $class (qw(Conf Log Mason)) { my $file = $poet->lib_path("$app_name/$class.pm"); ok( -f $file, "$file exists" ); } ok( -x $poet->bin_path("run.pl"), "run.pl executable" ); ok( -x $poet->bin_path("get.pl"), "get.pl executable" ); } sub test_dot_files_in_share_dir : Tests { my $self = shift; return 'author testing' if $ENV{AUTHOR_TESTING}; require File::Copy::Recursive; my $share_dir = $self->share_dir; my $temp_dir = tempdir_simple(); File::Copy::Recursive::rcopy( $share_dir, $temp_dir ) or die $!; my $gen_dir = "$temp_dir/generate.skel"; my @paths = ( "$gen_dir/extra", "$gen_dir/.git", "$gen_dir/bin/.svn" ); foreach my $path (@paths) { mkpath( $path, 0, 0775 ); write_file( "$path/hi.txt", "hi" ); } my $env_dir = $self->temp_env_dir( share_dir => $temp_dir ); ok( -d "$env_dir/extra", "extra exists" ); ok( !-d "$env_dir/.git", ".git does not exist" ); ok( !-d "$env_dir/bin/.svn", ".svn does not exist" ); } 1; Poet-0.13/lib/Poet/t/Import.pm0000644000076500000240000000431312034257703015315 0ustar swartzstaffpackage Poet::t::Import; BEGIN { $Poet::t::Import::VERSION = '0.13'; } use Test::Class::Most parent => 'Poet::Test::Class'; my ( $temp_env, $importer ); BEGIN { $temp_env = __PACKAGE__->initialize_temp_env(); $importer = $temp_env->importer; } sub test_valid_vars : Tests { cmp_deeply( $importer->valid_vars, supersetof(qw(cache conf log poet)) ); } sub test_import_vars : Tests { { package TestImportVars; BEGIN { $TestImportVars::VERSION = '0.13'; } BEGIN { $importer->export_to_level( 0, qw($cache $conf $env $poet) ) } use Test::Most; isa_ok( $cache, 'CHI::Driver', '$cache' ); isa_ok( $conf, 'Poet::Conf', '$conf' ); isa_ok( $env, 'Poet::Environment', '$env' ); isa_ok( $poet, 'Poet::Environment', '$poet' ); is( $env, $poet, '$env/$poet backward compat' ); } } sub test_import_bad_vars : Tests { { package TestImportVars2; BEGIN { $TestImportVars2::VERSION = '0.13'; } use Test::Most; throws_ok( sub { $importer->export_to_level( 0, qw($bad) ) }, qr/unknown import var '\$bad': valid import vars are '\$cache', '\$conf', '\$log', '\$poet'/, 'bad import' ); } } sub test_import_methods : Tests { { package TestImportMethods1; BEGIN { $TestImportMethods1::VERSION = '0.13'; } BEGIN { $importer->export_to_level(0) } use Test::Most; ok( TestImportMethods1->can('dp'), 'yes dp' ); ok( !TestImportMethods1->can('basename'), 'no basename' ); } { package TestImportMethods2; BEGIN { $TestImportMethods2::VERSION = '0.13'; } BEGIN { $importer->export_to_level( 0, qw(:file) ) } use Test::Most; foreach my $function (qw(dp basename mkpath rmtree)) { ok( TestImportMethods2->can($function), "yes $function" ); } } { package TestImportMethods3; BEGIN { $TestImportMethods3::VERSION = '0.13'; } BEGIN { $importer->export_to_level( 0, qw(:web) ) } use Test::Most; foreach my $function (qw(dp html_escape uri_escape)) { ok( TestImportMethods3->can($function), "yes $function" ); } } } 1; Poet-0.13/lib/Poet/t/Log.pm0000644000076500000240000000645612034257703014576 0ustar swartzstaffpackage Poet::t::Log; BEGIN { $Poet::t::Log::VERSION = '0.13'; } use Poet::Tools qw(rmtree tempdir_simple); use JSON::XS; use Test::Class::Most parent => 'Poet::Test::Class'; __PACKAGE__->initialize_temp_env(); sub test_log_config : Tests { my $poet = Poet::Environment->current_env; my $conf = $poet->conf; my $logs_dir = $poet->logs_dir; my $temp_dir = tempdir_simple(); my $other_dir = "$temp_dir/other"; my $test = sub { my ( $conf_settings, $expected ) = @_; my $lex = $conf->set_local($conf_settings); my $log_conf = Poet::Log->generate_log4perl_config(); is( $log_conf, $expected, encode_json($conf_settings) ); }; my $default_layout = "%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n"; rmtree($_) for ( $logs_dir, $other_dir ); $test->( {}, "log4perl.logger = INFO, default log4perl.appender.default = Log::Log4perl::Appender::File log4perl.appender.default.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.default.layout.ConversionPattern = $default_layout log4perl.appender.default.filename = $logs_dir/poet.log " ); ok( -d $logs_dir, "$logs_dir created" ); ok( !-d $other_dir, "$other_dir not created" ); rmtree($_) for ( $logs_dir, $other_dir ); $test->( { log => { 'defaults' => { level => 'debug', output => 'foo.log' } } }, "log4perl.logger = DEBUG, default log4perl.appender.default = Log::Log4perl::Appender::File log4perl.appender.default.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.default.layout.ConversionPattern = $default_layout log4perl.appender.default.filename = $logs_dir/foo.log " ); ok( -d $logs_dir, "$logs_dir created" ); ok( !-d $other_dir, "$other_dir not created" ); $test->( { log => { 'defaults' => { level => 'info', output => 'foo.log' }, 'class' => { 'Bar' => { level => 'warn', output => "$other_dir/bar.log" }, 'Bar.Errors' => { output => 'stderr' }, 'Bar.NonErrors' => { output => 'stdout' }, } } }, "log4perl.logger = INFO, default log4perl.appender.default = Log::Log4perl::Appender::File log4perl.appender.default.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.default.layout.ConversionPattern = $default_layout log4perl.appender.default.filename = $logs_dir/foo.log log4perl.logger.Bar = WARN, Bar log4perl.appender.Bar = Log::Log4perl::Appender::File log4perl.appender.Bar.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.Bar.layout.ConversionPattern = $default_layout log4perl.appender.Bar.filename = $other_dir/bar.log log4perl.logger.Bar.Errors = INFO, Bar_Errors log4perl.appender.Bar_Errors = Log::Log4perl::Appender::Screen log4perl.appender.Bar_Errors.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.Bar_Errors.layout.ConversionPattern = $default_layout log4perl.logger.Bar.NonErrors = INFO, Bar_NonErrors log4perl.appender.Bar_NonErrors = Log::Log4perl::Appender::Screen log4perl.appender.Bar_NonErrors.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.Bar_NonErrors.layout.ConversionPattern = $default_layout log4perl.appender.Bar_NonErrors.stderr = 0 " ); } 1; Poet-0.13/lib/Poet/t/NoLog4perl.pm0000644000076500000240000000107612034257703016033 0ustar swartzstaff# Test Log::Log4perl or Log::Any::Adapter::Log4perl not being present. # package Poet::t::NoLog4perl; BEGIN { $Poet::t::NoLog4perl::VERSION = '0.13'; } use Test::Class::Most parent => 'Poet::Test::Class'; use Module::Mask; use Poet::Tools qw(read_file); use strict; use warnings; sub test_no_log4perl : Tests { my $self = shift; my $poet = $self->initialize_temp_env(); my $error_file = $poet->logs_path("poet.log.ERROR"); ok( -f $error_file, "$error_file exists" ); like( read_file($error_file), qr/Could not load Log::Log4perl/ ); } 1; Poet-0.13/lib/Poet/t/PSGIHandler.pm0000644000076500000240000001515612034257703016112 0ustar swartzstaffpackage Poet::t::PSGIHandler; BEGIN { $Poet::t::PSGIHandler::VERSION = '0.13'; } use Test::Class::Most parent => 'Poet::Test::Class'; use Capture::Tiny qw(); use Guard; use Poet::Tools qw(dirname mkpath trim write_file); my $poet = __PACKAGE__->initialize_temp_env( conf => { layer => 'production', 'foo.bar' => 5, 'server.load_modules' => ['TestApp::Foo'] } ); unlink( glob( $poet->comps_path("*.mc") ) ); write_file( $poet->lib_path("TestApp/Foo.pm"), "package TestApp::Foo;\nsub bar {}\n1;\n" ); sub mech { my $self = shift; my $mech = $self->SUPER::mech( env => $poet ); @{ $mech->requests_redirectable } = (); return $mech; } sub add_comp { my ( $self, %params ) = @_; my $path = $params{path} or die "must pass path"; my $src = $params{src} or die "must pass src"; my $file = $poet->comps_dir . $path; mkpath( dirname($file), 0, 0775 ); write_file( $file, $src ); } sub try_psgi_comp { my ( $self, %params ) = @_; my $path = $params{path} or die "must pass path"; ( my $uri = $path ) =~ s/\.mc$//; my $qs = $params{qs} || ''; my $expect_code = defined( $params{expect_code} ) ? $params{expect_code} : 200; $self->add_comp(%params); my $mech = $self->mech(); { # Silence 'PSGI error' diagnostics if we're expecting error Test::More->builder->no_diag(1) if $expect_code == 500; scope_guard { Test::More->builder->no_diag(0) }; $mech->get( $uri . $qs ); } if ( my $expect_content = $params{expect_content} ) { if ( ref($expect_content) eq 'Regexp' ) { $mech->content_like( $expect_content, "$path - content" ); } else { is( trim( $mech->content ), trim($expect_content), "$path - content" ); } is( $mech->status, $expect_code, "$path - code" ); if ( my $expect_headers = $params{expect_headers} ) { while ( my ( $hdr, $value ) = each(%$expect_headers) ) { cmp_deeply( $mech->res->header($hdr), $value, "$path - header $hdr" ); } } } } sub test_get_pl : Tests { my $self = shift; $self->add_comp( path => '/getpl.mc', src => 'path = <% $m->req->path %>' ); my $cmd = sprintf( "%s /getpl", $poet->bin_path("get.pl") ); my $output = Capture::Tiny::capture_merged { system($cmd) }; is( $output, 'path = /getpl', "get.pl output" ); } sub test_basic : Tests { my $self = shift; $self->try_psgi_comp( path => '/basic.mc', src => 'path = <% $m->req->path %>', expect_content => 'path = /basic', ); } sub test_error : Tests { my $self = shift; $self->try_psgi_comp( path => '/error.mc', src => '% die "bleah";', expect_code => 500, expect_content => qr/bleah/, ); } sub test_not_found : Tests { my $self = shift; my $mech = $self->mech(); $mech->get("/does/not/exist"); is( $mech->status, 404, "status 404" ); like( $mech->content, qr/Not found/, "default not found page" ); } sub test_args : Tests { my $self = shift; $self->try_psgi_comp( path => '/args.mc', qs => '?a=1&a=2&b=3&b=4&c=5&c=6&d=7&d=8', src => ' <%args> $.a $.b => (isa => "Int") $.c => (isa => "ArrayRef"); $.d => (isa => "ArrayRef[Int]"); a = <% $.a %> b = <% $.b %> c = <% join(",", @{$.c}) %> d = <% join(",", @{$.d}) %> % my $args = $.args; <% Mason::Util::dump_one_line($args) %> ', expect_content => < '2',b => '4',c => ['5','6'],d => ['7','8']} EOF ); } sub test_abort : Tests { my $self = shift; $self->try_psgi_comp( path => '/redirect.mc', src => ' will not be printed % $m->redirect("http://www.google.com/"); will also not be printed ', expect_content => ' ', expect_code => 302, expect_headers => { Location => 'http://www.google.com/' }, ); $self->try_psgi_comp( path => '/go_to_redirect.mc', src => ' <%init> $m->go("/redirect"); ', expect_content => ' ', expect_code => 302, expect_headers => { Location => 'http://www.google.com/' }, ); $self->try_psgi_comp( path => '/visit_redirect.mc', src => ' <%init> $m->visit("/redirect"); ', expect_content => ' ', expect_code => 302, expect_headers => { Location => 'http://www.google.com/' }, ); $self->try_psgi_comp( path => '/redirect_301.mc', src => ' will not be printed % $m->redirect("http://www.yahoo.com/", 301); ', expect_content => ' ', expect_code => 301, expect_headers => { Location => 'http://www.yahoo.com/' }, ); $self->try_psgi_comp( path => '/not_found.mc', src => ' will not be printed % $m->clear_and_abort(404); ', expect_content => qr/Not found/, expect_code => 404, ); } sub test_import : Tests { my $self = shift; my $root_dir = $poet->root_dir; $self->try_psgi_comp( path => '/import.mc', src => ' foo.bar = <% $conf->get("foo.bar") %> root_dir = <% $poet->root_dir %> <% dh_live({baz => "blargh"}) %> ', expect_content => sprintf( " foo.bar = 5 root_dir = %s
\[dh_live at %s/comps/import.mc line 4.] [$$] {
  baz => 'blargh'
}

", $root_dir, $root_dir ) ); } sub test_visit : Tests { my $self = shift; Mason::Request->_reset_next_id(); $self->add_comp( path => '/subreq/other.mc', src => ' id=<% $m->id %> <% $m->page->cmeta->path %> <% $m->request_path %> <% Mason::Util::dump_one_line($m->request_args) %> ', ); $self->try_psgi_comp( path => '/subreq/visit.mc', src => ' begin id=<% $m->id %> <%perl>$m->visit("/subreq/other", foo => 5); id=<% $m->id %> end ', expect_content => " begin id=0 id=1 /subreq/other.mc /subreq/other \{foo => 5} id=0 end " ); } sub test_cache : Tests { my $self = shift; my $expected_root_dir = $poet->root_dir . "/data/cache"; $self->try_psgi_comp( path => '/cache.mc', src => ' chi_root_class: <% $m->cache->chi_root_class %> root_dir: <% $m->cache->root_dir %> ', expect_content => " chi_root_class: Poet::Cache root_dir: $expected_root_dir ", ); } sub test_misc : Tests { my $self = shift; $self->try_psgi_comp( path => '/misc.mc', src => 'TestApp::Foo = <% TestApp::Foo->can("bar") ? "loaded" : "not loaded" %>', expect_content => 'TestApp::Foo = loaded', ); } 1; Poet-0.13/lib/Poet/t/Run.pm0000644000076500000240000000244312034257703014611 0ustar swartzstaffpackage Poet::t::Run; BEGIN { $Poet::t::Run::VERSION = '0.13'; } use Test::Class::Most parent => 'Poet::Test::Class'; use Poet::Tools qw(read_file); use Guard; use IO::Socket; use Test::WWW::Mechanize; sub test_run : Tests { my $self = shift; my $poet = $self->temp_env( conf => { layer => 'development', server => { port => 9999 } } ); my $root_dir = $poet->root_dir; my $run_log = "$root_dir/logs/run.log"; if ( my $pid = fork() ) { scope_guard { kill( 1, $pid ) }; sleep(2); ok( -f $run_log, "run log exists" ); like( read_file($run_log), qr/Watching .* for file updates.*Accepting connections at .*:9999/s, "run log contents" ); ok( is_port_active( 9999, '127.0.0.1' ), "port 9999 active" ); my $mech = Test::WWW::Mechanize->new; $mech->get_ok('http://127.0.0.1:9999/'); $mech->content_like(qr/Welcome to Poet/); $mech->content_like(qr/Environment root.*\Q$root_dir\E/); } else { close STDOUT; close STDERR; exec( $poet->bin_path("run.pl > $run_log 2>&1") ); } } sub is_port_active { my ( $port, $bind_addr ) = @_; return IO::Socket::INET->new( PeerAddr => $bind_addr, PeerPort => $port ) ? 1 : 0; } 1; Poet-0.13/lib/Poet/t/Script.pm0000644000076500000240000000217112034257703015307 0ustar swartzstaffpackage Poet::t::Script; BEGIN { $Poet::t::Script::VERSION = '0.13'; } use Test::Class::Most parent => 'Poet::Test::Class'; use Capture::Tiny qw(capture); use Cwd qw(realpath); use YAML::XS; use Poet::Tools qw(dirname mkpath perl_executable tempdir_simple write_file); my $script_template; sub test_script : Tests { my $self = shift; my $root_dir = $self->temp_env_dir(); $self->write_conf_file( "$root_dir/conf/global/server.cfg", { 'foo.bar' => 42 } ); my $script = "$root_dir/bin/foo/bar.pl"; mkpath( dirname($script), 0, 0775 ); my $env_lib_dir = realpath("lib"); write_file( $script, sprintf( $script_template, perl_executable(), $env_lib_dir ) ); chmod( 0775, $script ); my ( $stdout, $stderr ) = capture { system($script) }; ok( !$stderr, "no stderr" . ( defined($stderr) ? " - $stderr" : "" ) ); my $result = Load($stdout); is_deeply( $result, [ $root_dir, "$root_dir/lib", "$root_dir/lib", 42 ] ); } $script_template = '#!%s use lib qw(%s); use Poet::Script qw($conf $poet); use YAML::XS; print Dump([$poet->root_dir, $poet->lib_dir, $INC[0], $conf->get("foo.bar")]); '; 1; Poet-0.13/lib/Poet/t/Subclassing.pm0000644000076500000240000000352512034257703016324 0ustar swartzstaffpackage Poet::t::Subclassing; BEGIN { $Poet::t::Subclassing::VERSION = '0.13'; } use Poet::Tools qw(write_file); use Test::Class::Most parent => 'Poet::Test::Class'; sub test_subclassing : Tests { my $self = shift; my $root_dir = $self->temp_env_dir(); write_file( "$root_dir/lib/TestApp/Cache.pm", join( "\n", "package TestApp::Cache;", "use Poet::Moose;", "extends 'Poet::Cache';" ) ); write_file( "$root_dir/lib/TestApp/Import.pm", join( "\n", "package TestApp::Import;", "use Poet::Moose;", "extends 'Poet::Import';", "no strict 'refs';", "before 'export_to_class' => sub { *{\$_[1] . '::bar'} = sub { 5 } };", "1", ) ); write_file( "$root_dir/lib/TestApp/Log.pm", join( "\n", "package TestApp::Log;", "use Poet::Moose;", "extends 'Poet::Log';", "sub get_logger { return bless({}, 'TestApp::Logger') }", ) ); my $poet = Poet::Environment->initialize_current_environment( root_dir => $root_dir, app_name => 'TestApp' ); isa_ok( $poet, 'Poet::Environment', 'env' ); # can't override this yet isa_ok( $poet->importer, 'TestApp::Import', 'import' ); isa_ok( $poet->conf, 'TestApp::Conf', 'conf' ); is( $poet->app_class('Cache'), 'TestApp::Cache', 'cache' ); { package Foo; BEGIN { $Foo::VERSION = '0.13'; } Poet->import(qw($cache $conf $log $poet)); use Test::More; is( $Foo::cache->chi_root_class, 'TestApp::Cache', '$cache' ); isa_ok( $Foo::conf, 'TestApp::Conf', '$conf' ); isa_ok( $Foo::log, 'TestApp::Logger', '$log' ); isa_ok( $Foo::poet, 'Poet::Environment', '$poet' ); is( Foo::bar(), 5, 'imported bar' ); } } 1; Poet-0.13/lib/Poet/t/Util.pm0000644000076500000240000000140512034257703014757 0ustar swartzstaffpackage Poet::t::Util; BEGIN { $Poet::t::Util::VERSION = '0.13'; } use Test::Class::Most parent => 'Poet::Test::Class'; use Poet::Tools qw(read_file); use Poet::Util::Debug qw(:all); use Capture::Tiny qw(capture_stderr); my $poet = __PACKAGE__->initialize_temp_env( conf => { layer => 'development' } ); sub test_debug : Tests { my $data = { foo => 5, bar => 6 }; my $expect = qr|\[d. at .* line .*\] \[\d+\] \{\n bar => 6,\n foo => 5\n\}|; throws_ok { dd($data) } $expect, "dd"; like( dh($data), $expect, "dh" ); like( capture_stderr { dp($data) }, $expect, "dp" ); my $console_log = $poet->logs_path("console.log"); ok( !-f $console_log, "no console log" ); dc($data); like( read_file($console_log), qr|$expect|, "dc" ); } 1; Poet-0.13/lib/Poet/TAGS0000644000076500000240000001606612034257703013733 0ustar swartzstaff App/Command/new.pm,73 package Poet::App::Command::new;1,0 my $description description11,486 App/Command/script.pm,40 package Poet::App::Command::script;1,0 App/Command.pm,32 package Poet::App::Command;1,0 App.pm,23 package Poet::App;1,0 Cache.pm,25 package Poet::Cache;1,0 Conf.pm,24 package Poet::Conf;1,0 Environment/Generator.pm,42 package Poet::Environment::Generator;1,0 Environment.pm,70 package Poet::Environment;1,0 my ($current_env)($current_env15,300 Import.pm,26 package Poet::Import;1,0 Log.pm,23 package Poet::Log;1,0 Manual/Intro.pod,78 webserver. The C line does several things:things106,2499 Manual/Subclassing.pod,510 use Poet::Moose;27,643 Poet will automatically detect,37,847 it needs a classname,39,1005 use Poet::Moose;49,1241 then your Mason subclasses will be autodetected as well,54,1298 package MyApp::Mason::Interp;56,1361 use Moose;57,1395 use Config::INI;73,1738 use Moose;74,1759 use Moose;85,2027 use Moose;101,2368 use Log::Any::Adapter;111,2621 use Log::Dispatch;112,2648 use Moose;113,2671 use Poet::Moose;125,2947 method provide_dbh 128,3001 Manual/Tutorial.pod,1520 package Blog::DB;94,2834 use strict;96,2879 use warnings;97,2895 __PACKAGE__->use_private_registry;use_private_registry100,2942 package Blog::Article;110,3177 use Blog::DB;111,3204 use strict;112,3222 use warnings;113,3238 database 137,3784 my $value 150,4344 You can also import sets of utilities in the same way,152,4391 Given a URL,168,5150 decides the overall page layout,170,5279  rm 179,5599 Three new pieces of syntax here:here233,7410 3 my $date_fmt 273,8617 everywhere. And of course,336,11015 4 component containing a single C<< <%class> >> block,389,13027 page component,393,13213 On line 4 we define a C method to validate the POST parameters,395,13289 the article,396,13368 top-level page components;398,13511 rendering anything,400,13663 On lines 7 and 410,14117 place. In this case,450,15591 Mason has a few built-in filters,454,15734 Mason has a few built-in filters, and others are provided in plugins;454,15734 use strict;483,16774 use warnings;484,16790 several things:things487,16885 use Blog::Article::Manager;521,17718 use strict;522,17750 use warnings;523,17766 my $days_to_keep 525,17789 my $min_date 526,17854 In line 530,18028 In line 532,18079 In line 7, we get C<$days_to_keep> from configuration,532,18079 Mason/Plugin/Compilation.pm,46 package Poet::Mason::Plugin::Compilation;1,0 Mason/Plugin/Request.pm,42 package Poet::Mason::Plugin::Request;1,0 Mason/Plugin.pm,33 package Poet::Mason::Plugin;1,0 Mason.pm,55 package Poet::Mason;1,0 my $instance;instance10,153 Moose.pm,70 package Poet::Moose;1,0 sub init_meta Poet::Moose::init_meta11,269 Plack/Request.pm,34 package Poet::Plack::Request;1,0 Plack/Response.pm,35 package Poet::Plack::Response;1,0 Script.pm,26 package Poet::Script;1,0 Server.pm,26 package Poet::Server;1,0 t/App.pm,93 package Poet::t::App;1,0 sub test_app_name_to_dir Poet::t::App::test_app_name_to_dir8,113 t/Conf.pm,455 package Poet::t::Conf;1,0 my $conf_files conf_files10,129 my $expected_values expected_values19,492 sub test_global Poet::t::Conf::test_global30,632 sub test_duplicate Poet::t::Conf::test_duplicate39,915 sub test_set_local Poet::t::Conf::test_set_local51,1279 sub test_dot_notation Poet::t::Conf::test_dot_notation81,2324 sub test_types Poet::t::Conf::test_types145,3943 sub test_layer_required Poet::t::Conf::test_layer_required183,5213 t/Environment.pm,102 package Poet::t::Environment;1,0 sub test_environment Poet::t::Environment::test_environment11,186 t/Import.pm,280 package Poet::t::Import;1,0 my ( $temp_env, $importer )( $temp_env, $importer 8,116 sub test_valid_vars Poet::t::Import::test_valid_vars15,233 sub test_import_vars Poet::t::Import::test_import_vars19,343 sub test_import_methods Poet::t::Import::test_import_methods30,686 t/Log.pm,84 package Poet::t::Log;1,0 sub test_log_config Poet::t::Log::test_log_config13,201 t/PSGIHandler.pm,741 package Poet::t::PSGIHandler;1,0 my $poet env14,261 sub mech Poet::t::PSGIHandler::mech18,392 sub add_comp Poet::t::PSGIHandler::add_comp24,506 sub try_psgi_comp Poet::t::PSGIHandler::try_psgi_comp33,773 sub test_get_pl Poet::t::PSGIHandler::test_get_pl74,2027 sub test_basic Poet::t::PSGIHandler::test_basic85,2353 sub test_error Poet::t::PSGIHandler::test_error94,2568 sub test_not_found Poet::t::PSGIHandler::test_not_found104,2803 sub test_args Poet::t::PSGIHandler::test_args112,3015 sub test_abort Poet::t::PSGIHandler::test_abort144,3565 sub test_import Poet::t::PSGIHandler::test_import179,4412 sub test_visit Poet::t::PSGIHandler::test_visit203,4870 sub test_cache Poet::t::PSGIHandler::test_cache238,5445 t/Run.pm,127 package Poet::t::Run;1,0 sub test_run Poet::t::Run::test_run13,229 sub is_port_active Poet::t::Run::is_port_active41,1145 t/Script.pm,126 package Poet::t::Script;1,0 my $script_template;script_template16,304 sub test_script Poet::t::Script::test_script18,326 t/Util.pm,96 package Poet::t::Util;1,0 my $poet env11,214 sub test_debug Poet::t::Util::test_debug13,284 Test/Util.pm,331 package Poet::Test::Util;1,0 sub write_conf_file Poet::Test::Util::write_conf_file18,375 sub temp_env Poet::Test::Util::temp_env28,658 sub temp_env_dir Poet::Test::Util::temp_env_dir47,1209 sub initialize_temp_env Poet::Test::Util::initialize_temp_env66,1775 sub build_test_mech Poet::Test::Util::build_test_mech71,1902 Tools.pm,648 package Poet::Tools;3,24 my $Fetch_Flags Fetch_Flags18,371 my $Store_Flags Store_Flags19,419 my $File_Spec_Using_Unix File_Spec_Using_Unix20,477 sub can_load Poet::Tools::can_load22,548 sub catdir Poet::Tools::catdir45,1029 sub catfile Poet::Tools::catfile51,1145 sub checksum Poet::Tools::checksum57,1263 sub find_wanted Poet::Tools::find_wanted71,1536 sub read_file Poet::Tools::read_file81,1695 sub tempdir_simple Poet::Tools::tempdir_simple106,2353 sub trim Poet::Tools::trim112,2463 sub uniq Poet::Tools::uniq121,2612 sub taint_is_on Poet::Tools::taint_is_on126,2681 sub write_file Poet::Tools::write_file130,2732 Types.pm,25 package Poet::Types;1,0 Util/Debug.pm,193 package Poet::Util::Debug;1,0 my $console_log;console_log12,262 sub _dump_value_with_caller Poet::Util::Debug::_dump_value_with_caller14,280 sub _define Poet::Util::Debug::_define25,603 Util/File.pm,30 package Poet::Util::File;1,0 Util/Web.pm,283 package Poet::Util::Web;1,0 my %html_escape html_escape12,270 my $html_escape html_escape14,357 my %js_escape js_escape17,430 sub html_escape Poet::Util::Web::html_escape34,865 sub js_escape Poet::Util::Web::js_escape40,975 sub make_uri Poet::Util::Web::make_uri47,1124 Manual.pod,0 Manual/Configuring.pod,0 Poet-0.13/lib/Poet/Test/0000775000076500000240000000000012034257703014162 5ustar swartzstaffPoet-0.13/lib/Poet/Test/Class.pm0000644000076500000240000000435012034257703015565 0ustar swartzstaffpackage Poet::Test::Class; BEGIN { $Poet::Test::Class::VERSION = '0.13'; } use Method::Signatures::Simple; use Carp; use Cwd qw(realpath); use Plack::Util; use Poet::Environment::Generator; use Poet::Environment; use Poet::Mechanize; use Poet::Tools qw(basename dirname mkpath rmtree tempdir_simple write_file); use Test::Class::Most; use YAML::XS; use strict; use warnings; __PACKAGE__->SKIP_CLASS("abstract base class"); method write_conf_file ($class:) { my ( $conf_file, $conf_content ) = @_; if ( ref($conf_content) eq 'HASH' ) { $conf_content = %$conf_content ? YAML::XS::Dump($conf_content) : ""; } mkpath( dirname($conf_file), 0, 0775 ); write_file( $conf_file, $conf_content ); } method temp_env ($class:) { my (%params) = @_; my $root_dir = $class->temp_env_dir(%params); my $app_name = $params{app_name} || 'TestApp'; if ( my $conf = $params{conf} ) { $class->write_conf_file( "$root_dir/conf/local.cfg", $conf ); } if ( my $conf_files = $params{conf_files} ) { while ( my ( $conf_file, $contents ) = each(%$conf_files) ) { $class->write_conf_file( "$root_dir/conf/$conf_file", $contents ); } } return Poet::Environment->new( root_dir => $root_dir, app_name => $app_name ); } method temp_env_dir ($class:) { my (%params) = @_; local $ENV{POET_SHARE_DIR} = $params{share_dir} || $class->share_dir; my $app_name = $params{app_name} || 'TestApp'; my $root_dir = Poet::Environment::Generator->generate_environment_directory( root_dir => tempdir_simple('Poet-XXXX'), app_name => $app_name, quiet => 1, style => 'bare', ); return realpath($root_dir); } method share_dir () { my $dist_root = dirname( dirname( dirname( dirname( realpath(__FILE__) ) ) ) ); my ($share_dir) = grep { -d $_ } ( "$dist_root/share", "$dist_root/lib/auto/share/dist/Poet" ); return $share_dir; } method initialize_temp_env ($class:) { my $poet = $class->temp_env(@_); Poet::Environment->initialize_current_environment( env => $poet ); } method mech ($class:) { return Poet::Mechanize->new(@_); } # prevent YAML::XS warning...wtf YAML::XS::Dump( {} ); YAML::XS::Dump( {} ); 1; Poet-0.13/lib/Poet/Tools.pm0000644000076500000240000001006712034257703014703 0ustar swartzstaff# Internal Poet tools # package Poet::Tools; BEGIN { $Poet::Tools::VERSION = '0.13'; } use Carp; use Class::MOP; use Config; use Fcntl qw( :DEFAULT :seek ); use File::Basename; use File::Find; use File::Path; use File::Slurp qw(read_dir); use File::Spec::Functions (); use File::Temp qw(tempdir); use Try::Tiny; use strict; use warnings; use base qw(Exporter); our @EXPORT_OK = qw(basename can_load catdir catfile checksum dirname find_wanted mkpath perl_executable read_dir read_file rmtree taint_is_on tempdir_simple trim uniq write_file ); my $Fetch_Flags = O_RDONLY | O_BINARY; my $Store_Flags = O_WRONLY | O_CREAT | O_BINARY; my $File_Spec_Using_Unix = $File::Spec::ISA[0] eq 'File::Spec::Unix'; sub can_load { # Load $class_name if possible. Return 1 if successful, 0 if it could not be # found, and rethrow load error (other than not found). # my ($class_name) = @_; my $result; try { Class::MOP::load_class($class_name); $result = 1; } catch { if ( /Can\'t locate .* in \@INC/ && !/Compilation failed/ ) { $result = 0; } else { die $_; } }; return $result; } sub catdir { return $File_Spec_Using_Unix ? join( "/", @_ ) : File::Spec::Functions::catdir(@_); } sub catfile { return $File_Spec_Using_Unix ? join( "/", @_ ) : File::Spec::Functions::catfile(@_); } sub checksum { my ($str) = @_; # Adler32 algorithm my $s1 = 1; my $s2 = 1; for my $c ( unpack( "C*", $str ) ) { $s1 = ( $s1 + $c ) % 65521; $s2 = ( $s2 + $s1 ) % 65521; } return ( $s2 << 16 ) + $s1; } # From File::Find::Wanted sub find_wanted { my $func = shift; my @files; local $_; find( sub { push @files, $File::Find::name if &$func }, @_ ); return @files; } # Return perl executable - from ExtUtils::MM_Unix sub perl_executable { my $interpreter; if ( $Config{startperl} =~ m,^\#!.*/perl, ) { $interpreter = $Config{startperl}; $interpreter =~ s,^\#!,,; } else { $interpreter = $Config{perlpath}; } return $interpreter; } sub read_file { my ($file) = @_; # Fast slurp, adapted from File::Slurp::read, with unnecessary options removed # my $buf = ""; my $read_fh; unless ( sysopen( $read_fh, $file, $Fetch_Flags ) ) { croak "read_file '$file' - sysopen: $!"; } my $size_left = -s $read_fh; while (1) { my $read_cnt = sysread( $read_fh, $buf, $size_left, length $buf ); if ( defined $read_cnt ) { last if $read_cnt == 0; $size_left -= $read_cnt; last if $size_left <= 0; } else { croak "read_file '$file' - sysread: $!"; } } return $buf; } sub tempdir_simple { my ($template) = @_; return tempdir( $template, TMPDIR => 1, CLEANUP => 1 ); } sub trim { my ($str) = @_; if ( defined($str) ) { for ($str) { s/^\s+//; s/\s+$// } } return $str; } # From List::MoreUtils sub uniq (@) { my %h; map { $h{$_}++ == 0 ? $_ : () } @_; } sub taint_is_on { return ${^TAINT} ? 1 : 0; } sub write_file { my ( $file, $data, $file_create_mode ) = @_; ($file) = $file =~ /^(.*)/s if taint_is_on(); # Untaint blindly $file_create_mode = oct(666) if !defined($file_create_mode); # Fast spew, adapted from File::Slurp::write, with unnecessary options removed # { my $write_fh; unless ( sysopen( $write_fh, $file, $Store_Flags, $file_create_mode ) ) { croak "write_file '$file' - sysopen: $!"; } my $size_left = length($data); my $offset = 0; do { my $write_cnt = syswrite( $write_fh, $data, $size_left, $offset ); unless ( defined $write_cnt ) { croak "write_file '$file' - syswrite: $!"; } $size_left -= $write_cnt; $offset += $write_cnt; } while ( $size_left > 0 ); truncate( $write_fh, sysseek( $write_fh, 0, SEEK_CUR ) ) } } 1; Poet-0.13/lib/Poet/Types.pm0000644000076500000240000000043612034257703014706 0ustar swartzstaffpackage Poet::Types; BEGIN { $Poet::Types::VERSION = '0.13'; } use Moose::Util::TypeConstraints; use strict; use warnings; subtype 'Poet::Types::AppName' => as 'Str', where { /^[[:alpha:]_]\w*$/ }, message { "The app name you provided, '$_', was not a valid identifier" }; 1; Poet-0.13/lib/Poet/Util/0000775000076500000240000000000012034257703014160 5ustar swartzstaffPoet-0.13/lib/Poet/Util/Debug.pm0000644000076500000240000001034012034257703015540 0ustar swartzstaffpackage Poet::Util::Debug; BEGIN { $Poet::Util::Debug::VERSION = '0.13'; } use Carp qw(longmess); use Data::Dumper; use strict; use warnings; use base qw(Exporter); our @EXPORT_OK = map { ( "$_", "$_" . "s", "$_" . "_live", "$_" . "s_live" ) } qw(dc dd dh dp); our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK ); my $console_log; sub _dump_value_with_caller { my ( $value, $func_name ) = @_; my $dump = Data::Dumper->new( [$value] )->Indent(1)->Sortkeys(1)->Quotekeys(0)->Terse(1)->Dump(); my @caller = caller(1); return sprintf( "[%s at %s line %d.] [%d] %s\n", $func_name, $caller[1], $caller[2], $$, $dump ); } sub _define { my ( $func, $code ) = @_; no strict 'refs'; my $funcs = $func . "s"; my $func_live = $func . "_live"; my $funcs_live = $func . "s_live"; *$func = sub { return unless Poet::Environment->current_env->conf->is_development; $code->( _dump_value_with_caller( $_[0], $func ) ); }; *$funcs = sub { return unless Poet::Environment->current_env->conf->is_development; $code->( longmess( _dump_value_with_caller( $_[0], $funcs ) ) ); }; *$func_live = sub { $code->( _dump_value_with_caller( $_[0], $func_live ) ); }; *$funcs_live = sub { $code->( longmess( _dump_value_with_caller( $_[0], $funcs_live ) ) ); }; } _define( 'dc', sub { $console_log ||= Poet::Environment->current_env->logs_path("console.log"); open( my $fh, ">>", $console_log ); $fh->print( $_[0] ); } ); _define( 'dd', sub { die $_[0]; } ); _define( 'dh', sub { return "
\n$_[0]
\n"; } ); _define( 'dp', sub { print STDERR $_[0]; } ); 1; =pod =head1 NAME Poet::Util::Debug - Debug utilities =head1 SYNOPSIS # In a script... use Poet::Script; # In a module... use Poet; # Automatically available in Mason components # then... # die with value dd $data; # print value to STDERR dp $data; # print value to logs/console.log dc $data; # return value prepped for HTML dh $data; # same as above with full stacktraces dds $data; dps $data; dcs $data; dhs $data; =head1 DESCRIPTION These debug utilities are automatically imported wherever C or C appear, and in all components. Because let's face it, debugging is something you always want at your fingertips. However, for safety, the short named versions of these utilities are no-ops outside of L, in case debug statements accidentally leak into production (we've all done it). You have to use longer, less convenient names outside of development for them to work. =head1 UTILITIES Each of these utilities takes a single scalar value. The value is serialized with L and prefixed with a file name, line number, and pid. e.g. dp { a => 5, b => 6 }; prints to STDERR [dp at ./d.pl line 6.] [1436] { a => 5, b => 6 } The variants suffixed with 's' additionally output a full stack trace. =over =item dd ($val), dds ($val) Die with the serialized I<$val>. =item dp ($val), dps ($val) Print the serialized I<$val> to STDERR. Useful in scripts. =item dc ($val), dcs ($val) Append the serialized I<$val> to "console.log" in the C subdirectory of the environment. Useful as a quick alternative to full-bore L. =item dh ($val), dhs ($val) Returns the serialized I<$val>, surrounded by C<<
 
>> tags. Useful for embedding in Mason components, e.g. <% dh($data) %> =back =head2 Live variants Each of the functions above must be appended with "_live" in order to work in L. e.g. # This is a no-op in live mode dp [$foo]; # but this will work dp_live [$foo]; =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Util/File.pm0000644000076500000240000000304612034257703015376 0ustar swartzstaffpackage Poet::Util::File; BEGIN { $Poet::Util::File::VERSION = '0.13'; } use File::Basename qw(basename dirname); use File::Path qw(); use File::Slurp qw(read_dir read_file write_file); use File::Spec::Functions qw(abs2rel canonpath catdir catfile rel2abs); use List::MoreUtils qw(uniq); use strict; use warnings; use base qw(Exporter); File::Path->import( @File::Path::EXPORT, @File::Path::EXPORT_OK ); our @EXPORT_OK = uniq( qw(abs2rel basename canonpath catdir catfile dirname read_file rel2abs write_file), @File::Path::EXPORT, @File::Path::EXPORT_OK ); our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK ); 1; =pod =head1 NAME Poet::Util::File - File utilities =head1 SYNOPSIS # In a script... use Poet::Script qw(:file); # In a module... use Poet qw(:file); # In a component... <%class> use Poet qw(:file); =head1 DESCRIPTION This group of utilities includes =over =item basename, dirname From L. =item mkpath, make_path, rmtree, remove_tree From L. =item read_file, write_file, read_dir From L. =item abs2rel canonpath catdir catfile rel2abs From L. =back =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet/Util/Web.pm0000644000076500000240000000501012034257703015225 0ustar swartzstaffpackage Poet::Util::Web; BEGIN { $Poet::Util::Web::VERSION = '0.13'; } use Data::Dumper; use URI; use URI::Escape qw(uri_escape uri_unescape); use strict; use warnings; use base qw(Exporter); our @EXPORT_OK = qw(html_escape js_escape make_uri uri_escape uri_unescape); our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK ); my %html_escape = ( '&' => '&', '>' => '>', '<' => '<', '"' => '"' ); my $html_escape = qr/([&<>"])/; # Stolen from Javascript::Value::Escape my %js_escape = ( q!\\! => 'u005c', q!"! => 'u0022', q!'! => 'u0027', q! 'u003c', q!>! => 'u003e', q!&! => 'u0026', q!=! => 'u003d', q!-! => 'u002d', q!;! => 'u003b', q!+! => 'u002b', "\x{2028}" => 'u2028', "\x{2029}" => 'u2029', ); map { $js_escape{ pack( 'U', $_ ) } = sprintf( "u%04d", $_ ) } ( 0x00 .. 0x1f, 0x7f ); sub html_escape { my $text = $_[0]; $text =~ s/$html_escape/$html_escape{$1}/mg; return $text; } sub js_escape { my $text = shift; $text =~ s!([\\"'<>&=\-;\+\x00-\x1f\x7f]|\x{2028}|\x{2029})!\\$js_escape{$1}!g; return $text; } sub make_uri { my ( $base, $params ) = @_; my $uri = URI->new($base); if ( defined($params) ) { die "second argument must be a hashref" if ref($params) ne 'HASH'; $uri->query_form($params); } return $uri->as_string; } 1; =pod =head1 NAME Poet::Util::Web - Web-related utilities =head1 SYNOPSIS # In a script... use Poet::Script qw(:web); # In a module... use Poet qw(:web); # Automatically available in Mason components =head1 DESCRIPTION This group of utilities includes =over =item html_escape ($str) Return I<$str> with HTML entities escaped/unescaped. =item uri_escape ($str), uri_unescape ($str) Return I<$str> URI escaped/unescaped, from L =item js_escape ($str) Return I<$str> escaped for Javascript, borrowed from L. =item make_uri ($path, $args) Create a URL by combining I<$path> with a query string formed from hashref I<$args>. e.g. make_uri("/foo/bar", { a => 5, b => 6 }); ==> /foo/bar?a=5&b=6 =back =head1 SEE ALSO L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/lib/Poet.pm0000644000076500000240000000625112034257703013603 0ustar swartzstaffpackage Poet; BEGIN { $Poet::VERSION = '0.13'; } use Poet::Environment; use Method::Signatures::Simple; use strict; use warnings; method import ($class:) { my $poet = Poet::Environment->current_env or die "environment has not been initialized!"; $poet->importer->export_to_level( 1, @_ ); } 1; =pod =head1 NAME Poet -- a modern Perl web framework for Mason developers =head1 SYNOPSIS % poet new MyApp my_app/.poet_root my_app/bin/app.psgi ... % my_app/bin/run.pl Running plackup --Reload ... --env development --port 5000 Watching ... for file updates. HTTP::Server::PSGI: Accepting connections at http://0:5000/ =head1 DESCRIPTION Poet is a modern Perl web framework designed especially for L developers. It uses L/L for server integration, Mason for request routing and templating, and a selection of best-of-breed CPAN modules for caching, logging and configuration. =head1 FEATURES =over =item * A common-sense L for web development =item * A L that scales elegantly with multiple coders and multiple layers (development/production) =item * Integration with L for logging, wrapped with dead-simple configuration =item * Integration with L for powerful and flexible caching =item * The power of L, an object-oriented templating system, for request routing and content generation =item * Easy access to common L and L from anywhere in your application =item * Conventions and defaults based on the author's best practices from over fifteen years of Perl web development; and =item * The freedom to L just about any of Poet's behaviors =back =head1 DOCUMENTATION All documentation is indexed at L. =head1 SUPPORT For now Poet will share a mailing list and IRC with Mason. The Mason mailing list is C; you must be L to send a message. The Mason IRC channel is L<#mason|irc://irc.perl.org/#mason>. Bugs and feature requests will be tracked at RT: http://rt.cpan.org/NoAuth/Bugs.html?Dist=Poet bug-poet@rt.cpan.org The latest source code can be browsed and fetched at: http://github.com/jonswar/perl-poet git clone git://github.com/jonswar/perl-poet.git =head1 ACKNOWLEDGEMENTS Poet was originally designed and developed for the Digital Media group of the Hearst Corporation, a diversified media company based in New York City. Many thanks to Hearst for agreeing to this open source release. However, Hearst has no direct involvement with this open source release and bears no responsibility for its support or maintenance. =head1 SEE ALSO L, L, L =head1 AUTHOR Jonathan Swartz =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. 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 __END__ Poet-0.13/LICENSE0000644000076500000240000004352512034257703012602 0ustar swartzstaffThis software is copyright (c) 2012 by Jonathan Swartz. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2012 by Jonathan Swartz. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2012 by Jonathan Swartz. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End Poet-0.13/Makefile.PL0000644000076500000240000000401412034257703013535 0ustar swartzstaff use strict; use warnings; use ExtUtils::MakeMaker 6.30; use File::ShareDir::Install; install_share dist => "share"; my %WriteMakefileArgs = ( 'ABSTRACT' => 'The Mason web framework', 'AUTHOR' => 'Jonathan Swartz ', 'BUILD_REQUIRES' => { 'Test::Class::Most' => '0', 'Test::WWW::Mechanize::PSGI' => '0' }, 'CONFIGURE_REQUIRES' => { 'ExtUtils::MakeMaker' => '6.30', 'File::ShareDir::Install' => '0.03' }, 'DISTNAME' => 'Poet', 'EXE_FILES' => [ 'bin/poet' ], 'LICENSE' => 'perl', 'NAME' => 'Poet', 'PREREQ_PM' => { 'CHI' => '0.52', 'Capture::Tiny' => '0', 'Data::Rmap' => '0.6', 'File::ShareDir' => '0', 'File::ShareDir::Install' => '0', 'File::Slurp' => '0', 'File::Spec::Functions' => '0', 'Guard' => '0', 'JSON::XS' => '0', 'Log::Any::Adapter' => '0', 'Mason' => '2.19', 'Mason::Plugin::Cache' => '0.04', 'Mason::Plugin::HTMLFilters' => '0', 'Mason::Plugin::RouterSimple' => '0.05', 'MasonX::ProcessDir' => '0.02', 'Method::Signatures::Simple' => '1.02', 'Moose' => '1.15', 'MooseX::App::Cmd' => '0', 'MooseX::HasDefaults::RO' => '0', 'MooseX::StrictConstructor' => '0', 'Plack' => '0', 'Plack::Middleware::Debug' => '0', 'Plack::Middleware::Session' => '0', 'Plack::Session::Store::Cache' => '0', 'Try::Tiny' => '0', 'URI' => '0', 'URI::Escape' => '0', 'YAML::XS' => '0' }, 'VERSION' => '0.13', 'test' => { 'TESTS' => 't/*.t' } ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.56) } ) { my $br = delete $WriteMakefileArgs{BUILD_REQUIRES}; my $pp = $WriteMakefileArgs{PREREQ_PM}; for my $mod ( keys %$br ) { if ( exists $pp->{$mod} ) { $pp->{$mod} = $br->{$mod} if $br->{$mod} > $pp->{$mod}; } else { $pp->{$mod} = $br->{$mod}; } } } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); package MY; use File::ShareDir::Install qw(postamble); Poet-0.13/MANIFEST0000644000076500000240000000554712034257703012730 0ustar swartzstaffChanges INSTALL LICENSE MANIFEST META.json META.yml Makefile.PL TAGS bin/poet eg/blog/README eg/blog/bin/app.psgi eg/blog/bin/get.pl eg/blog/bin/install.sh eg/blog/bin/purge_old_entries.pl eg/blog/bin/run.pl eg/blog/comps/Base.mc eg/blog/comps/all_articles.mi eg/blog/comps/article/display.mi eg/blog/comps/article/publish.mp eg/blog/comps/index.mc eg/blog/comps/new_article.mc eg/blog/conf/layer/development.cfg eg/blog/conf/local.cfg eg/blog/db/schema.sql eg/blog/lib/Blog/Article.pm eg/blog/lib/Blog/DB.pm eg/blog/logs/poet.log eg/blog/static/css/style.css lib/Poet.pm lib/Poet/App.pm lib/Poet/App/Command.pm lib/Poet/App/Command/new.pm lib/Poet/App/Command/script.pm lib/Poet/Cache.pm lib/Poet/Conf.pm lib/Poet/Environment.pm lib/Poet/Environment/Generator.pm lib/Poet/Import.pm lib/Poet/Log.pm lib/Poet/Manual.pod lib/Poet/Manual/Configuring.pod lib/Poet/Manual/Intro.pod lib/Poet/Manual/Subclassing.pod lib/Poet/Manual/Tutorial.pod lib/Poet/Mason.pm lib/Poet/Mason/Plugin.pm lib/Poet/Mason/Plugin/Compilation.pm lib/Poet/Mason/Plugin/Request.pm lib/Poet/Mechanize.pm lib/Poet/Moose.pm lib/Poet/Plack/Request.pm lib/Poet/Plack/Response.pm lib/Poet/Script.pm lib/Poet/Server.pm lib/Poet/TAGS lib/Poet/Test/Class.pm lib/Poet/Tools.pm lib/Poet/Types.pm lib/Poet/Util/Debug.pm lib/Poet/Util/File.pm lib/Poet/Util/Web.pm lib/Poet/t/App.pm lib/Poet/t/Conf.pm lib/Poet/t/Environment.pm lib/Poet/t/Import.pm lib/Poet/t/Log.pm lib/Poet/t/NoLog4perl.pm lib/Poet/t/PSGIHandler.pm lib/Poet/t/Run.pm lib/Poet/t/Script.pm lib/Poet/t/Subclassing.pm lib/Poet/t/Util.pm perlcriticrc share/generate.skel/DOT_poet_root share/generate.skel/bin/app.psgi share/generate.skel/bin/get.pl share/generate.skel/bin/run.pl share/generate.skel/bin/tmp/EMPTY share/generate.skel/comps/Base.mc share/generate.skel/comps/index.mc share/generate.skel/comps/tmp/EMPTY share/generate.skel/conf/dynamic/gen.pl share/generate.skel/conf/global.cfg share/generate.skel/conf/layer/development.cfg share/generate.skel/conf/layer/production.cfg share/generate.skel/conf/local.cfg share/generate.skel/data/EMPTY share/generate.skel/db/EMPTY share/generate.skel/lib/MyApp/Conf.pm share/generate.skel/lib/MyApp/Log.pm share/generate.skel/lib/MyApp/Mason.pm share/generate.skel/lib/MyApp/Mason/Compilation.pm share/generate.skel/lib/MyApp/Mason/README share/generate.skel/lib/MyApp/Mason/Request.pm share/generate.skel/lib/MyApp/README share/generate.skel/logs/EMPTY share/generate.skel/static/css/style.css share/generate.skel/static/errors/401.html share/generate.skel/static/errors/403.html share/generate.skel/static/errors/404.html share/generate.skel/static/errors/500.html share/generate.skel/static/images/EMPTY share/generate.skel/static/js/EMPTY share/generate.skel/t/EMPTY t/App.t t/Conf.t t/Environment.t t/Import.t t/Log.t t/PSGIHandler.t t/Script.t t/Subclassing.t t/Util.t t/author-NoLog4perl.t t/author-NoLogAnyAdapterLog4perl.t t/author-Run.t Poet-0.13/META.json0000644000076500000240000000505712034257703013214 0ustar swartzstaff{ "abstract" : "The Mason web framework", "author" : [ "Jonathan Swartz " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 4.200008, CPAN::Meta::Converter version 2.113640", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "Poet", "no_index" : { "directory" : [ "lib/Poet/t", "lib/Poet/Test", "lib/Poet/App.pm", "lib/Poet/App", "lib/Poet/Mason/Plugin.pm", "lib/Poet/Mason/Plugin", "lib/Poet/Types.pm", "lib/Poet/Util.pm" ] }, "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.30", "File::ShareDir::Install" : "0.03" } }, "runtime" : { "recommends" : { "Log::Any::Adapter::Log4perl" : 0, "Log::Log4perl" : 0 }, "requires" : { "CHI" : "0.52", "Capture::Tiny" : 0, "Data::Rmap" : "0.6", "File::ShareDir" : 0, "File::ShareDir::Install" : 0, "File::Slurp" : 0, "File::Spec::Functions" : 0, "Guard" : 0, "JSON::XS" : 0, "Log::Any::Adapter" : 0, "Mason" : "2.19", "Mason::Plugin::Cache" : "0.04", "Mason::Plugin::HTMLFilters" : 0, "Mason::Plugin::RouterSimple" : "0.05", "MasonX::ProcessDir" : "0.02", "Method::Signatures::Simple" : "1.02", "Moose" : "1.15", "MooseX::App::Cmd" : 0, "MooseX::HasDefaults::RO" : 0, "MooseX::StrictConstructor" : 0, "Plack" : 0, "Plack::Middleware::Debug" : 0, "Plack::Middleware::Session" : 0, "Plack::Session::Store::Cache" : 0, "Try::Tiny" : 0, "URI" : 0, "URI::Escape" : 0, "YAML::XS" : 0 } }, "test" : { "requires" : { "Test::Class::Most" : 0, "Test::WWW::Mechanize::PSGI" : 0 } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "mailto" : "bug-poet@rt.cpan.org", "web" : "http://rt.cpan.org/NoAuth/Bugs.html?Dist=Poet" }, "repository" : { "type" : "git", "url" : "git://github.com/jonswar/perl-poet.git", "web" : "https://github.com/jonswar/perl-poet" } }, "version" : "0.13" } Poet-0.13/META.yml0000644000076500000240000000273112034257703013040 0ustar swartzstaff--- abstract: 'The Mason web framework' author: - 'Jonathan Swartz ' build_requires: Test::Class::Most: 0 Test::WWW::Mechanize::PSGI: 0 configure_requires: ExtUtils::MakeMaker: 6.30 File::ShareDir::Install: 0.03 dynamic_config: 0 generated_by: 'Dist::Zilla version 4.200008, CPAN::Meta::Converter version 2.113640' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: Poet no_index: directory: - lib/Poet/t - lib/Poet/Test - lib/Poet/App.pm - lib/Poet/App - lib/Poet/Mason/Plugin.pm - lib/Poet/Mason/Plugin - lib/Poet/Types.pm - lib/Poet/Util.pm recommends: Log::Any::Adapter::Log4perl: 0 Log::Log4perl: 0 requires: CHI: 0.52 Capture::Tiny: 0 Data::Rmap: 0.6 File::ShareDir: 0 File::ShareDir::Install: 0 File::Slurp: 0 File::Spec::Functions: 0 Guard: 0 JSON::XS: 0 Log::Any::Adapter: 0 Mason: 2.19 Mason::Plugin::Cache: 0.04 Mason::Plugin::HTMLFilters: 0 Mason::Plugin::RouterSimple: 0.05 MasonX::ProcessDir: 0.02 Method::Signatures::Simple: 1.02 Moose: 1.15 MooseX::App::Cmd: 0 MooseX::HasDefaults::RO: 0 MooseX::StrictConstructor: 0 Plack: 0 Plack::Middleware::Debug: 0 Plack::Middleware::Session: 0 Plack::Session::Store::Cache: 0 Try::Tiny: 0 URI: 0 URI::Escape: 0 YAML::XS: 0 resources: bugtracker: http://rt.cpan.org/NoAuth/Bugs.html?Dist=Poet repository: git://github.com/jonswar/perl-poet.git version: 0.13 Poet-0.13/perlcriticrc0000644000076500000240000000036112034257703014174 0ustar swartzstaffonly = 1 severity = 1 verbose = %m at %f line %l [%p]\n [Moose::RequireMakeImmutable] [TestingAndDebugging::RequireUseStrict] equivalent_modules = Test::Class::Most Poet::Moose Mason::PluginRole [Variables::ProhibitConditionalDeclarations] Poet-0.13/README0000664000076500000240000000516712034257703012457 0ustar swartzstaffNAME Poet -- a modern Perl web framework for Mason developers SYNOPSIS % poet new MyApp my_app/.poet_root my_app/bin/app.psgi ... % my_app/bin/run.pl Running plackup --Reload ... --env development --port 5000 Watching ... for file updates. HTTP::Server::PSGI: Accepting connections at http://0:5000/ DESCRIPTION Poet is a modern Perl web framework designed especially for Mason developers. It uses PSGI/Plack for server integration, Mason for request routing and templating, and a selection of best-of-breed CPAN modules for caching, logging and configuration. FEATURES * A common-sense directory hierarchy for web development * A configuration system that scales elegantly with multiple coders and multiple layers (development/production) * Integration with Log4perl for logging, wrapped with dead-simple configuration * Integration with CHI for powerful and flexible caching * The power of Mason, an object-oriented templating system, for request routing and content generation * Easy access to common objects and utilities from anywhere in your application * Conventions and defaults based on the author's best practices from over fifteen years of Perl web development; and * The freedom to override just about any of Poet's behaviors DOCUMENTATION All documentation is indexed at Poet::Manual. SUPPORT For now Poet will share a mailing list and IRC with Mason. The Mason mailing list is `mason-users@lists.sourceforge.net'; you must be subscribed to send a message. The Mason IRC channel is #mason. Bugs and feature requests will be tracked at RT: http://rt.cpan.org/NoAuth/Bugs.html?Dist=Poet bug-poet@rt.cpan.org The latest source code can be browsed and fetched at: http://github.com/jonswar/perl-poet git clone git://github.com/jonswar/perl-poet.git ACKNOWLEDGEMENTS Poet was originally designed and developed for the Digital Media group of the Hearst Corporation, a diversified media company based in New York City. Many thanks to Hearst for agreeing to this open source release. However, Hearst has no direct involvement with this open source release and bears no responsibility for its support or maintenance. SEE ALSO Mason, Plack, PSGI AUTHOR Jonathan Swartz COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Jonathan Swartz. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Poet-0.13/share/0000775000076500000240000000000012034257703012670 5ustar swartzstaffPoet-0.13/share/generate.skel/0000775000076500000240000000000012034257703015417 5ustar swartzstaffPoet-0.13/share/generate.skel/bin/0000775000076500000240000000000012034257703016167 5ustar swartzstaffPoet-0.13/share/generate.skel/bin/app.psgi0000644000076500000240000000156712034257703017642 0ustar swartzstaffuse Poet::Script qw($conf $poet); use Plack::Builder; use Plack::Session::Store::Cache; use strict; use warnings; # Load modules configured in server.load_modules # $poet->app_class('Server')->load_startup_modules(); builder { # Add Plack middleware here # if ( $conf->is_development ) { enable "StackTrace"; enable "Debug"; } enable "ErrorDocument", map { $_ => $poet->static_path("errors/$_.html") } qw(401 403 404 500); if ( $conf->is_live ) { enable "HTTPExceptions", rethrow => 0; } enable "Static", path => qr{^/static/}, root => $poet->root_dir; enable "Session", store => Plack::Session::Store::Cache->new( cache => $poet->app_class('Cache')->new( namespace => 'session' ) ); sub { my $psgi_env = shift; $poet->app_class('Mason')->handle_psgi($psgi_env); }; }; Poet-0.13/share/generate.skel/bin/get.pl0000644000076500000240000000212012034257703017274 0ustar swartzstaff#!<% Poet::Tools::perl_executable %> use Poet::Script qw($poet); use Poet::Mechanize; use warnings; use strict; my $url = shift(@ARGV) or die "usage: $0 url"; my $mech = Poet::Mechanize->new(); $mech->get($url); if ( $mech->success ) { print $mech->content; } else { printf( "error getting '%s': %d\n%s", $url, $mech->status, $mech->content ? $mech->content . "\n" : '' ); } __END__ =pod =head1 NAME get.pl - Get a URL via command line without a running server =head1 SYNOPSIS get.pl url =head1 DESCRIPTION Runs a request through your Poet application in a single process without actually requiring a running server. The request will use the same psgi.app and pass through all the same middleware, etc. Uses L. The url scheme and host are optional, so either of these will work: get.pl /action get.pl http://localhost/action Because the request runs in a single process, it's easy to run through a debugger: perl -d get.pl /action or profiler: perl -d:NYTProf get.pl /action nytprofhtml =cut Poet-0.13/share/generate.skel/bin/run.pl0000755000076500000240000000073412034257703017335 0ustar swartzstaff#!<% Poet::Tools::perl_executable %> # # Runs plackup with appropriate options # use Poet::Script qw($conf $poet); use IPC::System::Simple qw(run); use strict; use warnings; my $app_psgi = $poet->bin_path("app.psgi"); my $server = $poet->app_class('Server'); # Get plackup options based on config (e.g. server.port) and layer # my @options = $server->get_plackup_options(); my @cmd = ("plackup", @options, $app_psgi); print "Running " . join(", ", @cmd) . "\n"; run(@cmd); Poet-0.13/share/generate.skel/bin/tmp/0000775000076500000240000000000012034257703016767 5ustar swartzstaffPoet-0.13/share/generate.skel/bin/tmp/EMPTY0000644000076500000240000000000012034257703017574 0ustar swartzstaffPoet-0.13/share/generate.skel/comps/0000775000076500000240000000000012034257703016540 5ustar swartzstaffPoet-0.13/share/generate.skel/comps/Base.mc0000644000076500000240000000045512034257703017735 0ustar swartzstaff<%text> <%class> has 'title' => (default => 'My site'); <%augment wrap> % $.Defer {{ <% $.title %> % }} <% inner() %> Poet-0.13/share/generate.skel/comps/index.mc0000644000076500000240000000227412034257703020173 0ustar swartzstaff<%text> <%class>

Welcome to Poet!


This is the default home page generated by comps/index.mc.

Site information

Environment root<% $poet->root_dir %>
App name<% $poet->app_name %>
Layer<% $conf->layer %>
Port<% $conf->get('server.port') %>

Where things go

Scriptsbin/
Configuration settingsconf/*.cfg
Mason componentscomps/
Perl libraries (*.pm)lib/<% $poet->app_name %>
Static files (css, images, and javascript)static/

Documentation

<%init> $.title('Welcome to Poet'); Poet-0.13/share/generate.skel/comps/tmp/0000775000076500000240000000000012034257703017340 5ustar swartzstaffPoet-0.13/share/generate.skel/comps/tmp/EMPTY0000644000076500000240000000000012034257703020145 0ustar swartzstaffPoet-0.13/share/generate.skel/conf/0000775000076500000240000000000012034257703016344 5ustar swartzstaffPoet-0.13/share/generate.skel/conf/dynamic/0000775000076500000240000000000012034257703017770 5ustar swartzstaffPoet-0.13/share/generate.skel/conf/dynamic/gen.pl0000644000076500000240000000037612034257703021102 0ustar swartzstaff#!<% Poet::Tools::perl_executable %> # # Processes the source files in this directory via MasonX::ProcessDir # and generates destination files in data/conf/dynamic. # use Poet::Script qw($conf); use strict; use warnings; $conf->generate_dynamic_conf(); Poet-0.13/share/generate.skel/conf/global.cfg0000644000076500000240000000171012034257703020262 0ustar swartzstaff# Contains configuration that applies to all environments. # Examples of built-in Poet configuration options below; # see Poet::Manual::Configuration for the full list. # SERVER # # These modules will be loaded on server startup. # #server.load_modules: # - DateTime # - DBI # - MyApp::Foo # LOGGING # # This is the default log configuration. See Poet::Log. # #log: # defaults: # level: info # output: poet.log # layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n" # CACHING # # This is the default cache configuration. See Poet::Cache. # #cache: # defaults: # driver: File # root_dir: $root/data/cache # # Use memcached instead of files. # #cache: # defaults: # driver: Memcached # servers: ["10.0.0.15:11211", "10.0.0.15:11212"] # MASON # # Add Mason plugins or override Mason options here. See Poet::Mason. # #mason: # extra_plugins: # - TidyObjectFiles # - +My::Mason::Plugin # data_dir: /other/data/dir Poet-0.13/share/generate.skel/conf/layer/0000775000076500000240000000000012034257703017460 5ustar swartzstaffPoet-0.13/share/generate.skel/conf/layer/development.cfg0000644000076500000240000000007312034257703022461 0ustar swartzstaff# Contains configuration specific to the development layer.Poet-0.13/share/generate.skel/conf/layer/production.cfg0000644000076500000240000000007212034257703022324 0ustar swartzstaff# Contains configuration specific to the production layer.Poet-0.13/share/generate.skel/conf/local.cfg0000644000076500000240000000022312034257703020112 0ustar swartzstaff# Contains configuration local to this environment. # This file should not be checked into version control. layer: development server.port: 5000 Poet-0.13/share/generate.skel/data/0000775000076500000240000000000012034257703016330 5ustar swartzstaffPoet-0.13/share/generate.skel/data/EMPTY0000644000076500000240000000000012034257703017135 0ustar swartzstaffPoet-0.13/share/generate.skel/db/0000775000076500000240000000000012034257703016004 5ustar swartzstaffPoet-0.13/share/generate.skel/db/EMPTY0000644000076500000240000000000012034257703016611 0ustar swartzstaffPoet-0.13/share/generate.skel/DOT_poet_root0000644000076500000240000000011412034257703020054 0ustar swartzstaff# Marks the Poet environment root. Do not delete. app_name: <% $app_name %> Poet-0.13/share/generate.skel/lib/0000775000076500000240000000000012034257703016165 5ustar swartzstaffPoet-0.13/share/generate.skel/lib/MyApp/0000775000076500000240000000000012034257703017213 5ustar swartzstaffPoet-0.13/share/generate.skel/lib/MyApp/Conf.pm0000644000076500000240000000047012034257703020435 0ustar swartzstaffpackage <% $app_name %>::Conf; use Poet::Moose; extends 'Poet::Conf'; # Add customizations to Poet::Conf here. # # e.g. Use INI instead of YAML for config files # # use Config::INI; # override 'read_conf_file' => sub { # my ($self, $file) = @_; # return Config::INI::Reader->read_file($file); # }; 1; Poet-0.13/share/generate.skel/lib/MyApp/Log.pm0000644000076500000240000000060512034257703020271 0ustar swartzstaffpackage <% $app_name %>::Log; use Poet qw($conf $poet); use Poet::Moose; extends 'Poet::Log'; # Add customizations to Poet::Log here. # # e.g. Use Log::Dispatch instead of Log4perl # # use Log::Any::Adapter; # use Log::Dispatch; # # override 'initialize_logging' => sub { # my $log = Log::Dispatch->new( ... ); # Log::Any::Adapter->set('Dispatch', dispatcher => $log); # }; 1; Poet-0.13/share/generate.skel/lib/MyApp/Mason/0000775000076500000240000000000012034257703020270 5ustar swartzstaffPoet-0.13/share/generate.skel/lib/MyApp/Mason/Compilation.pm0000644000076500000240000000053712034257703023107 0ustar swartzstaffpackage <% $app_name %>::Mason::Compilation; use Poet qw($conf $poet); use Poet::Moose; extends 'Mason::Compilation'; # Add customizations to Mason::Compilation here. # # e.g. Add Perl code to the top of every compiled component # # override 'output_class_header' => sub { # return join("\n", super(), 'use Foo;', 'use Bar qw(baz);'); # }; 1; Poet-0.13/share/generate.skel/lib/MyApp/Mason/README0000644000076500000240000000011412034257703021142 0ustar swartzstaffPut your app-specific Mason subclasses here. See Mason::Manual::Subclasses. Poet-0.13/share/generate.skel/lib/MyApp/Mason/Request.pm0000644000076500000240000000062012034257703022252 0ustar swartzstaffpackage <% $app_name %>::Mason::Request; use Poet qw($conf $poet); use Poet::Moose; extends 'Mason::Request'; # Add customizations to Mason::Request here. # # e.g. Perform tasks before and after each Mason request # # override 'run' => sub { # my $self = shift; # # do_tasks_before_request(); # # my $result = super(); # # do_tasks_after_request(); # # return $result; # }; 1; Poet-0.13/share/generate.skel/lib/MyApp/Mason.pm0000644000076500000240000000064212034257703020626 0ustar swartzstaffpackage <% $app_name %>::Mason; use Poet qw($conf $poet); use Poet::Moose; extends 'Poet::Mason'; # Add customizations to Poet::Mason here. # # e.g. Add plugins # # override 'get_plugins' => sub { # return ( super(), 'LvalueAttributes', 'TidyObjectFiles' ); # } # You can also create Mason subclasses in <% $app_name %>/Mason, e.g. # <% $app_name %>::Mason::Request, and they will be autodetected by Mason. 1; Poet-0.13/share/generate.skel/lib/MyApp/README0000644000076500000240000000013012034257703020063 0ustar swartzstaffPut your app-specific classes, and Poet subclasses here. See Poet::Manual::Subclassing. Poet-0.13/share/generate.skel/logs/0000775000076500000240000000000012034257703016363 5ustar swartzstaffPoet-0.13/share/generate.skel/logs/EMPTY0000644000076500000240000000000012034257703017170 0ustar swartzstaffPoet-0.13/share/generate.skel/static/0000775000076500000240000000000012034257703016706 5ustar swartzstaffPoet-0.13/share/generate.skel/static/css/0000775000076500000240000000000012034257703017476 5ustar swartzstaffPoet-0.13/share/generate.skel/static/css/style.css0000644000076500000240000000044412034257703021350 0ustar swartzstaffbody { color: #000; font-family: "Lucida Grande","Lucida Sans Unicode",Arial,Verdana,sans-serif; font-size: 14px; background: #F7F7F7; } table td, table th { padding: 5px; height: 100%; font-size: 14px; } h3 { font-size: 18px; } li { padding-top: 5px; }Poet-0.13/share/generate.skel/static/errors/0000775000076500000240000000000012034257703020222 5ustar swartzstaffPoet-0.13/share/generate.skel/static/errors/401.html0000644000076500000240000000054212034257703021413 0ustar swartzstaff Authentication required

Authentication required

This server could not verify that you are authorized to access the URL. You either supplied the wrong credentials (e.g., bad password), or your browser doesn't understand how to supply the credentials required. Poet-0.13/share/generate.skel/static/errors/403.html0000644000076500000240000000022412034257703021412 0ustar swartzstaff Forbidden

Forbidden

You don't have permission to access this URL. Poet-0.13/share/generate.skel/static/errors/404.html0000644000076500000240000000022512034257703021414 0ustar swartzstaff Not found

Not found

The requested URL was not found on this server. Poet-0.13/share/generate.skel/static/errors/500.html0000644000076500000240000000031112034257703021405 0ustar swartzstaff Server error

Server error

The server encountered an internal error and was unable to complete your request. Poet-0.13/share/generate.skel/static/images/0000775000076500000240000000000012034257703020153 5ustar swartzstaffPoet-0.13/share/generate.skel/static/images/EMPTY0000644000076500000240000000000012034257703020760 0ustar swartzstaffPoet-0.13/share/generate.skel/static/js/0000775000076500000240000000000012034257703017322 5ustar swartzstaffPoet-0.13/share/generate.skel/static/js/EMPTY0000644000076500000240000000000012034257703020127 0ustar swartzstaffPoet-0.13/share/generate.skel/t/0000775000076500000240000000000012034257703015662 5ustar swartzstaffPoet-0.13/share/generate.skel/t/EMPTY0000644000076500000240000000000012034257703016467 0ustar swartzstaffPoet-0.13/t/0000775000076500000240000000000012034257703012031 5ustar swartzstaffPoet-0.13/t/App.t0000644000076500000240000000006412034257703012734 0ustar swartzstaff#!perl -w use Poet::t::App; Poet::t::App->runtests; Poet-0.13/t/author-NoLog4perl.t0000644000076500000240000000043312034257703015501 0ustar swartzstaff#!perl -w BEGIN { unless ($ENV{AUTHOR_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for testing by the author'); } } use Poet::t::NoLog4perl; use Module::Mask; my $mask = new Module::Mask('Log::Log4perl'); Poet::t::NoLog4perl->runtests; Poet-0.13/t/author-NoLogAnyAdapterLog4perl.t0000644000076500000240000000045112034257703020114 0ustar swartzstaff#!perl -w BEGIN { unless ($ENV{AUTHOR_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for testing by the author'); } } use Poet::t::NoLog4perl; use Module::Mask; my $mask = new Module::Mask('Log::Any::Adapter::Log4perl'); Poet::t::NoLog4perl->runtests; Poet-0.13/t/author-Run.t0000644000076500000240000000031512034257703014257 0ustar swartzstaff#!perl -w BEGIN { unless ($ENV{AUTHOR_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for testing by the author'); } } use Poet::t::Run; Poet::t::Run->runtests; Poet-0.13/t/Conf.t0000644000076500000240000000006612034257703013103 0ustar swartzstaff#!perl -w use Poet::t::Conf; Poet::t::Conf->runtests; Poet-0.13/t/Environment.t0000644000076500000240000000010412034257703014513 0ustar swartzstaff#!perl -w use Poet::t::Environment; Poet::t::Environment->runtests; Poet-0.13/t/Import.t0000644000076500000240000000007212034257703013465 0ustar swartzstaff#!perl -w use Poet::t::Import; Poet::t::Import->runtests; Poet-0.13/t/Log.t0000644000076500000240000000006412034257703012735 0ustar swartzstaff#!perl -w use Poet::t::Log; Poet::t::Log->runtests; Poet-0.13/t/PSGIHandler.t0000644000076500000240000000010412034257703014247 0ustar swartzstaff#!perl -w use Poet::t::PSGIHandler; Poet::t::PSGIHandler->runtests; Poet-0.13/t/Script.t0000644000076500000240000000007212034257703013457 0ustar swartzstaff#!perl -w use Poet::t::Script; Poet::t::Script->runtests; Poet-0.13/t/Subclassing.t0000644000076500000240000000010412034257703014464 0ustar swartzstaff#!perl -w use Poet::t::Subclassing; Poet::t::Subclassing->runtests; Poet-0.13/t/Util.t0000644000076500000240000000006612034257703013133 0ustar swartzstaff#!perl -w use Poet::t::Util; Poet::t::Util->runtests; Poet-0.13/TAGS0000644000076500000240000003054212034257703012251 0ustar swartzstaff docs/pieces.txt,483 directly to Mason components,26,796 We I discourage the use of Perl code inside Mason components,30,982 Remember that Mason components compile down to Moose classes,33,1094 forth between a Controller and View that are often quite coupled,41,1573 related code in modules instead of components,45,1773 environment,48,1993 environment to a different directory,58,2450 d just 122,4190 That way,193,5584 Line 2 of the script,263,7592 does a few things:things267,7653 lib/Pod/Weaver/Section/SeeAlsoPoet.pm,121 package Pod::Weaver::Section::SeeAlsoPoet;1,0 sub weave_section Pod::Weaver::Section::SeeAlsoPoet::weave_section8,134 lib/Poet/App/Command/new.pm,73 package Poet::App::Command::new;1,0 my $description description11,486 lib/Poet/App/Command/script.pm,40 package Poet::App::Command::script;1,0 lib/Poet/App/Command.pm,32 package Poet::App::Command;1,0 lib/Poet/App.pm,23 package Poet::App;1,0 lib/Poet/Cache.pm,25 package Poet::Cache;1,0 lib/Poet/Conf.pm,24 package Poet::Conf;1,0 lib/Poet/Environment/Generator.pm,42 package Poet::Environment::Generator;1,0 lib/Poet/Environment.pm,70 package Poet::Environment;1,0 my ($current_env)($current_env15,301 lib/Poet/Import.pm,26 package Poet::Import;1,0 lib/Poet/Log.pm,23 package Poet::Log;1,0 lib/Poet/Manual/Intro.pod,78 webserver. The C line does several things:things106,2499 lib/Poet/Manual/Subclassing.pod,510 use Poet::Moose;27,643 Poet will automatically detect,37,847 it needs a classname,39,1005 use Poet::Moose;49,1241 then your Mason subclasses will be autodetected as well,54,1298 package MyApp::Mason::Interp;56,1361 use Moose;57,1395 use Config::INI;73,1738 use Moose;74,1759 use Moose;85,2027 use Moose;101,2368 use Log::Any::Adapter;111,2621 use Log::Dispatch;112,2648 use Moose;113,2671 use Poet::Moose;125,2947 method provide_dbh 128,3001 lib/Poet/Manual/Tutorial.pod,1409 package Blog::DB;96,2852 use strict;98,2897 use warnings;99,2913 __PACKAGE__->use_private_registry;use_private_registry102,2960 package Blog::Article;114,3219 use Blog::DB;115,3246 use strict;116,3264 use warnings;117,3280 Basically this gives us129,3522 database 155,3963 my $value 168,4523 You can also import sets of utilities in the same way,170,4570 Given a URL,211,6033 decides the overall page layout,213,6162 comps/index.mc:mc223,6483 Three new pieces of syntax here:here281,8360 3 my $date_fmt 324,9681 this everywhere. And of course,389,12114 4 component containing a single C<< <%class> >> block,468,14763 page component,472,14949 On line 4 we define a C method to validate the POST parameters,474,15025 the article,475,15104 top-level page components;477,15247 rendering anything,479,15399 On lines 7 and 489,15853 use strict;566,18572 use warnings;567,18588 several things:things570,18683 use Blog::Article;604,19516 use strict;605,19539 use warnings;606,19555 my $days_to_keep 608,19578 my $min_date 609,19643 In line 613,19817 In line 615,19868 In line 7, we get C<$days_to_keep> from configuration,615,19868 lib/Poet/Mason/Plugin/Compilation.pm,46 package Poet::Mason::Plugin::Compilation;1,0 lib/Poet/Mason/Plugin/Request.pm,42 package Poet::Mason::Plugin::Request;1,0 lib/Poet/Mason/Plugin.pm,33 package Poet::Mason::Plugin;1,0 lib/Poet/Mason.pm,55 package Poet::Mason;1,0 my $instance;instance10,153 lib/Poet/Mechanize.pm,65 package Poet::Mechanize;1,0 sub new Poet::Mechanize::new6,107 lib/Poet/Moose.pm,70 package Poet::Moose;1,0 sub init_meta Poet::Moose::init_meta11,269 lib/Poet/Plack/Request.pm,34 package Poet::Plack::Request;1,0 lib/Poet/Plack/Response.pm,35 package Poet::Plack::Response;1,0 lib/Poet/Script.pm,26 package Poet::Script;1,0 lib/Poet/Server.pm,84 package Poet::Server;1,0 my $loaded_startup_modules;loaded_startup_modules39,899 lib/Poet/t/App.pm,92 package Poet::t::App;1,0 sub test_app_name_to_dir Poet::t::App::test_app_name_to_dir4,76 lib/Poet/t/Conf.pm,647 package Poet::t::Conf;1,0 my $conf_files conf_files8,182 my $expected_values expected_values17,545 sub test_global Poet::t::Conf::test_global28,685 sub test_duplicate Poet::t::Conf::test_duplicate38,998 sub test_set_local Poet::t::Conf::test_set_local51,1391 sub test_dot_notation Poet::t::Conf::test_dot_notation82,2466 sub test_types Poet::t::Conf::test_types147,4127 sub test_layer_required Poet::t::Conf::test_layer_required186,5437 sub test_interpolation Poet::t::Conf::test_interpolation195,5641 sub test_dynamic_conf Poet::t::Conf::test_dynamic_conf221,6315 sub test_get_secure Poet::t::Conf::test_get_secure240,6986 lib/Poet/t/Environment.pm,191 package Poet::t::Environment;1,0 sub test_environment Poet::t::Environment::test_environment7,192 sub test_dot_files_in_share_dir Poet::t::Environment::test_dot_files_in_share_dir29,984 lib/Poet/t/Import.pm,279 package Poet::t::Import;1,0 my ( $temp_env, $importer )( $temp_env, $importer 4,79 sub test_valid_vars Poet::t::Import::test_valid_vars11,209 sub test_import_vars Poet::t::Import::test_import_vars15,319 sub test_import_methods Poet::t::Import::test_import_methods26,662 lib/Poet/t/Log.pm,83 package Poet::t::Log;1,0 sub test_log_config Poet::t::Log::test_log_config9,190 lib/Poet/t/NoLog4perl.pm,101 package Poet::t::NoLog4perl;3,73 sub test_no_log4perl Poet::t::NoLog4perl::test_no_log4perl10,231 lib/Poet/t/PSGIHandler.pm,796 package Poet::t::PSGIHandler;1,0 my $env env9,192 sub mech Poet::t::PSGIHandler::mech22,536 sub add_comp Poet::t::PSGIHandler::add_comp29,684 sub try_psgi_comp Poet::t::PSGIHandler::try_psgi_comp38,951 sub test_get_pl Poet::t::PSGIHandler::test_get_pl79,2205 sub test_basic Poet::t::PSGIHandler::test_basic90,2540 sub test_error Poet::t::PSGIHandler::test_error99,2761 sub test_not_found Poet::t::PSGIHandler::test_not_found109,2998 sub test_args Poet::t::PSGIHandler::test_args117,3210 sub test_abort Poet::t::PSGIHandler::test_abort149,3760 sub test_import Poet::t::PSGIHandler::test_import184,4607 sub test_visit Poet::t::PSGIHandler::test_visit208,5065 sub test_cache Poet::t::PSGIHandler::test_cache243,5640 sub test_misc Poet::t::PSGIHandler::test_misc260,6006 lib/Poet/t/Run.pm,126 package Poet::t::Run;1,0 sub test_run Poet::t::Run::test_run8,160 sub is_port_active Poet::t::Run::is_port_active37,1106 lib/Poet/t/Script.pm,126 package Poet::t::Script;1,0 my $script_template;script_template10,245 sub test_script Poet::t::Script::test_script12,267 lib/Poet/t/Util.pm,95 package Poet::t::Util;1,0 my $env env7,178 sub test_debug Poet::t::Util::test_debug10,263 lib/Poet/TAGS,2286 App/Command/new.pm,pm2,2 package Poet::App::Command::new;3,24 my $description description11description14,61 App/Command/script.pm,pm6,99 package Poet::App::Command::script;7,124 App/Command.pm,pm9,166 package Poet::App::Command;10,184 App.pm,pm12,218 package Poet::App;13,228 Cache.pm,pm15,253 package Poet::Cache;16,265 Conf.pm,pm18,292 package Poet::Conf;19,303 Environment/Generator.pm,pm21,329 package Poet::Environment::Generator;22,357 Environment.pm,pm24,401 package Poet::Environment;25,419 package Poet::Log;32,542 use Poet::Moose;38,694 use Poet::Moose;41,791 package MyApp::Mason::Interp;43,885 use Moose;44,927 use Config::INI;45,950 use Moose;46,979 use Moose;47,1002 use Moose;48,1025 use Log::Any::Adapter;49,1049 use Log::Dispatch;50,1085 use Moose;51,1117 use Poet::Moose;52,1141 package Blog::DB;56,1231 use strict;57,1261 use warnings;58,1285 __PACKAGE__->use_private_registry;use_private_registry59,1311 package Blog::Article;60,1380 use Blog::DB;61,1416 use strict;62,1443 use warnings;63,1468 everywhere. And of course,72,1763 Mason has a few built-in filters, and others are provided in plugins;85,2325 use strict;86,2405 use warnings;87,2431 use Blog::Article::Manager;89,2492 use strict;90,2534 use warnings;91,2560 package Poet::Mason::Plugin::Compilation;99,2784 package Poet::Mason::Plugin::Request;102,2859 package Poet::Mason::Plugin;105,2922 package Poet::Mason;108,2969 my $instance;109,2994 package Poet::Moose;112,3038 package Poet::Plack::Request;116,3130 package Poet::Plack::Response;119,3187 package Poet::Script;122,3237 package Poet::Server;125,3278 package Poet::t::App;128,3318 package Poet::t::Conf;132,3427 package Poet::t::Environment;143,3905 package Poet::t::Import;147,4025 package Poet::t::PSGIHandler;158,4426 package Poet::t::Run;174,5182 package Poet::t::Script;179,5327 my $script_template;180,5356 package Poet::t::Util;184,5468 package Poet::Test::Util;189,5583 package Poet::Tools;197,5929 package Poet::Types;214,6591 package Poet::Util::Debug;217,6636 my $console_log;218,6667 package Poet::Util::File;223,6847 package Poet::Util::Web;226,6895 lib/Poet/Test/Class.pm,31 package Poet::Test::Class;1,0 lib/Poet/Tools.pm,706 package Poet::Tools;3,24 my $Fetch_Flags Fetch_Flags19,399 my $Store_Flags Store_Flags20,447 my $File_Spec_Using_Unix File_Spec_Using_Unix21,505 sub can_load Poet::Tools::can_load23,576 sub catdir Poet::Tools::catdir46,1057 sub catfile Poet::Tools::catfile52,1173 sub checksum Poet::Tools::checksum58,1291 sub find_wanted Poet::Tools::find_wanted72,1564 sub perl_executable Poet::Tools::perl_executable83,1773 sub read_file Poet::Tools::read_file95,2036 sub tempdir_simple Poet::Tools::tempdir_simple120,2694 sub trim Poet::Tools::trim126,2804 sub uniq Poet::Tools::uniq135,2953 sub taint_is_on Poet::Tools::taint_is_on140,3022 sub write_file Poet::Tools::write_file144,3073 lib/Poet/Types.pm,25 package Poet::Types;1,0 lib/Poet/Util/Debug.pm,193 package Poet::Util::Debug;1,0 my $console_log;console_log12,262 sub _dump_value_with_caller Poet::Util::Debug::_dump_value_with_caller14,280 sub _define Poet::Util::Debug::_define25,603 lib/Poet/Util/File.pm,30 package Poet::Util::File;1,0 lib/Poet/Util/Web.pm,283 package Poet::Util::Web;1,0 my %html_escape html_escape12,270 my $html_escape html_escape14,357 my %js_escape js_escape17,430 sub html_escape Poet::Util::Web::html_escape34,865 sub js_escape Poet::Util::Web::js_escape40,975 sub make_uri Poet::Util::Web::make_uri47,1124 lib/Poet.pm,18 package Poet;1,0 share/generate.skel/bin/app.psgi,100 use Plack::Builder;2,33 use Plack::Session::Store::Cache;3,53 use strict;4,87 use warnings;5,99 share/generate.skel/bin/get.pl,40 my $url url7,112 my $mech mech8,159 share/generate.skel/bin/run.pl,104 my $app_psgi app_psgi10,174 my $server server11,217 my @options options15,327 my @cmd cmd17,374 share/generate.skel/comps/Base.mc,23