subtitle handling, prettified dates. bugfixes and better errors
[scpubgit/SCS.git] / lib / SCSite.pm
CommitLineData
5cc6d9e2 1package SCSite;
2
3use IO::All;
4use SCSite::PageSet;
5use Web::Simple;
6
5cc6d9e2 7has pages => (is => 'lazy');
8
ebd4c292 9has filters => (is => 'lazy');
10
5cc6d9e2 11has _layout_zoom => (is => 'lazy');
12
221c4151 13has _feed_configs => (is => 'lazy');
14
15has _feed_generator => (
16 is => 'lazy',
17 handles => { _feed_http_response => 'feed_http_response' },
18);
19
5cc6d9e2 20sub default_config {
21 (
5ca56460 22 pages_dir => 'share/pages',
23 template_dir => 'share/templates',
99cee940 24 static_dir => 'share/static',
221c4151 25 feed_id_prefix => 'http://shadow.cat',
5cc6d9e2 26 )
27}
28
29sub _build_pages {
30 my ($self) = @_;
ebd4c292 31 SCSite::PageSet->new(base_dir => io->dir($self->config->{pages_dir}))
32}
33
34sub _build_filters {
35 my ($self) = @_;
36 require SCSite::SubListFilter;
fc436d2e 37 require SCSite::SidebarFilter;
38 +{
39 map +($_ => "SCSite::${_}Filter"->new_from_site($self)),
40 qw(SubList Sidebar)
41 }
5cc6d9e2 42}
43
221c4151 44sub _build__feed_configs {
45 my $f = +{
46 'blog' => {
47 title => 'All Shadowcat blogs',
48 entries => { min_depth => 2, max_depth => 3 },
43d82938 49 },
50 'blog/matt-s-trout' => {
51 title => q{Matt S Trout (mst)'s blog},
52 entries => { at_depth => 1 },
53 },
54 'blog/mark-keating' => {
55 title => q{Mark Keating (mdk)'s blog},
56 entries => { min_depth => 1, max_depth => 2 },
57 },
58 'news' => {
59 title => 'Shadowcat News',
60 entries => { at_depth => 4 },
61 },
221c4151 62 };
63 $f->{$_}{base} ||= $_ for keys %$f;
64 $f;
65}
66
67sub _build__feed_generator {
68 my ($self) = @_;
69 require SCSite::FeedGenerator;
70 SCSite::FeedGenerator->new(
71 pages => $self->pages,
72 id_prefix => $self->config->{feed_id_prefix},
73 );
74}
75
5cc6d9e2 76sub dispatch_request {
77 my $self = shift;
78 sub (/feed/**/) {
221c4151 79 if (my $conf = $self->_feed_configs->{$_[1]}) {
80 $self->_feed_http_response(200 => $conf);
81 }
5cc6d9e2 82 },
83 sub (/) {
84 $self->_page_http_response(200 => $self->_find_page('index'));
85 },
86 sub (/**) {
87 [ 302, [ 'Location' => "/$_[1]/" ], [] ]
88 },
89 sub (/**/) {
90 my ($code, $page) = map {
91 $_ ? (200, $_) : (404, $self->_error_page(404))
92 } $self->_find_page($_[1]);
93 $self->_page_http_response($code => $page);
94 }
95}
96
97sub _error_page {
98 my ($self, $code) = @_;
99 $self->_find_page("error_${code}");
100}
101
102sub _find_page {
103 my ($self, $path) = @_;
104 $self->pages->get({ path => $path });
105}
106
107sub _http_response {
108 my ($self, $code, $type, $content) = @_;
109 [ $code, [ 'Content-type' => $type ], [ $content ] ];
110}
111
112sub _page_http_response {
113 my ($self, $code, $page) = @_;
114 [ $code, [ 'Content-type' => 'text/html' ], $self->_render_page($page) ];
115}
116
117sub _render_page {
118 my ($self, $page) = @_;
119 my $zoom = $self->_layout_zoom;
fc436d2e 120 my %filters = %{$self->filters};
121 $zoom->select('.page.title')->replace_content($page->title)
2a19b2a6 122 ->select('.page.subtitle')->${\sub {
123 $page->subtitle
124 ? $_[0]->replace_content($page->subtitle)
125 : $_[0]->replace('')
126 }}
127 ->select('.page.published_at')->replace_content($page->published_at)
99cee940 128 ->select('meta[name=description]')
129 ->set_attribute(content => $page->description)
130 ->select('meta[name=keywords]')
131 ->set_attribute(content => $page->keywords)
2a19b2a6 132 ->select('meta[name=created]')
133 ->set_attribute(content => $page->created)
134 ->select('.page.body')->replace_content(\$page->body)
fc436d2e 135 ->apply(sub {
136 foreach my $fname (sort keys %filters) {
137 my $cb = $filters{$fname}->callback_for($page);
138 $_ = $_->select(".${fname}")->collect({
139 filter => $cb, passthrough => 1
140 });
141 }
142 $_
ebd4c292 143 })
5cc6d9e2 144 ->to_fh
145}
146
147sub _build__layout_zoom {
148 my ($self) = @_;
149 HTML::Zoom->from_file(
150 io->dir($self->config->{template_dir})->catfile('layout.html')
151 )->memoize;
152}
153
154sub run_if_script {
155 return $_[0]->to_psgi_app if caller(1);
156 my $class = shift;
157 my @config_keys = keys %{{$class->default_config}};
158 require Getopt::Long;
159 my %config;
160 Getopt::Long::GetOptions(
161 map +("$_=s" => \$config{$_}), @config_keys
162 );
163 delete $config{$_} for grep !defined($config{$_}), keys %config;
164 my $new = $class->new(config => \%config);
165 $new->run(@_)
166}
167
99cee940 168around _run_cli => sub {
169 my ($orig, $self) = (shift, shift);
170 if (@_ >= 2 and $_[0] eq 'dev' and $_[1] eq 'server') {
171 require SCSite::DevMode;
172 Moo::Role->apply_roles_to_object($self, 'SCSite::DevMode');
173 return $self->_run_dev_server(@_[2..$#_]);
174 }
175 return $self->$orig(@_);
176};
177
5cc6d9e2 178__PACKAGE__->run_if_script;