Back to documentation
package Statocles;
our $VERSION = '0.094';
# ABSTRACT: A static site generator
use Statocles::Base 'Class';
use Scalar::Util qw( blessed );
use Getopt::Long qw( GetOptionsFromArray :config pass_through bundling no_auto_abbrev );
use Pod::Usage::Return qw( pod2usage );
use Beam::Wire;
use Mojo::Loader qw( load_class );
my @VERBOSE = ( "warn", "info", "debug", "trace" );
=attr site
The L<site|Statocles::Site> we're working with.
=cut
has site => (
is => 'ro',
isa => InstanceOf['Statocles::Site'],
);
=attr log
A L<Mojo::Log> object for logging. Defaults to the current site's C<log> attribute.
=cut
has log => (
is => 'rw',
lazy => 1,
default => sub {
$_[0]->site->log;
},
);
=method run
my $exitval = $cmd->run( @argv );
Run the command given in @argv. See L<statocles> for a list of commands and
options.
=cut
sub run {
my ( $class, @argv ) = @_;
my %opt = (
config => 'site.yml',
site => 'site',
verbose => 0,
);
GetOptionsFromArray( \@argv, \%opt,
'config:s',
'site:s',
'help|h',
'version',
'verbose|v+',
);
return pod2usage(0) if $opt{help};
if ( $opt{version} || ( $opt{verbose} && !@argv ) ) {
say "Statocles version $Statocles::VERSION (Perl $^V)";
return 0;
}
my $method = shift @argv;
return pod2usage("ERROR: Missing command") unless $method;
# Create site does not require a config file
if ( $method eq 'create' ) {
require Statocles::Command::create;
return Statocles::Command::create->new->run( @argv );
}
my ( $exit, $site ) = _load_site( %opt );
if ( $exit ) {
return $exit;
}
if ( $opt{verbose} ) {
$site->log->handle( \*STDOUT );
$site->log->level( $VERBOSE[ $opt{verbose} ] );
}
my $cmd_class = 'Statocles::Command::' . $method;
my $error = load_class( $cmd_class );
if ( $error ) {
if ( my $app = $site->apps->{ $method } ) {
if ( !$app->can( 'command' ) ) {
say STDERR sprintf 'ERROR: Application "%s" has no commands', $method;
return 1;
}
return $app->command( $method, @argv );
}
else {
return pod2usage("ERROR: Unknown command or app '$method'");
}
}
my $cmd = $cmd_class->new( site => $site );
return $cmd->run( @argv );
}
sub _load_site {
my ( %opt ) = @_;
if ( !-e $opt{config} ) {
warn sprintf qq{ERROR: Could not find config file "\%s"\n}, $opt{config};
return 1;
}
my $wire = eval { Beam::Wire->new( file => $opt{config} ) };
if ( $@ ) {
if ( blessed $@ && $@->isa( 'Beam::Wire::Exception::Config' ) ) {
my $remedy;
if ( $@ =~ /found character that cannot start any token/ || $@ =~ /YAML_PARSE_ERR_NONSPACE_INDENTATION/ ) {
$remedy = "Check that you are not using tabs for indentation. ";
}
elsif ( $@ =~ /did not find expected key/ || $@ =~ /YAML_PARSE_ERR_INCONSISTENT_INDENTATION/ ) {
$remedy = "Check your indentation. ";
}
elsif ( $@ =~ /Syck parser/ && $@ =~ /syntax error/ ) {
$remedy = "Check your indentation. ";
}
my $more_info = ( !$opt{verbose} ? qq{run with the "--verbose" option or } : "" )
. "check Statocles::Help::Error";
warn sprintf qq{ERROR: Could not load config file "%s". %sFor more information, %s.%s},
$opt{config},
$remedy,
$more_info,
( $opt{verbose} ? "\n\nRaw error: $@" : "" )
;
return 1;
}
die $@;
}
my $site = eval { $wire->get( $opt{site} ) };
if ( $@ ) {
if ( blessed $@ && $@->isa( 'Beam::Wire::Exception::NotFound' ) && $@->name eq $opt{site} ) {
warn sprintf qq{ERROR: Could not find site named "%s" in config file "%s"\n},
$opt{site}, $opt{config};
return 1;
}
warn sprintf qq{ERROR: Could not create site object "%s" in config file "%s": %s\n},
$opt{site}, $opt{config}, $@;
return 1;
}
return ( 0, $site );
}
# The currently-running site.
# I hate this, but I know of no better way to ensure that we always have access
# to a Mojo::Log object, while still being relatively useful, without having to
# wire up every single object with a log object.
our $SITE;
BEGIN {
package # Hide from PAUSE
site;
sub log { return $SITE->log }
}
1;
__END__
=head1 SYNOPSIS
# Create a new site
statocles create www.example.com
# Create a new blog post
export EDITOR=vim
statocles blog post
# Build the site
statocles build
# Test the site in a local web browser
statocles daemon
# Deploy the site
statocles deploy
=head1 DESCRIPTION
Statocles is an application for building static web pages from a set of plain
YAML and Markdown files. It is designed to make it as simple as possible to
develop rich web content using basic text-based tools.
=head2 FEATURES
=over
=item *
A simple format based on
L<Markdown|http://daringfireball.net/projects/markdown/> for editing site
content.
=item *
A command-line application for building, deploying, and editing the site.
=item *
A simple daemon to display a test site before it goes live.
=item *
A L<blogging application|Statocles::App::Blog#FEATURES> with
=over
=item *
RSS and Atom syndication feeds.
=item *
Tags to organize blog posts. Tags have their own custom feeds.
=item *
Crosspost links to direct users to a syndicated blog.
=item *
Post-dated blog posts to appear automatically when the date is passed.
=back
=item *
Customizable L<themes|Statocles::Theme> using L<the Mojolicious template
language|Mojo::Template#SYNTAX>.
=item *
A clean default theme using L<the Skeleton CSS library|http://getskeleton.com>.
=item *
SEO-friendly features such as L<sitemaps (sitemap.xml)|http://www.sitemaps.org>.
=item *
L<Automatic checking for broken links|Statocles::Plugin::LinkCheck>.
=item *
L<Syntax highlighting|Statocles::Plugin::Highlight> for code and configuration blocks.
=item *
Hooks to add L<your own plugins|Statocles::Plugin> and L<your own custom
applications|Statocles::App>.
=back
=head1 GETTING STARTED
To get started with Statocles, L<consult the Statocles::Help guides|Statocles::Help>.
=head1 SEE ALSO
For news and documentation, L<visit the Statocles website at
http://preaction.me/statocles|http://preaction.me/statocles>.