28edde43dcb039de8bd1e8fed21e1247116930d3
[scpubgit/App-SCS.git] / lib / App / SCS / Page.pm
1 package App::SCS::Page;
2
3 use IO::All;
4 use Time::Local qw(timelocal);
5 use Data::Pond qw(pond_read_datum pond_write_datum);
6 use List::Util qw(reduce);
7 use Module::Runtime qw(use_module);
8 use HTML::Zoom;
9 use Moo;
10
11 has "_$_" => (is => 'ro', init_arg => $_) for qw(page_set);
12
13 sub _page_set_class { ref($_[0]->_page_set) }
14 sub _top_dir { $_[0]->_page_set->top_dir }
15 sub _my_path { io->dir($_[0]->_top_dir)->catdir($_[0]->path) }
16
17 with 'App::SCS::Role::PageChildren';
18
19 has $_ => (is => 'ro') for qw(
20   title subtitle description keywords  html created path
21 );
22
23 has plugin_config => (is => 'rw', 'required' => 1);
24
25 sub has_plugin_config { exists $_[0]->plugin_config->{$_[1]} }
26
27 sub with_plugin_config {
28   my ($self, $with_name, $with_config) = @_;
29   my @orig = @{$self->plugin_config};
30   my @new;
31   while (my ($name, $config) = splice @orig, 0, 2) {
32     push @new, (
33       $name eq $with_name
34         ? ($name, { %$config, %$with_config })
35         : ($name, $config)
36     );
37   }
38   return $self->with(plugin_config => \@new);
39 }
40
41 has _raw_page_plugins => (is => 'lazy', builder => sub {
42   my ($self) = @_;
43   my $plugin_config = $self->plugin_config;
44   my ($plugin_map, $defaults) = @{$self->_page_set->plugin_config}
45                                  {qw(plugin_map defaults)};
46   my @spec = (@$defaults, @$plugin_config);
47   my @plugins;
48   while (my ($name, $config) = splice @spec, 0, 2) {
49     my $info = $plugin_map->{$name};
50     push @plugins,
51       use_module($info->{class})->new(
52         ($info->{config}||sub{})->(), %$config, page => $self,
53         plugin_map => $plugin_map, # some things will need this
54       );
55   }
56   return \@plugins;
57 });
58
59 has _page_plugins => (is => 'lazy', builder => sub {
60   my ($self) = @_;
61   my $raw = $self->_raw_page_plugins;
62   reduce { $b->filter_plugins($a) } $raw, @$raw;
63 });
64
65 sub published_at {
66   $_[0]->created
67     ? scalar localtime timelocal
68         map +(@{$_}[0..3], $_->[4]-1, $_->[5]-1900),
69           [ reverse split '\D+', $_[0]->created ]
70     : ''
71 }
72
73 sub to_app {
74   my ($self) = @_;
75   return sub { $self->to_psgi_response(@_) };
76 }
77
78 sub to_psgi_response {
79   my ($self, $env) = @_;
80
81   if (my $cb = $env->{'App::SCS::Command::Generate.extra_pages'}) {
82     $cb->($_->extra_pages) for @{$self->page_plugins};
83   }
84
85   $self->_psgi_response;
86 }
87
88 has _psgi_response => (is => 'lazy');
89
90 sub _build__psgi_response {
91   my ($self) = @_;
92
93   my $psgi_res = [
94     200, [ 'Content-type' => 'text/html' ], $self->_content_zoom->to_fh
95   ];
96
97   return reduce {
98     $b->filter_psgi_response($a)
99   } $psgi_res, @{$self->_page_plugins};
100 }
101
102 sub _content_zoom {
103   my ($self) = @_;
104   return reduce {
105     $b->filter_content_zoom($a)
106   } $self->_html_zoom, @{$self->_page_plugins};
107 }
108
109 sub _html_zoom {
110   my ($self) = @_;
111   return reduce {
112     $b->filter_html_zoom($a)
113   } HTML::Zoom->from_html($self->html), @{$self->_page_plugins};
114 }
115
116 sub body {
117   my ($self) = @_;
118   HTML::Zoom->from_html($self->html)
119             ->collect(body => { into => \my @ev })
120             ->run;
121   HTML::Zoom->from_events(\@ev)->to_html;
122 }
123
124 no Moo;
125
126 sub with {
127   my $self = shift;
128   return ref($self)->new(%$self, @_);
129 }
130
131 1;