| blib/lib/SoggyOnion.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 31 | 33 | 93.9 |
| branch | n/a | ||
| condition | n/a | ||
| subroutine | 11 | 11 | 100.0 |
| pod | n/a | ||
| total | 42 | 44 | 95.4 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package SoggyOnion; | ||||||
| 2 | 1 | 1 | 41729 | use strict; | |||
| 1 | 3 | ||||||
| 1 | 77 | ||||||
| 3 | 1 | 1 | 6 | use warnings; | |||
| 1 | 3 | ||||||
| 1 | 34 | ||||||
| 4 | 1 | 1 | 74 | use Carp; | |||
| 1 | 2 | ||||||
| 1 | 290 | ||||||
| 5 | |||||||
| 6 | our $VERSION = '0.04'; | ||||||
| 7 | |||||||
| 8 | # soggyonion and the default plugins use the Template Toolkit | ||||||
| 9 | 1 | 1 | 2468 | use Template; | |||
| 1 | 34096 | ||||||
| 1 | 38 | ||||||
| 10 | |||||||
| 11 | # which template file to use for this module | ||||||
| 12 | 1 | 1 | 11 | use constant TEMPLATE_FILE => 'main.tt2'; | |||
| 1 | 2 | ||||||
| 1 | 60 | ||||||
| 13 | |||||||
| 14 | # Parallel::ForkManager threads the page creation. i sure hope that | ||||||
| 15 | # Cache::File does the right thing in terms of locking.. | ||||||
| 16 | 1 | 1 | 3904 | use Parallel::ForkManager; | |||
| 1 | 62120 | ||||||
| 1 | 53 | ||||||
| 17 | |||||||
| 18 | # how many pages to create at once | ||||||
| 19 | 1 | 1 | 11 | use constant MAX_THREADS => 10; | |||
| 1 | 2 | ||||||
| 1 | 84 | ||||||
| 20 | |||||||
| 21 | # use Cache::FileCache to cache the results of plugins | ||||||
| 22 | 1 | 1 | 1043 | use Cache::FileCache; | |||
| 1 | 58652 | ||||||
| 1 | 66 | ||||||
| 23 | |||||||
| 24 | # use IO::Scalar to buffer the output of threads so the output appears | ||||||
| 25 | # in the correct order | ||||||
| 26 | 1 | 1 | 2950 | use IO::Scalar; | |||
| 1 | 8465 | ||||||
| 1 | 48 | ||||||
| 27 | |||||||
| 28 | #------------------------------------------------------------------------ | ||||||
| 29 | |||||||
| 30 | # preload base classes | ||||||
| 31 | 1 | 1 | 738 | use SoggyOnion::Plugin; | |||
| 1 | 3 | ||||||
| 1 | 32 | ||||||
| 32 | 1 | 1 | 569 | use SoggyOnion::Resource; | |||
| 0 | |||||||
| 0 | |||||||
| 33 | |||||||
| 34 | # a simple accessor for our options -- makes the code look particularly | ||||||
| 35 | # neat in plugins | ||||||
| 36 | our $OPTIONS; | ||||||
| 37 | |||||||
| 38 | sub options { | ||||||
| 39 | my ( $self, $value ) = @_; | ||||||
| 40 | if ($value) { | ||||||
| 41 | croak "configuration error: options isn't a hash\n" | ||||||
| 42 | unless ref $value eq 'HASH'; | ||||||
| 43 | $OPTIONS = $value; | ||||||
| 44 | } | ||||||
| 45 | return $OPTIONS; | ||||||
| 46 | } | ||||||
| 47 | |||||||
| 48 | # hopefully plugins that get pages will use this as the User-Agent. it | ||||||
| 49 | # can be set by exporting the $ua variable for LWP::Simple | ||||||
| 50 | # ($LWP::Simple::ua) or setting the agent option for LWP::UserAgent | ||||||
| 51 | # objects. | ||||||
| 52 | sub useragent { | ||||||
| 53 | return "SoggyOnion/$VERSION"; | ||||||
| 54 | } | ||||||
| 55 | |||||||
| 56 | # here's the meaty subroutine that is called from the executable. the | ||||||
| 57 | # executable's job is to parse a configuration file, retrieve a hash of | ||||||
| 58 | # options and call SoggyOnion->options with it, then retrieve a hash | ||||||
| 59 | # that is the layout of the pages and call SoggyOnion->generate with it. | ||||||
| 60 | sub generate { | ||||||
| 61 | my ( $self, $layout ) = @_; | ||||||
| 62 | die "configuration error: options isn't defined\n" | ||||||
| 63 | unless $self->options; | ||||||
| 64 | die "configuration error: layout isn't an array\n" | ||||||
| 65 | unless ref $layout eq 'ARRAY'; | ||||||
| 66 | |||||||
| 67 | # determine resource class | ||||||
| 68 | $self->options->{resourceclass} ||= 'SoggyOnion::Resource'; | ||||||
| 69 | |||||||
| 70 | # initialize our cache | ||||||
| 71 | my $cache; | ||||||
| 72 | eval { | ||||||
| 73 | $cache = Cache::FileCache->new( | ||||||
| 74 | { cache_root => $self->options->{cachedir}, } ); | ||||||
| 75 | }; | ||||||
| 76 | die "error creating cache: $@\n" | ||||||
| 77 | if $@; | ||||||
| 78 | |||||||
| 79 | # initialize templates | ||||||
| 80 | my $template = Template->new( | ||||||
| 81 | INCLUDE_PATH => $self->options->{templatedir}, | ||||||
| 82 | OUTPUT_PATH => $self->options->{outputdir}, | ||||||
| 83 | ); | ||||||
| 84 | |||||||
| 85 | # initialize thread manager | ||||||
| 86 | my $fm = Parallel::ForkManager->new(MAX_THREADS); | ||||||
| 87 | |||||||
| 88 | # process all pages | ||||||
| 89 | foreach my $page (@$layout) { | ||||||
| 90 | |||||||
| 91 | # fork off! | ||||||
| 92 | my $pid = $fm->start and next; | ||||||
| 93 | |||||||
| 94 | # set up buffer so that the output for each page generation is | ||||||
| 95 | # only printed at the end of the thread. note: i ran into | ||||||
| 96 | # problems when trying to capture STDERR to the same filehandle, | ||||||
| 97 | # and that's probably the problem :) | ||||||
| 98 | my $output; | ||||||
| 99 | my $output_fh = IO::Scalar->new( \$output ); | ||||||
| 100 | local *REAL_STDOUT = *STDOUT; | ||||||
| 101 | local *STDOUT = $output_fh; | ||||||
| 102 | |||||||
| 103 | # a little feedback | ||||||
| 104 | print "creating page $page->{name} ($page->{title})\n"; | ||||||
| 105 | |||||||
| 106 | # construct the body for each page by grabbing the content | ||||||
| 107 | # from each item | ||||||
| 108 | my $body = ''; | ||||||
| 109 | unless ( ref $page->{items} eq 'ARRAY' ) { | ||||||
| 110 | warn "Error: items for page $page->{name} isn't an array\n"; | ||||||
| 111 | $fm->finish; | ||||||
| 112 | next; | ||||||
| 113 | } | ||||||
| 114 | foreach my $item ( @{ $page->{items} } ) { | ||||||
| 115 | |||||||
| 116 | # let SoggyOnion/Resource.pm figure out what kind of | ||||||
| 117 | # resource this is and simply return an object. this is | ||||||
| 118 | # i made it easy to specify a "resourceclass" option in the | ||||||
| 119 | # config, so that you can extend SoggyOnion::Resource to | ||||||
| 120 | # automatically determine a class using your plugins | ||||||
| 121 | my $resource = $self->options->{resourceclass}->new($item); | ||||||
| 122 | unless ( ref $resource ) { | ||||||
| 123 | warn "\t\tcouldn't find appropriate handler for item $item\n"; | ||||||
| 124 | $fm->finish; | ||||||
| 125 | next; | ||||||
| 126 | } | ||||||
| 127 | my $id = $resource->id; | ||||||
| 128 | print "\tprocessing item ", $id, "\n"; | ||||||
| 129 | |||||||
| 130 | # is the resource already cached? check and see by | ||||||
| 131 | # comparing the resource's modification time against the | ||||||
| 132 | # modification time of the cached copy. if the resource's | ||||||
| 133 | # mod time is newer, regenerate the content. | ||||||
| 134 | # (see Cache::Cache and Cache::Object for more info) | ||||||
| 135 | my $content = 'empty content'; | ||||||
| 136 | if ( $cache->get_object($id) | ||||||
| 137 | && $cache->get_object($id)->get_created_at | ||||||
| 138 | >= $resource->mod_time ) | ||||||
| 139 | { | ||||||
| 140 | print "\t\tcache is up to date\n"; | ||||||
| 141 | $content = $cache->get($id); | ||||||
| 142 | } | ||||||
| 143 | else { | ||||||
| 144 | $content = eval { $resource->content }; | ||||||
| 145 | if ($@) { | ||||||
| 146 | $content | ||||||
| 147 | = "error generating this resource: $@"; |
||||||
| 148 | warn "\t\terror generating: $@\n"; | ||||||
| 149 | $fm->finish; | ||||||
| 150 | next; | ||||||
| 151 | } | ||||||
| 152 | $cache->set( $id => $content ); | ||||||
| 153 | } | ||||||
| 154 | |||||||
| 155 | # add the item to the body, set the 's id attribute to |
||||||
| 156 | # the id of the item | ||||||
| 157 | $body .= qq( \n); |
||||||
| 158 | $body .= $content; | ||||||
| 159 | $body .= qq(\n); | ||||||
| 160 | } | ||||||
| 161 | |||||||
| 162 | # create the output file | ||||||
| 163 | $template->process( | ||||||
| 164 | TEMPLATE_FILE, | ||||||
| 165 | { thispage => $page, | ||||||
| 166 | allpages => $layout, | ||||||
| 167 | content => $body, | ||||||
| 168 | }, | ||||||
| 169 | $page->{name}, | ||||||
| 170 | ) | ||||||
| 171 | or warn "\t\t" . $template->error . "\n"; | ||||||
| 172 | |||||||
| 173 | # print buffer to output | ||||||
| 174 | print REAL_STDOUT $output; | ||||||
| 175 | |||||||
| 176 | # finish with fork | ||||||
| 177 | $fm->finish; | ||||||
| 178 | } | ||||||
| 179 | |||||||
| 180 | # cleanup all the children | ||||||
| 181 | $fm->wait_all_children; | ||||||
| 182 | |||||||
| 183 | print "done!\n"; | ||||||
| 184 | } | ||||||
| 185 | |||||||
| 186 | 1; | ||||||
| 187 | __END__ |