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 )