Makefile.PL and $VERSION
[scpubgit/SCS.git] / lib / SCSite.pm
CommitLineData
dac1c02b 1#!/usr/bin/env perl
2
5cc6d9e2 3package SCSite;
4
2f796f69 5our $VERSION = '0.001001'; # 0.1.1
6
5cc6d9e2 7use IO::All;
8use SCSite::PageSet;
9use Web::Simple;
10
5cc6d9e2 11has pages => (is => 'lazy');
12
ebd4c292 13has filters => (is => 'lazy');
14
5cc6d9e2 15has _layout_zoom => (is => 'lazy');
16
221c4151 17has _feed_configs => (is => 'lazy');
18
19has _feed_generator => (
20 is => 'lazy',
21 handles => { _feed_http_response => 'feed_http_response' },
22);
23
5cc6d9e2 24sub default_config {
25 (
5ca56460 26 pages_dir => 'share/pages',
27 template_dir => 'share/templates',
99cee940 28 static_dir => 'share/static',
221c4151 29 feed_id_prefix => 'http://shadow.cat',
5cc6d9e2 30 )
31}
32
33sub _build_pages {
34 my ($self) = @_;
ebd4c292 35 SCSite::PageSet->new(base_dir => io->dir($self->config->{pages_dir}))
36}
37
38sub _build_filters {
39 my ($self) = @_;
40 require SCSite::SubListFilter;
fc436d2e 41 require SCSite::SidebarFilter;
42 +{
43 map +($_ => "SCSite::${_}Filter"->new_from_site($self)),
44 qw(SubList Sidebar)
45 }
5cc6d9e2 46}
47
221c4151 48sub _build__feed_configs {
49 my $f = +{
50 'blog' => {
51 title => 'All Shadowcat blogs',
52 entries => { min_depth => 2, max_depth => 3 },
43d82938 53 },
54 'blog/matt-s-trout' => {
55 title => q{Matt S Trout (mst)'s blog},
56 entries => { at_depth => 1 },
57 },
58 'blog/mark-keating' => {
59 title => q{Mark Keating (mdk)'s blog},
60 entries => { min_depth => 1, max_depth => 2 },
61 },
62 'news' => {
63 title => 'Shadowcat News',
64 entries => { at_depth => 4 },
65 },
221c4151 66 };
67 $f->{$_}{base} ||= $_ for keys %$f;
68 $f;
69}
70
71sub _build__feed_generator {
72 my ($self) = @_;
73 require SCSite::FeedGenerator;
74 SCSite::FeedGenerator->new(
75 pages => $self->pages,
76 id_prefix => $self->config->{feed_id_prefix},
77 );
78}
79
5cc6d9e2 80sub dispatch_request {
81 my $self = shift;
82 sub (/feed/**/) {
221c4151 83 if (my $conf = $self->_feed_configs->{$_[1]}) {
84 $self->_feed_http_response(200 => $conf);
85 }
5cc6d9e2 86 },
87 sub (/) {
88 $self->_page_http_response(200 => $self->_find_page('index'));
89 },
90 sub (/**) {
91 [ 302, [ 'Location' => "/$_[1]/" ], [] ]
92 },
93 sub (/**/) {
94 my ($code, $page) = map {
95 $_ ? (200, $_) : (404, $self->_error_page(404))
96 } $self->_find_page($_[1]);
97 $self->_page_http_response($code => $page);
98 }
99}
100
101sub _error_page {
102 my ($self, $code) = @_;
103 $self->_find_page("error_${code}");
104}
105
106sub _find_page {
107 my ($self, $path) = @_;
108 $self->pages->get({ path => $path });
109}
110
111sub _http_response {
112 my ($self, $code, $type, $content) = @_;
113 [ $code, [ 'Content-type' => $type ], [ $content ] ];
114}
115
116sub _page_http_response {
117 my ($self, $code, $page) = @_;
118 [ $code, [ 'Content-type' => 'text/html' ], $self->_render_page($page) ];
119}
120
121sub _render_page {
122 my ($self, $page) = @_;
123 my $zoom = $self->_layout_zoom;
fc436d2e 124 my %filters = %{$self->filters};
125 $zoom->select('.page.title')->replace_content($page->title)
2a19b2a6 126 ->select('.page.subtitle')->${\sub {
127 $page->subtitle
128 ? $_[0]->replace_content($page->subtitle)
129 : $_[0]->replace('')
130 }}
131 ->select('.page.published_at')->replace_content($page->published_at)
99cee940 132 ->select('meta[name=description]')
133 ->set_attribute(content => $page->description)
134 ->select('meta[name=keywords]')
135 ->set_attribute(content => $page->keywords)
2a19b2a6 136 ->select('meta[name=created]')
137 ->set_attribute(content => $page->created)
138 ->select('.page.body')->replace_content(\$page->body)
fc436d2e 139 ->apply(sub {
140 foreach my $fname (sort keys %filters) {
141 my $cb = $filters{$fname}->callback_for($page);
142 $_ = $_->select(".${fname}")->collect({
143 filter => $cb, passthrough => 1
144 });
145 }
146 $_
ebd4c292 147 })
5cc6d9e2 148 ->to_fh
149}
150
151sub _build__layout_zoom {
152 my ($self) = @_;
153 HTML::Zoom->from_file(
154 io->dir($self->config->{template_dir})->catfile('layout.html')
155 )->memoize;
156}
157
158sub run_if_script {
159 return $_[0]->to_psgi_app if caller(1);
160 my $class = shift;
161 my @config_keys = keys %{{$class->default_config}};
162 require Getopt::Long;
f82adbe1 163 my %config = map +($_ => $ENV{"SCS_${\uc $_}"}), @config_keys;
5cc6d9e2 164 Getopt::Long::GetOptions(
165 map +("$_=s" => \$config{$_}), @config_keys
166 );
167 delete $config{$_} for grep !defined($config{$_}), keys %config;
168 my $new = $class->new(config => \%config);
169 $new->run(@_)
170}
171
99cee940 172around _run_cli => sub {
173 my ($orig, $self) = (shift, shift);
7857f551 174 if (@_ >= 2 and $_[0] eq 'dev') {
99cee940 175 require SCSite::DevMode;
176 Moo::Role->apply_roles_to_object($self, 'SCSite::DevMode');
7857f551 177 if ($self->can("_run_dev_$_[1]")) {
178 return $self->${\"_run_dev_$_[1]"}(@_[2..$#_]);
179 } else {
180 die "No such dev mode $_[1]";
181 }
182 }
183 if (@_ >= 1 and my $code = $self->can("_run_cli_$_[0]")) {
184 shift;
185 return $self->$code(@_);
99cee940 186 }
187 return $self->$orig(@_);
188};
189
7857f551 190sub _run_cli_generate {
58152655 191 my ($self, $to, @spec) = @_;
7857f551 192 die "generate requires a directory to generate to"
193 unless $to and -d $to;
194 my $out = io($to);
58152655 195 my $check = do {
196 if (@spec) { '^('.join('|',map quotemeta($_),@spec).')' }
197 else { '.' }
198 };
7857f551 199 foreach my $path ('', $self->pages->all_paths) {
58152655 200 next unless "$path/" =~ /$check/;
201 print "Generating ${path}\n";
7857f551 202 my $dir = $out->catdir($path);
203 $dir->mkpath;
204 $dir->catfile('index.html')->print(
205 $self->run_test_request(GET => "$path/")->content
206 );
207 }
208 foreach my $path (map "/feed/$_/", keys %{$self->_feed_configs}) {
58152655 209 next unless "$path/" =~ /$check/;
210 print "Generating ${path}\n";
7857f551 211 my $dir = $out->catdir($path);
212 $dir->mkpath;
213 $dir->catfile('index.atom')->print(
214 $self->run_test_request(GET => $path)->content
215 );
216 }
217}
218
219
5cc6d9e2 220__PACKAGE__->run_if_script;