7 has pages => (is => 'lazy');
9 has filters => (is => 'lazy');
11 has _layout_zoom => (is => 'lazy');
13 has _feed_configs => (is => 'lazy');
15 has _feed_generator => (
17 handles => { _feed_http_response => 'feed_http_response' },
22 pages_dir => 'share/pages',
23 template_dir => 'share/templates',
24 static_dir => 'share/static',
25 feed_id_prefix => 'http://shadow.cat',
31 SCSite::PageSet->new(base_dir => io->dir($self->config->{pages_dir}))
36 require SCSite::SubListFilter;
37 require SCSite::SidebarFilter;
39 map +($_ => "SCSite::${_}Filter"->new_from_site($self)),
44 sub _build__feed_configs {
47 title => 'All Shadowcat blogs',
48 entries => { min_depth => 2, max_depth => 3 },
50 'blog/matt-s-trout' => {
51 title => q{Matt S Trout (mst)'s blog},
52 entries => { at_depth => 1 },
54 'blog/mark-keating' => {
55 title => q{Mark Keating (mdk)'s blog},
56 entries => { min_depth => 1, max_depth => 2 },
59 title => 'Shadowcat News',
60 entries => { at_depth => 4 },
63 $f->{$_}{base} ||= $_ for keys %$f;
67 sub _build__feed_generator {
69 require SCSite::FeedGenerator;
70 SCSite::FeedGenerator->new(
71 pages => $self->pages,
72 id_prefix => $self->config->{feed_id_prefix},
76 sub dispatch_request {
79 if (my $conf = $self->_feed_configs->{$_[1]}) {
80 $self->_feed_http_response(200 => $conf);
84 $self->_page_http_response(200 => $self->_find_page('index'));
87 [ 302, [ 'Location' => "/$_[1]/" ], [] ]
90 my ($code, $page) = map {
91 $_ ? (200, $_) : (404, $self->_error_page(404))
92 } $self->_find_page($_[1]);
93 $self->_page_http_response($code => $page);
98 my ($self, $code) = @_;
99 $self->_find_page("error_${code}");
103 my ($self, $path) = @_;
104 $self->pages->get({ path => $path });
108 my ($self, $code, $type, $content) = @_;
109 [ $code, [ 'Content-type' => $type ], [ $content ] ];
112 sub _page_http_response {
113 my ($self, $code, $page) = @_;
114 [ $code, [ 'Content-type' => 'text/html' ], $self->_render_page($page) ];
118 my ($self, $page) = @_;
119 my $zoom = $self->_layout_zoom;
120 my %filters = %{$self->filters};
121 $zoom->select('.page.title')->replace_content($page->title)
122 ->select('.page.subtitle')->${\sub {
124 ? $_[0]->replace_content($page->subtitle)
127 ->select('.page.published_at')->replace_content($page->published_at)
128 ->select('meta[name=description]')
129 ->set_attribute(content => $page->description)
130 ->select('meta[name=keywords]')
131 ->set_attribute(content => $page->keywords)
132 ->select('meta[name=created]')
133 ->set_attribute(content => $page->created)
134 ->select('.page.body')->replace_content(\$page->body)
136 foreach my $fname (sort keys %filters) {
137 my $cb = $filters{$fname}->callback_for($page);
138 $_ = $_->select(".${fname}")->collect({
139 filter => $cb, passthrough => 1
147 sub _build__layout_zoom {
149 HTML::Zoom->from_file(
150 io->dir($self->config->{template_dir})->catfile('layout.html')
155 return $_[0]->to_psgi_app if caller(1);
157 my @config_keys = keys %{{$class->default_config}};
158 require Getopt::Long;
160 Getopt::Long::GetOptions(
161 map +("$_=s" => \$config{$_}), @config_keys
163 delete $config{$_} for grep !defined($config{$_}), keys %config;
164 my $new = $class->new(config => \%config);
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..$#_]);
175 return $self->$orig(@_);
178 __PACKAGE__->run_if_script;