Commit | Line | Data |
5cc6d9e2 |
1 | package SCSite; |
2 | |
3 | use IO::All; |
4 | use SCSite::PageSet; |
5 | use Web::Simple; |
6 | |
5cc6d9e2 |
7 | has pages => (is => 'lazy'); |
8 | |
ebd4c292 |
9 | has filters => (is => 'lazy'); |
10 | |
5cc6d9e2 |
11 | has _layout_zoom => (is => 'lazy'); |
12 | |
221c4151 |
13 | has _feed_configs => (is => 'lazy'); |
14 | |
15 | has _feed_generator => ( |
16 | is => 'lazy', |
17 | handles => { _feed_http_response => 'feed_http_response' }, |
18 | ); |
19 | |
5cc6d9e2 |
20 | sub 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 | |
29 | sub _build_pages { |
30 | my ($self) = @_; |
ebd4c292 |
31 | SCSite::PageSet->new(base_dir => io->dir($self->config->{pages_dir})) |
32 | } |
33 | |
34 | sub _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 |
44 | sub _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 | |
67 | sub _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 |
76 | sub 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 | |
97 | sub _error_page { |
98 | my ($self, $code) = @_; |
99 | $self->_find_page("error_${code}"); |
100 | } |
101 | |
102 | sub _find_page { |
103 | my ($self, $path) = @_; |
104 | $self->pages->get({ path => $path }); |
105 | } |
106 | |
107 | sub _http_response { |
108 | my ($self, $code, $type, $content) = @_; |
109 | [ $code, [ 'Content-type' => $type ], [ $content ] ]; |
110 | } |
111 | |
112 | sub _page_http_response { |
113 | my ($self, $code, $page) = @_; |
114 | [ $code, [ 'Content-type' => 'text/html' ], $self->_render_page($page) ]; |
115 | } |
116 | |
117 | sub _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 | |
147 | sub _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 | |
154 | sub 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 |
168 | around _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; |