App-Dapper
view release on metacpan or search on metacpan
lib/App/Dapper.pm view on Meta::CPAN
package App::Dapper;
=head1 NAME
App::Dapper - A publishing tool for static websites.
=cut
use utf8;
use open ':std', ':encoding(UTF-8)';
use 5.8.0;
use strict;
use warnings FATAL => 'all';
use vars '$VERSION';
use Exporter qw(import);
use IO::Dir;
#use Template;
use Template::Alloy;
use Template::Constants qw( :debug );
use Text::MultiMarkdown 'markdown';
use Net::HTTPServer;
use YAML::PP qw/ Load Dump LoadFile DumpFile /;
use File::Spec::Functions qw/ canonpath /;
use File::Path qw(make_path);
use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
use DateTime;
use DateTime::Format::XSD;
use App::Dapper::Serve;
use App::Dapper::Init;
use App::Dapper::Utils;
use App::Dapper::Defaults;
use App::Dapper::Filters;
my $DEFAULT_PORT = 8000;
my $ID = 0;
=head1 VERSION
Version 0.21
=cut
our $VERSION = '0.21';
our @EXPORT = qw($VERSION);
=head1 SYNOPSIS
B<Dapper> allows you to transform simple text files into static websites. By installing the App::Dapper Perl module, an executable named C<dapper> will be available to you in your terminal window. You can use this executable in a number of ways:
# Initialize the current directory with a fresh skeleton of a site
$ dapper [-solc] init
# Build the site
$ dapper [-solc] build
# Serve the site locally at http://localhost:8000
$ dapper [-solc] serve
# Rebuild the site if anything (source, layout dirs; config file) changes
$ dapper [-solc] watch
# Get help on usage and switches
$ dapper -h
# Print the version
$ dapper -v
Additionally, B<Dapper> may be used as a perl module directly from a script. Examples:
use App::Dapper;
# Create a Dapper object
my $d = App::Dapper->new();
# Initialize a new website in the current directory
$d->init();
# Build the site
$d->build();
# Serve the site locally at http://localhost:8000
$d->serve();
=head1 DESCRIPTION
lib/App/Dapper.pm view on Meta::CPAN
=item B<-o>, B<--output>=I<output directory>
Specify the directory to place the output files in. If this command line option is not present, it defaults to "_output".
=item B<-l>, B<--layout>=I<layout directory>
Specify the directory containing source files to process. If this command line option is not present, it defaults to "_layout".
=item B<-h>
Get help on available commands and options.
=item B<-v>
Print the version and exit.
=back
=cut
=head1 METHODS
Dapper may be used directly from a script as well. The following methods are available:
=head2 new
Create a new B<Dapper> object. Example:
my $d = App::Dapper->new();
Alternatively, the source dir, output dir, layout dir, and configuration file
may be specified. Example:
my $d = App::Dapper->new("_source", "_output", "_layout", "_config.yml");
After creating a B<Dapper> object, the followin hash elements may be accessed:
use App::Dapper;
my $d = App::Dapper->new();
print "Source directory: $d->{source}\n";
print "Output directory: $d->{output}\n";
print "Layout directory: $d->{layout}\n";
print "Config file: $d->{config}\n";
print "Object instantiation time: $d->{site}->{time}\n";
=cut
sub new {
my $class = shift;
my $self = {
created=> 1,
source => shift,
output => shift,
layout => shift,
config => shift,
};
$self->{site} = App::Dapper::Defaults::get_defaults();
$self->{site}->{time} = DateTime->now( time_zone => DateTime::TimeZone->new( name => 'local' ) );
$self->{source} = "_source" unless defined($self->{source});
$self->{output} = "_output" unless defined($self->{output});
$self->{layout} = "_layout" unless defined($self->{layout});
$self->{config} = "_config.yml" unless defined($self->{config});
bless $self, $class;
return $self;
}
=head2 init
Initializes a new skeleton project in the current directory (of the calling script, and uses the defined source dir, output dir, layout dir, and config file. Example usage:
use App::Dapper;
my $d = App::Dapper->new();
$d->init();
=cut
sub init {
my ($self) = @_;
App::Dapper::Init::init(
$self->{source},
$self->{output},
$self->{layout},
$self->{config}
);
print "Project initialized.\n";
}
=head2 build
Build the site. Example:
use App::Dapper;
my $d = App::Dapper->new();
$d->build();
When the site is built, it is done in three steps:
1. Parse. In this step, the configuration file is read. In addition, all the source files in the source directory as well as the layout files in the layout directory are reach and stored in the site hash.
2. Transform. Combine source and layout files.
3. Render. Save output files to the output directory.
=cut
sub build {
my($self) = @_;
$ID = 0;
$self->parse();
$self->transform();
$self->render();
lib/App/Dapper.pm view on Meta::CPAN
if (defined $source_handle) {
# cycle through each element in the current directory
while(defined($directory_element = $source_handle->read)) {
# print "directory element:$source/$directory_element\n";
if(-d "$source_dir/$directory_element" and $directory_element ne "." and $directory_element ne "..") {
$self->walk("$source_dir/$directory_element", "$output_dir/$directory_element");
}
elsif(-f "$source_dir/$directory_element" and $directory_element ne "." and $directory_element ne "..") {
# Skip dot files
if ($directory_element =~ /^\./) { next; }
# Construct output file name, which is a combination
# of the stem of the source file and the extension of the template.
# Example:
# - Source: index.md
# - Template: layout.html
# - Destination: index.html
my $source = "$source_dir/$directory_element";
my $output = "$output_dir/$directory_element";
$self->build_inventory($source, $output);
}
}
undef $source_handle;
}
else {
die "error: could not get a handle on $source_dir/$directory_element";
}
#undef %b_ddm;
}
# sub build_inventory - Build the internal hash of files, configurations, and layouts.
sub build_inventory {
my ($self, $source_file_name, $destination_file_name) = @_;
my %page = ();
my $frontmatter;
print "Processing $source_file_name\n";
my $source_content = App::Dapper::Utils::read_file($source_file_name);
$source_content =~ /(---.*?)---(.*)/s;
$frontmatter = Load($1) or die "error parsing \"$source_file_name\": $!\n";
$page{content} = $2;
for my $key (keys %{$frontmatter}) {
$page{$key} = $frontmatter->{$key};
}
$page{slug} = App::Dapper::Utils::slugify($page{title});
my $date;
if (not $page{date}) {
$date = DateTime::Format::XSD->parse_datetime(App::Dapper::Utils::get_modified_time($source_file_name));
# print "Didn't find date for $source_file_name. Setting to file modified date of $date\n";
$page{date} = $date;
} else {
$date = DateTime::Format::XSD->parse_datetime($page{date});
}
$page{date} = $date;
$page{year} = $date->year();
$page{month} = $date->month();
$page{day} = $date->day();
$page{hour} = $date->hour();
$page{minute} = $date->minute();
$page{second} = $date->second();
# Insert zero to beginning if year, month, day,
# hour, minute or second is less than 10
$page{$_} < 10 and $page{$_} = '0' . $page{$_}
for ('year', 'month', 'day', 'hour', 'minute', 'second');
if(not $page{timezone}) {
$page{timezone} = DateTime::TimeZone->new( name => 'local' );
}
$page{url} = defined $page{urlpattern} ? $page{urlpattern} : $self->{site}->{urlpattern};
$page{url} =~ s/\:category/$page{categories}/g unless not defined $page{categories};
$page{url} =~ s/\:year/$page{year}/g unless not defined $page{year};
$page{url} =~ s/\:month/$page{month}/g unless not defined $page{month};
$page{url} =~ s/\:day/$page{day}/g unless not defined $page{day};
$page{url} =~ s/\:hour/$page{hour}/g unless not defined $page{hour};
$page{url} =~ s/\:minute/$page{minute}/g unless not defined $page{minute};
$page{url} =~ s/\:second/$page{second}/g unless not defined $page{second};
$page{url} =~ s/\:slug/$page{slug}/g unless not defined $page{slug};
$page{id} = ++$ID; #$page{url};
if (not defined $page{extension}) { $page{extension} = $self->{site}->{extension}; }
$page{source_file_extension} = App::Dapper::Utils::filter_extension($source_file_name);
$page{filename} = App::Dapper::Utils::filter_stem("$destination_file_name") . $page{extension};
if(defined $page{categories}) {
my $filename = $self->{site}->{output} . $page{url};
$filename =~ s/\/$/\/index.html/;
$page{filename} = canonpath $filename;
}
my ($volume, $dirname, $file) = File::Spec->splitpath( $page{filename} );
$page{dirname} = $dirname;
if ($page{source_file_extension} eq ".md") {
$page{content} = markdown($page{content});
# Save first paragraph of content as excerpt
$page{content} =~ /(<p>.*?<\/p>)/s;
$page{excerpt} = $1;
}
else {
print "Did not run markdown on $page{filename} since the extension was not .md\n";
}
# Remove leading spaces and newline
$page{content} =~ s/^[ \n]*//;
if ($page{categories}) {
push @{$self->{site}->{categories}->{$page{categories}}}, \%page;
# Keep the array sorted
@{$self->{site}->{categories}->{$page{categories}}} = sort { $a->{date} <=> $b->{date} } @{$self->{site}->{categories}->{$page{categories}}};
}
push @{$self->{site}->{pages}}, \%page;
}
# sub copy(sourcedir, outputdir) - Copies files and directories from <sourcedir> to <outputdir> as long as they do not made what is contained in $self->{site}->{ignore}.
sub copy {
my ($self, $dir, $output) = @_;
opendir(DIR, $dir) or die $!;
( run in 0.917 second using v1.01-cache-2.11-cpan-39bf76dae61 )