| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # ABSTRACT: Static website publishing framework | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | package HiD; | 
| 5 |  |  |  |  |  |  | our $AUTHORITY = 'cpan:GENEHACK'; | 
| 6 |  |  |  |  |  |  | $HiD::VERSION = '1.991'; | 
| 7 | 12 |  |  | 12 |  | 2696246 | use Moose; | 
|  | 12 |  |  |  |  | 2074963 |  | 
|  | 12 |  |  |  |  | 123 |  | 
| 8 | 12 |  |  | 12 |  | 78290 | use namespace::autoclean; | 
|  | 12 |  |  |  |  | 36539 |  | 
|  | 12 |  |  |  |  | 1372 |  | 
| 9 |  |  |  |  |  |  | # note: we also do 'with HiD::Role::DoesLogging', just later on because reasons. | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 12 |  |  | 12 |  | 963 | use 5.014; # strict, unicode_strings | 
|  | 12 |  |  |  |  | 48 |  | 
| 12 | 12 |  |  | 12 |  | 3092 | use utf8; | 
|  | 12 |  |  |  |  | 110 |  | 
|  | 12 |  |  |  |  | 60 |  | 
| 13 | 12 |  |  | 12 |  | 2774 | use autodie; | 
|  | 12 |  |  |  |  | 109209 |  | 
|  | 12 |  |  |  |  | 54 |  | 
| 14 | 12 |  |  | 12 |  | 68450 | use warnings; | 
|  | 12 |  |  |  |  | 27 |  | 
|  | 12 |  |  |  |  | 435 |  | 
| 15 | 12 |  |  | 12 |  | 69 | use warnings    qw/ FATAL  utf8     /; | 
|  | 12 |  |  |  |  | 23 |  | 
|  | 12 |  |  |  |  | 473 |  | 
| 16 | 12 |  |  | 12 |  | 3114 | use open        qw/ :std  :utf8     /; | 
|  | 12 |  |  |  |  | 8835 |  | 
|  | 12 |  |  |  |  | 68 |  | 
| 17 | 12 |  |  | 12 |  | 4045 | use charnames   qw/ :full           /; | 
|  | 12 |  |  |  |  | 215195 |  | 
|  | 12 |  |  |  |  | 92 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 12 |  |  | 12 |  | 2809 | use Class::Load        qw/ try_load_class /; | 
|  | 12 |  |  |  |  | 24 |  | 
|  | 12 |  |  |  |  | 701 |  | 
| 20 | 12 |  |  | 12 |  | 7032 | use DateTime; | 
|  | 12 |  |  |  |  | 4037464 |  | 
|  | 12 |  |  |  |  | 578 |  | 
| 21 | 12 |  |  | 12 |  | 4797 | use File::Find::Rule; | 
|  | 12 |  |  |  |  | 73526 |  | 
|  | 12 |  |  |  |  | 87 |  | 
| 22 | 12 |  |  | 12 |  | 1260 | use Path::Tiny; | 
|  | 12 |  |  |  |  | 8310 |  | 
|  | 12 |  |  |  |  | 604 |  | 
| 23 | 12 |  |  | 12 |  | 67 | use Try::Tiny; | 
|  | 12 |  |  |  |  | 21 |  | 
|  | 12 |  |  |  |  | 528 |  | 
| 24 | 12 |  |  | 12 |  | 1372 | use YAML::Tiny; | 
|  | 12 |  |  |  |  | 13873 |  | 
|  | 12 |  |  |  |  | 593 |  | 
| 25 |  |  |  |  |  |  |  | 
| 26 | 12 |  |  | 12 |  | 4107 | use HiD::File; | 
|  | 12 |  |  |  |  | 38 |  | 
|  | 12 |  |  |  |  | 569 |  | 
| 27 | 12 |  |  | 12 |  | 5928 | use HiD::Layout; | 
|  | 12 |  |  |  |  | 43 |  | 
|  | 12 |  |  |  |  | 452 |  | 
| 28 | 12 |  |  | 12 |  | 4345 | use HiD::Page; | 
|  | 12 |  |  |  |  | 44 |  | 
|  | 12 |  |  |  |  | 493 |  | 
| 29 | 12 |  |  | 12 |  | 5031 | use HiD::Pager; | 
|  | 12 |  |  |  |  | 5095 |  | 
|  | 12 |  |  |  |  | 491 |  | 
| 30 | 12 |  |  | 12 |  | 4659 | use HiD::Post; | 
|  | 12 |  |  |  |  | 47 |  | 
|  | 12 |  |  |  |  | 464 |  | 
| 31 | 12 |  |  | 12 |  | 88 | use HiD::Types; | 
|  | 12 |  |  |  |  | 24 |  | 
|  | 12 |  |  |  |  | 49343 |  | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | has categories => ( | 
| 35 |  |  |  |  |  |  | is      => 'ro' , | 
| 36 |  |  |  |  |  |  | isa     => 'Maybe[HashRef[ArrayRef[HiD::Post]]]' , | 
| 37 |  |  |  |  |  |  | lazy    => 1 , | 
| 38 |  |  |  |  |  |  | builder => '_build_categories' , | 
| 39 |  |  |  |  |  |  | ); | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | sub _build_categories { | 
| 42 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 43 |  |  |  |  |  |  |  | 
| 44 | 0 | 0 |  |  |  | 0 | return undef unless $self->posts; | 
| 45 |  |  |  |  |  |  |  | 
| 46 | 0 |  |  |  |  | 0 | my $categories_hash = {}; | 
| 47 | 0 |  |  |  |  | 0 | foreach my $post ( @{$self->posts} ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 48 | 0 |  |  |  |  | 0 | push @{ $categories_hash->{$_} }, $post for @{ $post->categories }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 49 |  |  |  |  |  |  | } | 
| 50 |  |  |  |  |  |  |  | 
| 51 | 0 |  |  |  |  | 0 | return $categories_hash; | 
| 52 |  |  |  |  |  |  | } | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | has cli_opts => ( | 
| 56 |  |  |  |  |  |  | is      => 'ro' , | 
| 57 |  |  |  |  |  |  | isa     => 'HashRef' , | 
| 58 |  |  |  |  |  |  | lazy    => 1 , | 
| 59 |  |  |  |  |  |  | default => sub {{}} , | 
| 60 |  |  |  |  |  |  | ); | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | has config => ( | 
| 64 |  |  |  |  |  |  | is      => 'ro' , | 
| 65 |  |  |  |  |  |  | isa     => 'HashRef' , | 
| 66 |  |  |  |  |  |  | traits  => [ 'Hash' ] , | 
| 67 |  |  |  |  |  |  | lazy    => 1 , | 
| 68 |  |  |  |  |  |  | builder => '_build_config' , | 
| 69 |  |  |  |  |  |  | handles => { get_config => 'get' } , | 
| 70 |  |  |  |  |  |  | ); | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | sub _build_config { | 
| 73 | 26 |  |  | 26 |  | 51 | my $self = shift; | 
| 74 |  |  |  |  |  |  |  | 
| 75 | 26 |  |  |  |  | 53 | my( $config , $config_loaded ); | 
| 76 |  |  |  |  |  |  |  | 
| 77 | 26 | 100 |  |  |  | 533 | if ( my $file = $self->config_file ) { | 
| 78 |  |  |  |  |  |  | try { | 
| 79 | 24 |  | 50 | 24 |  | 986 | $config = YAML::Tiny->read( $file )->[0] // {}; | 
| 80 | 19 | 50 |  |  |  | 10730 | ref $config eq 'HASH' or die $!; | 
| 81 | 19 |  |  |  |  | 60 | $config_loaded++; | 
| 82 | 24 |  |  |  |  | 208 | }; | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  |  | 
| 85 | 26 | 100 | 33 |  |  | 3864 | $config_loaded or $config = {} | 
| 86 |  |  |  |  |  |  | and warn( "Could not read configuration. Using defaults (and options).\n" ); | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | return { | 
| 89 | 26 |  |  |  |  | 659 | %{ $self->default_config } , | 
| 90 |  |  |  |  |  |  | %$config , | 
| 91 | 26 |  |  |  |  | 364 | %{ $self->cli_opts } , | 
|  | 26 |  |  |  |  | 542 |  | 
| 92 |  |  |  |  |  |  | }; | 
| 93 |  |  |  |  |  |  | } | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | # this is down here so it will see the 'get_config' delegation... | 
| 96 |  |  |  |  |  |  | with 'HiD::Role::DoesLogging'; | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | has config_file => ( | 
| 100 |  |  |  |  |  |  | is      => 'ro' , | 
| 101 |  |  |  |  |  |  | isa     => 'Str' , | 
| 102 |  |  |  |  |  |  | ); | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | has default_config => ( | 
| 106 |  |  |  |  |  |  | is       => 'ro' , | 
| 107 |  |  |  |  |  |  | isa      => 'HashRef' , | 
| 108 |  |  |  |  |  |  | traits   => [ 'Hash' ] , | 
| 109 |  |  |  |  |  |  | init_arg => undef , | 
| 110 |  |  |  |  |  |  | default  => sub{{ | 
| 111 |  |  |  |  |  |  | default_author => 'your name here!' , | 
| 112 |  |  |  |  |  |  | destination    => '_site'    , | 
| 113 |  |  |  |  |  |  | include_dir    => '_includes', | 
| 114 |  |  |  |  |  |  | layout_dir     => '_layouts' , | 
| 115 |  |  |  |  |  |  | plugin_dir     => '_plugins' , | 
| 116 |  |  |  |  |  |  | posts_dir      => '_posts' , | 
| 117 |  |  |  |  |  |  | drafts_dir     => '_drafts' , | 
| 118 |  |  |  |  |  |  | source         => '.' , | 
| 119 |  |  |  |  |  |  | }}, | 
| 120 |  |  |  |  |  |  | ); | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | has destination => ( | 
| 124 |  |  |  |  |  |  | is      => 'ro' , | 
| 125 |  |  |  |  |  |  | isa     => 'HiD_DirPath' , | 
| 126 |  |  |  |  |  |  | lazy    => 1 , | 
| 127 |  |  |  |  |  |  | default => sub { | 
| 128 |  |  |  |  |  |  | my $dest = shift->get_config( 'destination' ); | 
| 129 |  |  |  |  |  |  | path( $dest )->mkpath() unless -e -d $dest; | 
| 130 |  |  |  |  |  |  | return $dest; | 
| 131 |  |  |  |  |  |  | }, | 
| 132 |  |  |  |  |  |  | ); | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | has draft_post_file_regex => ( | 
| 136 |  |  |  |  |  |  | is      => 'ro' , | 
| 137 |  |  |  |  |  |  | isa     => 'RegexpRef' , | 
| 138 |  |  |  |  |  |  | default => sub { qr/^(?:.+?)\.(?:mk|mkd|mkdn|markdown|md|mmd|text|textile|html)$/ }, | 
| 139 |  |  |  |  |  |  | ); | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | has excerpt_separator => ( | 
| 143 |  |  |  |  |  |  | is  => 'ro' , | 
| 144 |  |  |  |  |  |  | isa => 'Str' , | 
| 145 |  |  |  |  |  |  | lazy => 1 , | 
| 146 |  |  |  |  |  |  | default => sub { shift->get_config( 'excerpt_separator' ) // "\n\n" } | 
| 147 |  |  |  |  |  |  | ); | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | has include_dir => ( | 
| 151 |  |  |  |  |  |  | is      => 'ro' , | 
| 152 |  |  |  |  |  |  | isa     => 'Maybe[HiD_DirPath]' , | 
| 153 |  |  |  |  |  |  | lazy    => 1 , | 
| 154 |  |  |  |  |  |  | default => sub { | 
| 155 |  |  |  |  |  |  | my $self = shift; | 
| 156 |  |  |  |  |  |  | my $dir  = $self->get_config( 'include_dir' ); | 
| 157 |  |  |  |  |  |  | ( -e -d '_includes' ) ? $dir : undef; | 
| 158 |  |  |  |  |  |  | }, | 
| 159 |  |  |  |  |  |  | ); | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | has inputs => ( | 
| 163 |  |  |  |  |  |  | is      => 'ro' , | 
| 164 |  |  |  |  |  |  | isa     => 'HashRef', | 
| 165 |  |  |  |  |  |  | default => sub {{}} , | 
| 166 |  |  |  |  |  |  | traits  => ['Hash'], | 
| 167 |  |  |  |  |  |  | handles => { | 
| 168 |  |  |  |  |  |  | add_input  => 'set' , | 
| 169 |  |  |  |  |  |  | seen_input => 'exists' , | 
| 170 |  |  |  |  |  |  | }, | 
| 171 |  |  |  |  |  |  | ); | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | has layout_dir => ( | 
| 175 |  |  |  |  |  |  | is      => 'ro' , | 
| 176 |  |  |  |  |  |  | isa     => 'HiD_DirPath' , | 
| 177 |  |  |  |  |  |  | lazy    => 1 , | 
| 178 |  |  |  |  |  |  | default => sub { shift->get_config( 'layout_dir' ) }, | 
| 179 |  |  |  |  |  |  | ); | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | has layouts => ( | 
| 183 |  |  |  |  |  |  | is      => 'ro' , | 
| 184 |  |  |  |  |  |  | isa     => 'HashRef[HiD::Layout]', | 
| 185 |  |  |  |  |  |  | lazy    => 1 , | 
| 186 |  |  |  |  |  |  | builder => '_build_layouts', | 
| 187 |  |  |  |  |  |  | traits  => ['Hash'] , | 
| 188 |  |  |  |  |  |  | handles => { | 
| 189 |  |  |  |  |  |  | get_layout_by_name => 'get' , | 
| 190 |  |  |  |  |  |  | }, | 
| 191 |  |  |  |  |  |  | ); | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | sub _build_layouts { | 
| 194 | 16 |  |  | 16 |  | 23 | my $self = shift; | 
| 195 |  |  |  |  |  |  |  | 
| 196 | 16 |  |  |  |  | 58 | $self->INFO( "Building layouts" ); | 
| 197 |  |  |  |  |  |  |  | 
| 198 | 16 |  |  |  |  | 561 | my @layout_files = File::Find::Rule->file | 
| 199 |  |  |  |  |  |  | ->in( $self->layout_dir ); | 
| 200 |  |  |  |  |  |  |  | 
| 201 | 16 |  |  |  |  | 9997 | my %layouts; | 
| 202 | 16 |  |  |  |  | 47 | foreach my $layout_file ( @layout_files ) { | 
| 203 | 31 |  |  |  |  | 902 | my $dir = $self->layout_dir; | 
| 204 |  |  |  |  |  |  |  | 
| 205 | 31 |  |  |  |  | 245 | my( $layout_name , $extension ) = $layout_file | 
| 206 |  |  |  |  |  |  | =~ m|^$dir/(.*)\.([^.]+)$|; | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 31 |  |  |  |  | 595 | $layouts{$layout_name} = HiD::Layout->new({ | 
| 209 |  |  |  |  |  |  | filename  => $layout_file, | 
| 210 |  |  |  |  |  |  | processor => $self->processor , | 
| 211 |  |  |  |  |  |  | }); | 
| 212 |  |  |  |  |  |  |  | 
| 213 | 30 |  |  |  |  | 775 | $self->add_input( $layout_file => 'layout' ); | 
| 214 |  |  |  |  |  |  |  | 
| 215 | 30 |  |  |  |  | 136 | $self->DEBUG( "* Added layout $layout_file" ); | 
| 216 |  |  |  |  |  |  | } | 
| 217 |  |  |  |  |  |  |  | 
| 218 | 15 |  |  |  |  | 256 | foreach my $layout_name ( keys %layouts ) { | 
| 219 | 30 |  |  |  |  | 597 | my $metadata = $layouts{$layout_name}->metadata; | 
| 220 |  |  |  |  |  |  |  | 
| 221 | 30 | 50 |  |  |  | 75 | if ( my $embedded_layout = $metadata->{layout} ) { | 
| 222 |  |  |  |  |  |  | die "FIXME embedded layout fail $embedded_layout" | 
| 223 | 0 | 0 |  |  |  | 0 | unless $layouts{$embedded_layout}; | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | $layouts{$layout_name}->set_layout( | 
| 226 | 0 |  |  |  |  | 0 | $layouts{$embedded_layout} | 
| 227 |  |  |  |  |  |  | ); | 
| 228 |  |  |  |  |  |  | } | 
| 229 |  |  |  |  |  |  | } | 
| 230 |  |  |  |  |  |  |  | 
| 231 | 15 |  |  |  |  | 262 | return \%layouts; | 
| 232 |  |  |  |  |  |  | } | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | has limit_posts => ( | 
| 236 |  |  |  |  |  |  | is     => 'ro' , | 
| 237 |  |  |  |  |  |  | isa    => 'HiD_PosInt' , | 
| 238 |  |  |  |  |  |  | ); | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | has objects => ( | 
| 242 |  |  |  |  |  |  | is      => 'ro' , | 
| 243 |  |  |  |  |  |  | isa     => 'ArrayRef[Object]' , | 
| 244 |  |  |  |  |  |  | traits  => [ 'Array' ] , | 
| 245 |  |  |  |  |  |  | default => sub {[]}, | 
| 246 |  |  |  |  |  |  | handles => { | 
| 247 |  |  |  |  |  |  | add_object  => 'push' , | 
| 248 |  |  |  |  |  |  | all_objects => 'elements' , | 
| 249 |  |  |  |  |  |  | }, | 
| 250 |  |  |  |  |  |  | ); | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | has page_file_regex => ( | 
| 254 |  |  |  |  |  |  | is      => 'ro' , | 
| 255 |  |  |  |  |  |  | isa     => 'RegexpRef', | 
| 256 |  |  |  |  |  |  | default => sub { qr/\.(mk|mkd|mkdn|markdown|mmd|textile|html|htm|xml|xhtml|xhtm|shtm|shtml|rss)$/ } , | 
| 257 |  |  |  |  |  |  | ); | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | has pages => ( | 
| 261 |  |  |  |  |  |  | is      => 'ro', | 
| 262 |  |  |  |  |  |  | isa     => 'Maybe[ArrayRef[HiD::Page]]', | 
| 263 |  |  |  |  |  |  | lazy    => 1 , | 
| 264 |  |  |  |  |  |  | builder => '_build_pages' , | 
| 265 |  |  |  |  |  |  | ); | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | sub _build_pages { | 
| 268 | 16 |  |  | 16 |  | 26 | my $self = shift; | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | # build posts before pages | 
| 271 | 16 |  |  |  |  | 301 | $self->posts; | 
| 272 |  |  |  |  |  |  |  | 
| 273 | 15 |  |  |  |  | 52 | $self->INFO( "Posts built." ); | 
| 274 |  |  |  |  |  |  |  | 
| 275 | 15 |  |  |  |  | 510 | my @potential_pages = File::Find::Rule->file-> | 
| 276 |  |  |  |  |  |  | name( $self->page_file_regex )->in( '.' ); | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 144 |  |  |  |  | 165 | my @pages = grep { $_ } map { | 
| 279 | 15 | 100 | 100 |  |  | 21375 | if ($self->seen_input( $_ ) or $_ =~ /^_/ ) { 0 } | 
|  | 144 |  |  |  |  | 3915 |  | 
|  | 109 |  |  |  |  | 187 |  | 
| 280 |  |  |  |  |  |  | else { | 
| 281 |  |  |  |  |  |  | try { | 
| 282 | 35 |  |  | 35 |  | 2150 | my $page = HiD::Page->new({ | 
| 283 |  |  |  |  |  |  | dest_dir       => $self->destination, | 
| 284 |  |  |  |  |  |  | hid            => $self , | 
| 285 |  |  |  |  |  |  | input_filename => $_ , | 
| 286 |  |  |  |  |  |  | layouts        => $self->layouts , | 
| 287 |  |  |  |  |  |  | }); | 
| 288 | 35 |  |  |  |  | 837 | $page->content; | 
| 289 | 35 |  |  |  |  | 820 | $self->add_input( $_ => 'page' ); | 
| 290 | 35 |  |  |  |  | 927 | $self->add_object( $page ); | 
| 291 | 35 |  |  |  |  | 92 | $page; | 
| 292 |  |  |  |  |  |  | } | 
| 293 | 35 |  |  | 0 |  | 237 | catch { 0 }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 294 |  |  |  |  |  |  | } | 
| 295 |  |  |  |  |  |  | } @potential_pages; | 
| 296 |  |  |  |  |  |  |  | 
| 297 | 15 |  |  |  |  | 284 | return \@pages; | 
| 298 |  |  |  |  |  |  | } | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | has plugin_dir => ( | 
| 302 |  |  |  |  |  |  | is      => 'ro', | 
| 303 |  |  |  |  |  |  | isa     => 'Maybe[HiD_DirPath]', | 
| 304 |  |  |  |  |  |  | lazy    => 1, | 
| 305 |  |  |  |  |  |  | default => sub { | 
| 306 |  |  |  |  |  |  | my $dir = shift->get_config('plugin_dir'); | 
| 307 |  |  |  |  |  |  | (-e -d $dir) ? $dir : undef; | 
| 308 |  |  |  |  |  |  | }, | 
| 309 |  |  |  |  |  |  | ); | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | has plugins => ( | 
| 313 |  |  |  |  |  |  | is      => 'ro' , | 
| 314 |  |  |  |  |  |  | isa     => 'ArrayRef[Pluginish]' , | 
| 315 |  |  |  |  |  |  | lazy    => 1 , | 
| 316 |  |  |  |  |  |  | builder => '_build_plugins' , | 
| 317 |  |  |  |  |  |  | ); | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | sub _build_plugins { | 
| 320 | 15 |  |  | 15 |  | 31 | my $self = shift; | 
| 321 |  |  |  |  |  |  |  | 
| 322 | 15 |  |  |  |  | 21 | my @loaded_plugins; | 
| 323 |  |  |  |  |  |  |  | 
| 324 | 15 | 50 |  |  |  | 288 | if ( my $plugin_list = $self->config->{plugins} ){ | 
| 325 | 0 | 0 |  |  |  | 0 | my @plugins = ( ref $plugin_list eq 'ARRAY' ) ? @$plugin_list | 
| 326 |  |  |  |  |  |  | : (split /\s+/ , $plugin_list ); | 
| 327 |  |  |  |  |  |  |  | 
| 328 | 0 |  |  |  |  | 0 | foreach ( @plugins ) { | 
| 329 |  |  |  |  |  |  |  | 
| 330 | 0 | 0 |  |  |  | 0 | my $plugin_name = ( /^\+/ ) ? $_ : "HiD::Generator::$_"; | 
| 331 | 0 |  |  |  |  | 0 | $self->INFO( "* Loading plugin $plugin_name" ); | 
| 332 | 0 | 0 |  |  |  | 0 | next unless _load_plugin_or_warn( $plugin_name ); | 
| 333 | 0 |  |  |  |  | 0 | push @loaded_plugins , $plugin_name->new; | 
| 334 |  |  |  |  |  |  | } | 
| 335 |  |  |  |  |  |  | } | 
| 336 |  |  |  |  |  |  |  | 
| 337 | 15 | 50 |  |  |  | 281 | if ( my $plugin_dir = $self->plugin_dir ) { | 
| 338 |  |  |  |  |  |  | # plugin modules in plugin_dir | 
| 339 | 0 |  |  |  |  | 0 | my @mods = File::Find::Rule->file-> | 
| 340 |  |  |  |  |  |  | name( '*.pm' )->in( $plugin_dir ); | 
| 341 |  |  |  |  |  |  |  | 
| 342 | 0 |  |  |  |  | 0 | push @INC , $plugin_dir; | 
| 343 |  |  |  |  |  |  |  | 
| 344 | 0 |  |  |  |  | 0 | foreach my $m ( @mods ) { | 
| 345 | 0 |  |  |  |  | 0 | $m =~ s|$plugin_dir/?||; | 
| 346 | 0 |  |  |  |  | 0 | $m =~ s|.pm$||; | 
| 347 | 0 |  |  |  |  | 0 | $self->INFO("* Loading plugin $m" ); | 
| 348 | 0 | 0 |  |  |  | 0 | next unless _load_plugin_or_warn( $m ); | 
| 349 | 0 |  |  |  |  | 0 | push @loaded_plugins, $m->new; | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  | } | 
| 352 |  |  |  |  |  |  |  | 
| 353 | 15 |  |  |  |  | 257 | return \@loaded_plugins; | 
| 354 |  |  |  |  |  |  | } | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | sub _load_plugin_or_warn { | 
| 357 | 0 |  |  | 0 |  | 0 | my $plugin = shift; | 
| 358 |  |  |  |  |  |  |  | 
| 359 | 0 |  |  |  |  | 0 | my( $lrlt , $lerr ) = try_load_class( $plugin ); | 
| 360 |  |  |  |  |  |  |  | 
| 361 | 0 | 0 | 0 |  |  | 0 | warn "plugin $plugin cannot be loaded : $lerr \n" and return undef | 
| 362 |  |  |  |  |  |  | unless $lrlt; | 
| 363 |  |  |  |  |  |  |  | 
| 364 | 0 | 0 | 0 |  |  | 0 | ( $plugin->isa('HiD::Plugin') or | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 365 |  |  |  |  |  |  | $plugin->does('HiD::Plugin') or | 
| 366 |  |  |  |  |  |  | $plugin->does('HiD::Generator') ) | 
| 367 |  |  |  |  |  |  | or warn "plugin $plugin is not a valid plugin.\n" | 
| 368 |  |  |  |  |  |  | and return undef; | 
| 369 |  |  |  |  |  |  |  | 
| 370 | 0 |  |  |  |  | 0 | return 1; | 
| 371 |  |  |  |  |  |  | } | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | has post_file_regex => ( | 
| 375 |  |  |  |  |  |  | is      => 'ro' , | 
| 376 |  |  |  |  |  |  | isa     => 'RegexpRef' , | 
| 377 |  |  |  |  |  |  | default => sub { qr/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}-(?:.+?)\.(?:mk|mkd|mkdn|markdown|md|mmd|text|textile|html)$/ }, | 
| 378 |  |  |  |  |  |  | ); | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | has posts_dir => ( | 
| 382 |  |  |  |  |  |  | is      => 'ro' , | 
| 383 |  |  |  |  |  |  | isa     => 'HiD_DirPath' , | 
| 384 |  |  |  |  |  |  | lazy    => 1 , | 
| 385 |  |  |  |  |  |  | default => sub { shift->get_config( 'posts_dir' ) }, | 
| 386 |  |  |  |  |  |  | ); | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | has posts => ( | 
| 390 |  |  |  |  |  |  | is      => 'ro' , | 
| 391 |  |  |  |  |  |  | isa     => 'ArrayRef[HiD_Post]' , | 
| 392 |  |  |  |  |  |  | traits  => [ qw/ Array / ] , | 
| 393 |  |  |  |  |  |  | handles => { posts_size => 'count' } , | 
| 394 |  |  |  |  |  |  | lazy    => 1 , | 
| 395 |  |  |  |  |  |  | builder => '_build_posts' , | 
| 396 |  |  |  |  |  |  | ); | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | sub _build_posts { | 
| 399 | 16 |  |  | 16 |  | 30 | my $self = shift; | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | # build layouts before posts | 
| 402 | 16 |  |  |  |  | 293 | $self->layouts; | 
| 403 |  |  |  |  |  |  |  | 
| 404 | 15 |  |  |  |  | 57 | $self->INFO("Layouts built."); | 
| 405 |  |  |  |  |  |  |  | 
| 406 | 15 |  |  |  |  | 206 | $self->INFO("Building posts." ); | 
| 407 |  |  |  |  |  |  |  | 
| 408 | 15 |  |  |  |  | 223 | my $rule = File::Find::Rule->new; | 
| 409 |  |  |  |  |  |  |  | 
| 410 | 15 |  |  |  |  | 173 | my @posts_directories = $rule->or( | 
| 411 |  |  |  |  |  |  | $rule->new->directory->name( $self->get_config( 'posts_dir' )) , | 
| 412 |  |  |  |  |  |  | $rule->new->directory->name( $self->get_config( 'destination' ))->prune->discard , | 
| 413 |  |  |  |  |  |  | )->in( $self->source ); | 
| 414 |  |  |  |  |  |  |  | 
| 415 | 15 |  |  |  |  | 18303 | my @potential_posts = File::Find::Rule->file | 
| 416 |  |  |  |  |  |  | ->name( $self->post_file_regex )->in( @posts_directories ); | 
| 417 |  |  |  |  |  |  |  | 
| 418 | 15 | 50 |  |  |  | 10208 | if ( $self->get_config( 'publish_drafts' )){ | 
| 419 | 0 |  |  |  |  | 0 | push @potential_posts , $self->_build_potential_draft_posts_list , | 
| 420 |  |  |  |  |  |  | } | 
| 421 |  |  |  |  |  |  |  | 
| 422 | 44 |  |  |  |  | 143 | my @posts = grep { $_ } map { | 
| 423 | 15 |  |  |  |  | 40 | try { | 
| 424 | 44 |  |  | 44 |  | 3422 | $self->DEBUG( "* Trying to build post $_" ); | 
| 425 | 44 |  |  |  |  | 1268 | my $post = HiD::Post->new({ | 
| 426 |  |  |  |  |  |  | dest_dir       => $self->destination, | 
| 427 |  |  |  |  |  |  | hid            => $self , | 
| 428 |  |  |  |  |  |  | input_filename => $_ , | 
| 429 |  |  |  |  |  |  | layouts        => $self->layouts , | 
| 430 |  |  |  |  |  |  | }); | 
| 431 | 43 |  |  |  |  | 962 | $self->add_input( $_ => 'post' ); | 
| 432 | 43 |  |  |  |  | 1022 | $self->add_object( $post ); | 
| 433 | 43 |  |  |  |  | 186 | $self->DEBUG( "* Built post $_" ); | 
| 434 | 43 |  |  |  |  | 640 | $post; | 
| 435 |  |  |  |  |  |  | } | 
| 436 | 44 |  |  | 1 |  | 732 | catch { $self->ERROR( "ERROR: Post failed to build: $_" ) ; return 0 }; | 
|  | 1 |  |  |  |  | 22 |  | 
|  | 1 |  |  |  |  | 315 |  | 
| 437 |  |  |  |  |  |  | } @potential_posts; | 
| 438 |  |  |  |  |  |  |  | 
| 439 | 15 |  |  |  |  | 48 | @posts = sort { $b->date <=> $a->date } @posts; | 
|  | 121 |  |  |  |  | 8833 |  | 
| 440 |  |  |  |  |  |  |  | 
| 441 | 15 | 50 |  |  |  | 647 | if ( my $limit = $self->limit_posts ) { | 
| 442 | 0 | 0 |  |  |  | 0 | die "--limit_posts must be positive" if $limit < 1; | 
| 443 | 0 |  |  |  |  | 0 | @posts = splice( @posts , -$limit , $limit ); | 
| 444 |  |  |  |  |  |  | } | 
| 445 |  |  |  |  |  |  |  | 
| 446 | 15 |  |  |  |  | 419 | return \@posts; | 
| 447 |  |  |  |  |  |  | } | 
| 448 |  |  |  |  |  |  |  | 
| 449 |  |  |  |  |  |  | sub _build_potential_draft_posts_list { | 
| 450 | 0 |  |  | 0 |  | 0 | my( $self ) = @_; | 
| 451 |  |  |  |  |  |  |  | 
| 452 | 0 |  |  |  |  | 0 | my $rule = File::Find::Rule->new; | 
| 453 |  |  |  |  |  |  |  | 
| 454 | 0 |  |  |  |  | 0 | my @posts_directories = $rule->or( | 
| 455 |  |  |  |  |  |  | $rule->new->directory->name( $self->get_config( 'drafts_dir' )) , | 
| 456 |  |  |  |  |  |  | $rule->new->directory->name( $self->get_config( 'destination' ))->prune->discard , | 
| 457 |  |  |  |  |  |  | )->in( $self->source ); | 
| 458 |  |  |  |  |  |  |  | 
| 459 | 0 |  |  |  |  | 0 | my @potential_posts = File::Find::Rule->file | 
| 460 |  |  |  |  |  |  | ->name( $self->draft_post_file_regex )->in( @posts_directories ); | 
| 461 |  |  |  |  |  |  |  | 
| 462 | 0 |  |  |  |  | 0 | return @potential_posts; | 
| 463 |  |  |  |  |  |  | } | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | has processor => ( | 
| 467 |  |  |  |  |  |  | is        => 'ro' , | 
| 468 |  |  |  |  |  |  | isa       => 'HiD::Processor' , | 
| 469 |  |  |  |  |  |  | lazy      => 1 , | 
| 470 |  |  |  |  |  |  | predicate => 'has_processor', | 
| 471 |  |  |  |  |  |  | default   => sub { | 
| 472 |  |  |  |  |  |  | my $self = shift; | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | my $processor_name  = $self->get_config( 'processor_name' ) // 'Handlebars'; | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | my $processor_class = ( $processor_name =~ /^\+/ ) ? $processor_name | 
| 477 |  |  |  |  |  |  | : "HiD::Processor::$processor_name"; | 
| 478 |  |  |  |  |  |  |  | 
| 479 |  |  |  |  |  |  | try_load_class( $processor_class ); | 
| 480 |  |  |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | return $processor_class->new( $self->processor_args ); | 
| 482 |  |  |  |  |  |  | }, | 
| 483 |  |  |  |  |  |  | ); | 
| 484 |  |  |  |  |  |  |  | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | has processor_args => ( | 
| 487 |  |  |  |  |  |  | is      => 'ro' , | 
| 488 |  |  |  |  |  |  | isa     => 'ArrayRef|HashRef' , | 
| 489 |  |  |  |  |  |  | lazy    => 1 , | 
| 490 |  |  |  |  |  |  | default => sub { | 
| 491 |  |  |  |  |  |  | my $self = shift; | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | my $processor_args = defined $self->get_config('processor_args') ? $self->get_config('processor_args') : {}; | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | if(ref $processor_args eq 'HASH' && !exists $processor_args->{path}) { | 
| 496 |  |  |  |  |  |  | my @path = ( $self->layout_dir ); | 
| 497 |  |  |  |  |  |  | push @path , $self->include_dir | 
| 498 |  |  |  |  |  |  | if defined $self->include_dir; | 
| 499 |  |  |  |  |  |  | $processor_args->{path} = \@path; | 
| 500 |  |  |  |  |  |  | } | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | return $processor_args; | 
| 503 |  |  |  |  |  |  | }, | 
| 504 |  |  |  |  |  |  | ); | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  |  | 
| 507 |  |  |  |  |  |  | has regular_files => ( | 
| 508 |  |  |  |  |  |  | is      => 'ro', | 
| 509 |  |  |  |  |  |  | isa     => 'Maybe[ArrayRef[HiD::File]]', | 
| 510 |  |  |  |  |  |  | lazy    => 1 , | 
| 511 |  |  |  |  |  |  | builder => '_build_regular_files' , | 
| 512 |  |  |  |  |  |  | ); | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | sub _build_regular_files { | 
| 515 | 16 |  |  | 16 |  | 37 | my $self = shift; | 
| 516 |  |  |  |  |  |  |  | 
| 517 |  |  |  |  |  |  | # build pages before regular files | 
| 518 | 16 |  |  |  |  | 306 | $self->pages; | 
| 519 |  |  |  |  |  |  |  | 
| 520 | 15 |  |  |  |  | 53 | $self->INFO( "Pages built" ); | 
| 521 |  |  |  |  |  |  |  | 
| 522 | 15 |  |  |  |  | 523 | my @potential_files = File::Find::Rule->file->in( '.' ); | 
| 523 |  |  |  |  |  |  |  | 
| 524 | 206 |  |  |  |  | 215 | my @files = grep { $_ } map { | 
| 525 | 15 | 100 | 100 |  |  | 20679 | if ($self->seen_input( $_ ) or $_ =~ /^_/ ) { 0 } | 
|  | 206 | 50 | 33 |  |  | 4757 |  | 
|  | 181 | 50 |  |  |  | 324 |  | 
| 526 | 0 |  |  |  |  | 0 | elsif( $_ =~ /^\.git/ ) { 0 } | 
| 527 | 0 |  |  |  |  | 0 | elsif( $_ =~ /^\.svn/ or $_ =~ /\/\.svn\// ) { 0 } | 
| 528 |  |  |  |  |  |  | else { | 
| 529 | 25 |  |  |  |  | 454 | my $file = HiD::File->new({ | 
| 530 |  |  |  |  |  |  | dest_dir       => $self->destination, | 
| 531 |  |  |  |  |  |  | input_filename => $_ , | 
| 532 |  |  |  |  |  |  | }); | 
| 533 | 25 |  |  |  |  | 644 | $self->add_input( $_ => 'file' ); | 
| 534 | 25 |  |  |  |  | 602 | $self->add_object( $file ); | 
| 535 | 25 |  |  |  |  | 50 | $file; | 
| 536 |  |  |  |  |  |  | } | 
| 537 |  |  |  |  |  |  | } @potential_files; | 
| 538 |  |  |  |  |  |  |  | 
| 539 | 15 |  |  |  |  | 293 | return \@files; | 
| 540 |  |  |  |  |  |  | } | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | has remove_unwritten_files => ( | 
| 544 |  |  |  |  |  |  | is => 'ro' , | 
| 545 |  |  |  |  |  |  | isa => 'Bool' , | 
| 546 |  |  |  |  |  |  | lazy => 1 , | 
| 547 |  |  |  |  |  |  | default => sub { | 
| 548 |  |  |  |  |  |  | my $self = shift; | 
| 549 |  |  |  |  |  |  | return $self->get_config('remove_unwritten_files') | 
| 550 |  |  |  |  |  |  | if defined $self->get_config('remove_unwritten_files'); | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | return 1; | 
| 553 |  |  |  |  |  |  | }, | 
| 554 |  |  |  |  |  |  | ); | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | has source => ( | 
| 558 |  |  |  |  |  |  | is      => 'ro' , | 
| 559 |  |  |  |  |  |  | isa     => 'HiD_DirPath' , | 
| 560 |  |  |  |  |  |  | lazy    => 1 , | 
| 561 |  |  |  |  |  |  | default => sub { | 
| 562 |  |  |  |  |  |  | my $self   = shift; | 
| 563 |  |  |  |  |  |  | my $source = $self->get_config( 'source' ); | 
| 564 |  |  |  |  |  |  | chdir $source or die $!; | 
| 565 |  |  |  |  |  |  | return $source; | 
| 566 |  |  |  |  |  |  | }, | 
| 567 |  |  |  |  |  |  | ); | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  |  | 
| 570 |  |  |  |  |  |  | has 'tags' => ( | 
| 571 |  |  |  |  |  |  | is      => 'ro', | 
| 572 |  |  |  |  |  |  | isa     => 'Maybe[HashRef[ArrayRef[HiD::Post]]]', | 
| 573 |  |  |  |  |  |  | lazy    => 1, | 
| 574 |  |  |  |  |  |  | builder => '_build_tags', | 
| 575 |  |  |  |  |  |  | ); | 
| 576 |  |  |  |  |  |  |  | 
| 577 |  |  |  |  |  |  | sub _build_tags { | 
| 578 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 579 |  |  |  |  |  |  |  | 
| 580 | 0 | 0 |  |  |  | 0 | return undef unless $self->posts; | 
| 581 |  |  |  |  |  |  |  | 
| 582 | 0 |  |  |  |  | 0 | my $tags_hash = {}; | 
| 583 | 0 |  |  |  |  | 0 | foreach my $post (@{$self->posts}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 584 | 0 |  |  |  |  | 0 | push @{$tags_hash->{$_}}, $post for @{$post->tags}; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 585 |  |  |  |  |  |  | } | 
| 586 | 0 |  |  |  |  | 0 | return $tags_hash; | 
| 587 |  |  |  |  |  |  | } | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  |  | 
| 590 |  |  |  |  |  |  | has time => ( | 
| 591 |  |  |  |  |  |  | is       => 'ro', | 
| 592 |  |  |  |  |  |  | isa      => 'DateTime' , | 
| 593 |  |  |  |  |  |  | init_arg => undef , | 
| 594 |  |  |  |  |  |  | default  => sub { DateTime->now() } , | 
| 595 |  |  |  |  |  |  | ); | 
| 596 |  |  |  |  |  |  |  | 
| 597 |  |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  |  | 
| 599 |  |  |  |  |  |  | has written_files => ( | 
| 600 |  |  |  |  |  |  | is      => 'ro' , | 
| 601 |  |  |  |  |  |  | isa     => 'HashRef' , | 
| 602 |  |  |  |  |  |  | traits  => [ qw/ Hash / ] , | 
| 603 |  |  |  |  |  |  | default => sub {{}}, | 
| 604 |  |  |  |  |  |  | handles => { | 
| 605 |  |  |  |  |  |  | add_written_file  => 'set' , | 
| 606 |  |  |  |  |  |  | all_written_files => 'keys' , | 
| 607 |  |  |  |  |  |  | wrote_file        => 'defined' , | 
| 608 |  |  |  |  |  |  | }, | 
| 609 |  |  |  |  |  |  | ); | 
| 610 |  |  |  |  |  |  |  | 
| 611 |  |  |  |  |  |  |  | 
| 612 |  |  |  |  |  |  | sub publish { | 
| 613 | 16 |  |  | 16 | 1 | 1174 | my( $self ) = @_; | 
| 614 |  |  |  |  |  |  |  | 
| 615 | 16 | 50 | 33 |  |  | 295 | if ( -e $self->destination && $self->get_config( 'clean_destination' )){ | 
| 616 | 0 |  |  |  |  | 0 | my $path = path( $self->destination ); | 
| 617 | 0 |  |  |  |  | 0 | $path->remove_tree(); | 
| 618 | 0 |  |  |  |  | 0 | $self->INFO( "cleaned destination directory" ); | 
| 619 | 0 |  |  |  |  | 0 | $path->mkpath(); | 
| 620 |  |  |  |  |  |  | } | 
| 621 |  |  |  |  |  |  |  | 
| 622 | 16 |  |  |  |  | 73 | $self->INFO( "publish" ); | 
| 623 |  |  |  |  |  |  |  | 
| 624 |  |  |  |  |  |  | # bootstrap data structures | 
| 625 |  |  |  |  |  |  | # FIXME should have a more explicit way to do this | 
| 626 | 16 |  |  |  |  | 615 | $self->regular_files; | 
| 627 |  |  |  |  |  |  |  | 
| 628 | 15 |  |  |  |  | 52 | $self->INFO( "files bootstrapped" ); | 
| 629 |  |  |  |  |  |  |  | 
| 630 | 15 |  |  |  |  | 438 | $self->add_written_file( $self->destination => '_site_dir' ); | 
| 631 |  |  |  |  |  |  |  | 
| 632 | 15 |  |  |  |  | 64 | $self->INFO( "processing plugins for generate()" ); | 
| 633 |  |  |  |  |  |  |  | 
| 634 | 15 |  |  |  |  | 186 | foreach my $plugin ( @{ $self->plugins } ) { | 
|  | 15 |  |  |  |  | 284 |  | 
| 635 | 0 | 0 |  |  |  | 0 | if ( $plugin->does( 'HiD::Generator' )) { | 
| 636 | 0 |  |  |  |  | 0 | $plugin->generate($self) | 
| 637 |  |  |  |  |  |  | } | 
| 638 |  |  |  |  |  |  | } | 
| 639 |  |  |  |  |  |  |  | 
| 640 | 15 | 50 |  |  |  | 258 | if ( $self->config->{pagination} ){ | 
| 641 |  |  |  |  |  |  | my $entries_per_page = $self->config->{pagination}{entries} | 
| 642 | 0 | 0 |  |  |  | 0 | or die "Must set 'pagination.entries' key in pagination config"; | 
| 643 |  |  |  |  |  |  |  | 
| 644 |  |  |  |  |  |  | my $page_fstring = $self->config->{pagination}{page} | 
| 645 | 0 | 0 |  |  |  | 0 | or die "Must set 'pagination.page' key in pagination config"; | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | my $template = $self->config->{pagination}{template} | 
| 648 | 0 | 0 |  |  |  | 0 | or die "Must set 'pagination.template' key in pagination config"; | 
| 649 |  |  |  |  |  |  |  | 
| 650 | 0 |  |  |  |  | 0 | my $pager = HiD::Pager->new({ | 
| 651 |  |  |  |  |  |  | entries             => $self->posts , | 
| 652 |  |  |  |  |  |  | entries_per_page    => $entries_per_page , | 
| 653 |  |  |  |  |  |  | hid                 => $self , | 
| 654 |  |  |  |  |  |  | page_pattern        => $page_fstring | 
| 655 |  |  |  |  |  |  | }); | 
| 656 |  |  |  |  |  |  |  | 
| 657 | 0 |  |  |  |  | 0 | while ( my $page_data = $pager->next ) { | 
| 658 |  |  |  |  |  |  | my $page = HiD::Page->new({ | 
| 659 |  |  |  |  |  |  | dest_dir       => $self->destination , | 
| 660 |  |  |  |  |  |  | hid            => $self , | 
| 661 |  |  |  |  |  |  | input_filename => $template , | 
| 662 |  |  |  |  |  |  | layouts        => $self->layouts , | 
| 663 |  |  |  |  |  |  | url            => $page_data->{current_page_url} , | 
| 664 | 0 |  |  |  |  | 0 | }); | 
| 665 | 0 |  |  |  |  | 0 | $page->metadata->{page_data} = $page_data; | 
| 666 |  |  |  |  |  |  |  | 
| 667 | 0 |  |  |  |  | 0 | $self->add_input( "Paged page $page_data->{page_number}" => 'page' ); | 
| 668 | 0 |  |  |  |  | 0 | $self->add_object( $page ); | 
| 669 |  |  |  |  |  |  | } | 
| 670 |  |  |  |  |  |  | } | 
| 671 |  |  |  |  |  |  |  | 
| 672 |  |  |  |  |  |  |  | 
| 673 | 15 |  |  |  |  | 381 | foreach my $file ( $self->all_objects ) { | 
| 674 | 103 |  |  |  |  | 4933 | $file->publish; | 
| 675 |  |  |  |  |  |  |  | 
| 676 | 103 |  |  |  |  | 50564 | $self->INFO( sprintf "* Published %s" , $file->output_filename ); | 
| 677 |  |  |  |  |  |  |  | 
| 678 | 103 |  |  |  |  | 3993 | my $path = path( $file->output_filename ); | 
| 679 | 103 |  |  |  |  | 3160 | while ( $path ne '.' ) { | 
| 680 | 370 |  |  |  |  | 15263 | $self->add_written_file( $path->stringify() => 1 ); | 
| 681 | 370 |  |  |  |  | 945 | $path = $path->parent; | 
| 682 |  |  |  |  |  |  | } | 
| 683 |  |  |  |  |  |  | } | 
| 684 |  |  |  |  |  |  |  | 
| 685 | 15 | 50 |  |  |  | 983 | if ( $self->remove_unwritten_files ) { | 
| 686 | 15 |  |  |  |  | 279 | foreach ( File::Find::Rule->in( $self->destination )) { | 
| 687 | 213 | 100 |  |  |  | 21867 | next if $self->wrote_file($_); | 
| 688 |  |  |  |  |  |  |  | 
| 689 | 1 |  |  |  |  | 5 | my $path = path( $_ ); | 
| 690 | 1 | 50 |  |  |  | 26 | $path->is_dir ? $path->remove_tree : $path->remove; | 
| 691 |  |  |  |  |  |  | } | 
| 692 |  |  |  |  |  |  | } | 
| 693 |  |  |  |  |  |  |  | 
| 694 | 15 | 50 |  |  |  | 297 | return 1 unless $self->plugins; | 
| 695 |  |  |  |  |  |  |  | 
| 696 | 15 |  |  |  |  | 59 | $self->INFO( "processing plugins for after_publish()" ); | 
| 697 |  |  |  |  |  |  |  | 
| 698 | 15 |  |  |  |  | 222 | foreach my $plugin ( @{ $self->plugins } ) { | 
|  | 15 |  |  |  |  | 243 |  | 
| 699 | 0 | 0 | 0 |  |  | 0 | if ( $plugin->does( 'HiD::Plugin' ) or | 
| 700 |  |  |  |  |  |  | ### FIXME remove after 13 Nov 2014 | 
| 701 |  |  |  |  |  |  | $plugin->isa( 'HiD::Plugin'  )) { | 
| 702 | 0 |  |  |  |  | 0 | $plugin->after_publish($self) | 
| 703 |  |  |  |  |  |  | } | 
| 704 |  |  |  |  |  |  | } | 
| 705 |  |  |  |  |  |  |  | 
| 706 | 15 |  |  |  |  | 60 | 1; | 
| 707 |  |  |  |  |  |  | } | 
| 708 |  |  |  |  |  |  |  | 
| 709 |  |  |  |  |  |  |  | 
| 710 |  |  |  |  |  |  |  | 
| 711 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 712 |  |  |  |  |  |  | 1; | 
| 713 |  |  |  |  |  |  |  | 
| 714 |  |  |  |  |  |  | __END__ | 
| 715 |  |  |  |  |  |  |  | 
| 716 |  |  |  |  |  |  | =pod | 
| 717 |  |  |  |  |  |  |  | 
| 718 |  |  |  |  |  |  | =encoding UTF-8 | 
| 719 |  |  |  |  |  |  |  | 
| 720 |  |  |  |  |  |  | =head1 NAME | 
| 721 |  |  |  |  |  |  |  | 
| 722 |  |  |  |  |  |  | HiD - Static website publishing framework | 
| 723 |  |  |  |  |  |  |  | 
| 724 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | HīD is a blog-aware, GitHub-friendly, static website generation system | 
| 727 |  |  |  |  |  |  | inspired by Jekyll. | 
| 728 |  |  |  |  |  |  |  | 
| 729 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 730 |  |  |  |  |  |  |  | 
| 731 |  |  |  |  |  |  | HiD users probably want to look at the documentation for the L<hid> command. | 
| 732 |  |  |  |  |  |  |  | 
| 733 |  |  |  |  |  |  | Subsequent documentation in this file describes internal details that are only | 
| 734 |  |  |  |  |  |  | useful or interesting for people that are trying to modify or extend HiD. | 
| 735 |  |  |  |  |  |  |  | 
| 736 |  |  |  |  |  |  | =head1 ATTRIBUTES | 
| 737 |  |  |  |  |  |  |  | 
| 738 |  |  |  |  |  |  | =head2 categories | 
| 739 |  |  |  |  |  |  |  | 
| 740 |  |  |  |  |  |  | Categories hash, contains (category, post) pairs | 
| 741 |  |  |  |  |  |  |  | 
| 742 |  |  |  |  |  |  | =head2 cli_opts | 
| 743 |  |  |  |  |  |  |  | 
| 744 |  |  |  |  |  |  | Hashref of command line options to integrate into the config. | 
| 745 |  |  |  |  |  |  |  | 
| 746 |  |  |  |  |  |  | (L<HiD::App::Command>s should pass in the C<$opt> variable to this.) | 
| 747 |  |  |  |  |  |  |  | 
| 748 |  |  |  |  |  |  | =head2 config | 
| 749 |  |  |  |  |  |  |  | 
| 750 |  |  |  |  |  |  | Hashref of configuration information. | 
| 751 |  |  |  |  |  |  |  | 
| 752 |  |  |  |  |  |  | =head2 config_file | 
| 753 |  |  |  |  |  |  |  | 
| 754 |  |  |  |  |  |  | Path to a configuration file. | 
| 755 |  |  |  |  |  |  |  | 
| 756 |  |  |  |  |  |  | =head2 default_config | 
| 757 |  |  |  |  |  |  |  | 
| 758 |  |  |  |  |  |  | Hashref of standard configuration options. The default config is: | 
| 759 |  |  |  |  |  |  |  | 
| 760 |  |  |  |  |  |  | destination => '_site'    , | 
| 761 |  |  |  |  |  |  | include_dir => '_includes', | 
| 762 |  |  |  |  |  |  | layout_dir  => '_layouts' , | 
| 763 |  |  |  |  |  |  | plugin_dir  => '_plugins' , | 
| 764 |  |  |  |  |  |  | posts_dir   => '_posts' , | 
| 765 |  |  |  |  |  |  | drafts_dir  => '_drafts' , | 
| 766 |  |  |  |  |  |  | source      => '.' , | 
| 767 |  |  |  |  |  |  |  | 
| 768 |  |  |  |  |  |  | =head2 destination | 
| 769 |  |  |  |  |  |  |  | 
| 770 |  |  |  |  |  |  | Directory to write output files into. | 
| 771 |  |  |  |  |  |  |  | 
| 772 |  |  |  |  |  |  | B<N.B.:> If it doesn't exist and is needed, it will be created. | 
| 773 |  |  |  |  |  |  |  | 
| 774 |  |  |  |  |  |  | =head2 draft_post_file_regex | 
| 775 |  |  |  |  |  |  |  | 
| 776 |  |  |  |  |  |  | Regular expression for which files will be recognized as draft blog posts. | 
| 777 |  |  |  |  |  |  |  | 
| 778 |  |  |  |  |  |  | FIXME should this be configurable? | 
| 779 |  |  |  |  |  |  |  | 
| 780 |  |  |  |  |  |  | FIXME this and post_file_regex should probably be built based on a common | 
| 781 |  |  |  |  |  |  | underlying "post_extensions_regex" attr... | 
| 782 |  |  |  |  |  |  |  | 
| 783 |  |  |  |  |  |  | =head2 excerpt_separator | 
| 784 |  |  |  |  |  |  |  | 
| 785 |  |  |  |  |  |  | String that distinguishes initial excerpt from "below the fold" content | 
| 786 |  |  |  |  |  |  |  | 
| 787 |  |  |  |  |  |  | Defaults to "\n\n" | 
| 788 |  |  |  |  |  |  |  | 
| 789 |  |  |  |  |  |  | =head2 include_dir | 
| 790 |  |  |  |  |  |  |  | 
| 791 |  |  |  |  |  |  | Directory for template "include" files | 
| 792 |  |  |  |  |  |  |  | 
| 793 |  |  |  |  |  |  | =head2 inputs | 
| 794 |  |  |  |  |  |  |  | 
| 795 |  |  |  |  |  |  | Hashref of input files. Keys are file paths; values are what type of file the | 
| 796 |  |  |  |  |  |  | system has classified that path as. | 
| 797 |  |  |  |  |  |  |  | 
| 798 |  |  |  |  |  |  | =head2 layout_dir | 
| 799 |  |  |  |  |  |  |  | 
| 800 |  |  |  |  |  |  | Directory where template files are located. | 
| 801 |  |  |  |  |  |  |  | 
| 802 |  |  |  |  |  |  | =head2 layouts | 
| 803 |  |  |  |  |  |  |  | 
| 804 |  |  |  |  |  |  | Hashref of L<HiD::Layout> objects, keyed by layout name. | 
| 805 |  |  |  |  |  |  |  | 
| 806 |  |  |  |  |  |  | =head2 limit_posts | 
| 807 |  |  |  |  |  |  |  | 
| 808 |  |  |  |  |  |  | If set, only this many blog post files will be processed during publishing. | 
| 809 |  |  |  |  |  |  |  | 
| 810 |  |  |  |  |  |  | Setting this can significantly speed up publishing for sites with many blog posts. | 
| 811 |  |  |  |  |  |  |  | 
| 812 |  |  |  |  |  |  | =head2 objects | 
| 813 |  |  |  |  |  |  |  | 
| 814 |  |  |  |  |  |  | Array of objects (pages, posts, files) created during site processing. | 
| 815 |  |  |  |  |  |  |  | 
| 816 |  |  |  |  |  |  | =head2 page_file_regex | 
| 817 |  |  |  |  |  |  |  | 
| 818 |  |  |  |  |  |  | Regular expression for identifying "page" files. | 
| 819 |  |  |  |  |  |  |  | 
| 820 |  |  |  |  |  |  | # FIXME should it be possible to set this from the config? | 
| 821 |  |  |  |  |  |  |  | 
| 822 |  |  |  |  |  |  | =head2 pages | 
| 823 |  |  |  |  |  |  |  | 
| 824 |  |  |  |  |  |  | Arrayref of L<HiD::Page> objects, populated during processing. | 
| 825 |  |  |  |  |  |  |  | 
| 826 |  |  |  |  |  |  | =head2 plugin_dir | 
| 827 |  |  |  |  |  |  |  | 
| 828 |  |  |  |  |  |  | Directory for plugins, which will be called after publish. | 
| 829 |  |  |  |  |  |  |  | 
| 830 |  |  |  |  |  |  | =head2 plugins | 
| 831 |  |  |  |  |  |  |  | 
| 832 |  |  |  |  |  |  | Plugins, which consume either of the L<HiD::Plugin> or L<HiD::Generator> roles. | 
| 833 |  |  |  |  |  |  |  | 
| 834 |  |  |  |  |  |  | Plugins used to subclass L<HiD::Plugin>, but that behavior is deprecated and | 
| 835 |  |  |  |  |  |  | will be removed on or after 13 Nov 2014. | 
| 836 |  |  |  |  |  |  |  | 
| 837 |  |  |  |  |  |  | =head2 post_file_regex | 
| 838 |  |  |  |  |  |  |  | 
| 839 |  |  |  |  |  |  | Regular expression for which files will be recognized as blog posts. | 
| 840 |  |  |  |  |  |  |  | 
| 841 |  |  |  |  |  |  | FIXME should this be configurable? | 
| 842 |  |  |  |  |  |  |  | 
| 843 |  |  |  |  |  |  | =head2 posts_dir | 
| 844 |  |  |  |  |  |  |  | 
| 845 |  |  |  |  |  |  | Directory where blog posts are located. | 
| 846 |  |  |  |  |  |  |  | 
| 847 |  |  |  |  |  |  | =head2 posts | 
| 848 |  |  |  |  |  |  |  | 
| 849 |  |  |  |  |  |  | Arrayref of L<HiD::Post> objects, populated during processing. | 
| 850 |  |  |  |  |  |  |  | 
| 851 |  |  |  |  |  |  | =head2 processor | 
| 852 |  |  |  |  |  |  |  | 
| 853 |  |  |  |  |  |  | Slot to hold the L<HiD::Processor> object that will be used during the | 
| 854 |  |  |  |  |  |  | publication process. | 
| 855 |  |  |  |  |  |  |  | 
| 856 |  |  |  |  |  |  | =head2 processor_args | 
| 857 |  |  |  |  |  |  |  | 
| 858 |  |  |  |  |  |  | Arguments to use when instantiating the L<processor> attribute. | 
| 859 |  |  |  |  |  |  |  | 
| 860 |  |  |  |  |  |  | Can be an arrayref or a hashref. | 
| 861 |  |  |  |  |  |  |  | 
| 862 |  |  |  |  |  |  | Defaults to appropriate Template Toolkit arguments. | 
| 863 |  |  |  |  |  |  |  | 
| 864 |  |  |  |  |  |  | =head2 regular_files | 
| 865 |  |  |  |  |  |  |  | 
| 866 |  |  |  |  |  |  | ArrayRef of L<HiD::File> objects, populated during processing. | 
| 867 |  |  |  |  |  |  |  | 
| 868 |  |  |  |  |  |  | =head2 remove_unwritten_files ( Boolean ) | 
| 869 |  |  |  |  |  |  |  | 
| 870 |  |  |  |  |  |  | Boolean value controlling whether files found in the C<dest_dir> that weren't | 
| 871 |  |  |  |  |  |  | produced by HiD should be removed. In other words, when this is true, after a | 
| 872 |  |  |  |  |  |  | C<hid publish> run, only files produced by HiD will be found in the | 
| 873 |  |  |  |  |  |  | C<dest_dir>. | 
| 874 |  |  |  |  |  |  |  | 
| 875 |  |  |  |  |  |  | Defaults to true. | 
| 876 |  |  |  |  |  |  |  | 
| 877 |  |  |  |  |  |  | =head2 source | 
| 878 |  |  |  |  |  |  |  | 
| 879 |  |  |  |  |  |  | Base directory that all other paths are calculated relative to. | 
| 880 |  |  |  |  |  |  |  | 
| 881 |  |  |  |  |  |  | =head2 tags | 
| 882 |  |  |  |  |  |  |  | 
| 883 |  |  |  |  |  |  | Tags hash, contains (tag, posts) pairs | 
| 884 |  |  |  |  |  |  |  | 
| 885 |  |  |  |  |  |  | =head2 time | 
| 886 |  |  |  |  |  |  |  | 
| 887 |  |  |  |  |  |  | DateTime object from the start of the latest run of the system. | 
| 888 |  |  |  |  |  |  |  | 
| 889 |  |  |  |  |  |  | Cannot be set via argument. | 
| 890 |  |  |  |  |  |  |  | 
| 891 |  |  |  |  |  |  | =head2 written_files | 
| 892 |  |  |  |  |  |  |  | 
| 893 |  |  |  |  |  |  | Hashref of files written out during the publishing process. | 
| 894 |  |  |  |  |  |  |  | 
| 895 |  |  |  |  |  |  | =head1 METHODS | 
| 896 |  |  |  |  |  |  |  | 
| 897 |  |  |  |  |  |  | =head2 get_config | 
| 898 |  |  |  |  |  |  |  | 
| 899 |  |  |  |  |  |  | my $config_key_value = $self->get_config( $config_key_name ); | 
| 900 |  |  |  |  |  |  |  | 
| 901 |  |  |  |  |  |  | Given a config key name, returns a config key value. | 
| 902 |  |  |  |  |  |  |  | 
| 903 |  |  |  |  |  |  | =head2 add_input | 
| 904 |  |  |  |  |  |  |  | 
| 905 |  |  |  |  |  |  | $self->add_input( $input_file => $input_type ); | 
| 906 |  |  |  |  |  |  |  | 
| 907 |  |  |  |  |  |  | Record what input type a particular input file is. | 
| 908 |  |  |  |  |  |  |  | 
| 909 |  |  |  |  |  |  | =head2 seen_input | 
| 910 |  |  |  |  |  |  |  | 
| 911 |  |  |  |  |  |  | if( $self->seen_input( $input_file )) { ... } | 
| 912 |  |  |  |  |  |  |  | 
| 913 |  |  |  |  |  |  | Check to see if a particular input file has been seen. | 
| 914 |  |  |  |  |  |  |  | 
| 915 |  |  |  |  |  |  | =head2 get_layout_by_name | 
| 916 |  |  |  |  |  |  |  | 
| 917 |  |  |  |  |  |  | my $hid_layout_obj = $self->get_layout_by_name( $name ); | 
| 918 |  |  |  |  |  |  |  | 
| 919 |  |  |  |  |  |  | Given a layout name (e.g., 'default') returns the corresponding L<HiD::Layout> object. | 
| 920 |  |  |  |  |  |  |  | 
| 921 |  |  |  |  |  |  | =head2 add_object | 
| 922 |  |  |  |  |  |  |  | 
| 923 |  |  |  |  |  |  | $self->add_object( $generated_object ); | 
| 924 |  |  |  |  |  |  |  | 
| 925 |  |  |  |  |  |  | Add an object to the set of objects generated during site processing. | 
| 926 |  |  |  |  |  |  |  | 
| 927 |  |  |  |  |  |  | =head2 all_objects | 
| 928 |  |  |  |  |  |  |  | 
| 929 |  |  |  |  |  |  | my @objects = $self->all_objects; | 
| 930 |  |  |  |  |  |  |  | 
| 931 |  |  |  |  |  |  | Returns the list of all objects that have been generated. | 
| 932 |  |  |  |  |  |  |  | 
| 933 |  |  |  |  |  |  | =head2 add_written_file | 
| 934 |  |  |  |  |  |  |  | 
| 935 |  |  |  |  |  |  | $self->add_written_file( $file => 1 ); | 
| 936 |  |  |  |  |  |  |  | 
| 937 |  |  |  |  |  |  | Record that a file was written. | 
| 938 |  |  |  |  |  |  |  | 
| 939 |  |  |  |  |  |  | =head2 all_written_files | 
| 940 |  |  |  |  |  |  |  | 
| 941 |  |  |  |  |  |  | my @files = $self->all_written_files; | 
| 942 |  |  |  |  |  |  |  | 
| 943 |  |  |  |  |  |  | Return the list of all files that were written out. | 
| 944 |  |  |  |  |  |  |  | 
| 945 |  |  |  |  |  |  | =head2 wrote_file | 
| 946 |  |  |  |  |  |  |  | 
| 947 |  |  |  |  |  |  | if( $self->wrote_file( $file )) { ... } | 
| 948 |  |  |  |  |  |  |  | 
| 949 |  |  |  |  |  |  | Check to see if a particular file has been written out. | 
| 950 |  |  |  |  |  |  |  | 
| 951 |  |  |  |  |  |  | =head2 publish | 
| 952 |  |  |  |  |  |  |  | 
| 953 |  |  |  |  |  |  | $self->publish; | 
| 954 |  |  |  |  |  |  |  | 
| 955 |  |  |  |  |  |  | Process files and generate output per the active configuration. | 
| 956 |  |  |  |  |  |  |  | 
| 957 |  |  |  |  |  |  | =head1 CONTRIBUTORS | 
| 958 |  |  |  |  |  |  |  | 
| 959 |  |  |  |  |  |  | =over 4 | 
| 960 |  |  |  |  |  |  |  | 
| 961 |  |  |  |  |  |  | =item * | 
| 962 |  |  |  |  |  |  |  | 
| 963 |  |  |  |  |  |  | ChinaXing | 
| 964 |  |  |  |  |  |  |  | 
| 965 |  |  |  |  |  |  | =item * | 
| 966 |  |  |  |  |  |  |  | 
| 967 |  |  |  |  |  |  | reyjrar | 
| 968 |  |  |  |  |  |  |  | 
| 969 |  |  |  |  |  |  | =item * | 
| 970 |  |  |  |  |  |  |  | 
| 971 |  |  |  |  |  |  | Yanick Champoux | 
| 972 |  |  |  |  |  |  |  | 
| 973 |  |  |  |  |  |  | =item * | 
| 974 |  |  |  |  |  |  |  | 
| 975 |  |  |  |  |  |  | Jake Goldsborough | 
| 976 |  |  |  |  |  |  |  | 
| 977 |  |  |  |  |  |  | =item * | 
| 978 |  |  |  |  |  |  |  | 
| 979 |  |  |  |  |  |  | Trey Bianchini | 
| 980 |  |  |  |  |  |  |  | 
| 981 |  |  |  |  |  |  | =back | 
| 982 |  |  |  |  |  |  |  | 
| 983 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 984 |  |  |  |  |  |  |  | 
| 985 |  |  |  |  |  |  | =over 4 | 
| 986 |  |  |  |  |  |  |  | 
| 987 |  |  |  |  |  |  | =item * | 
| 988 |  |  |  |  |  |  |  | 
| 989 |  |  |  |  |  |  | L<jekyll|http://jekyllrb.com/> | 
| 990 |  |  |  |  |  |  |  | 
| 991 |  |  |  |  |  |  | =item * | 
| 992 |  |  |  |  |  |  |  | 
| 993 |  |  |  |  |  |  | L<Papery> | 
| 994 |  |  |  |  |  |  |  | 
| 995 |  |  |  |  |  |  | =item * | 
| 996 |  |  |  |  |  |  |  | 
| 997 |  |  |  |  |  |  | L<StaticVolt> | 
| 998 |  |  |  |  |  |  |  | 
| 999 |  |  |  |  |  |  | =back | 
| 1000 |  |  |  |  |  |  |  | 
| 1001 |  |  |  |  |  |  | =head1 VERSION | 
| 1002 |  |  |  |  |  |  |  | 
| 1003 |  |  |  |  |  |  | version 1.991 | 
| 1004 |  |  |  |  |  |  |  | 
| 1005 |  |  |  |  |  |  | =head1 AUTHOR | 
| 1006 |  |  |  |  |  |  |  | 
| 1007 |  |  |  |  |  |  | John SJ Anderson <genehack@genehack.org> | 
| 1008 |  |  |  |  |  |  |  | 
| 1009 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 1010 |  |  |  |  |  |  |  | 
| 1011 |  |  |  |  |  |  | This software is copyright (c) 2015 by John SJ Anderson. | 
| 1012 |  |  |  |  |  |  |  | 
| 1013 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 1014 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 1015 |  |  |  |  |  |  |  | 
| 1016 |  |  |  |  |  |  | =cut |