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