basic page rendering
[scpubgit/SCS.git] / lib / SCSite.pm
1 package SCSite;
2
3 use IO::All;
4 use SCSite::PageSet;
5 use Web::Simple;
6
7 {
8   package SCSite::Link; use Moo; has $_ => (is => 'ro') for qw(title to);
9 }
10
11 has _topbar => (is => 'ro', default => sub { [
12   map SCSite::Link->new(title => $_->[0], to => $_->[1] ),
13     map [ /^(.*?)\s+(\S+)$/ ],
14       split("\n", <<'ENDBAR')
15 Home /
16 About /about
17 News /news
18 Catalyst /catalyst
19 Servers /servers
20 Professional Services /services
21 Blogs /blog
22 ENDBAR
23   ] }
24 );
25
26 has pages => (is => 'lazy');
27
28 has _layout_zoom => (is => 'lazy');
29
30 sub default_config {
31   (
32     pages_dir => 'share/content',
33     template_dir => 'share/skin',
34   )
35 }
36
37 sub _build_pages {
38   my ($self) = @_;
39   SCSite::PageSet->new(base_dir => $self->config->{pages_dir})
40 }
41
42 sub dispatch_request {
43   my $self = shift;
44   sub (/feed/**/) {
45     $self->_http_response(500 => 'text/plain' => 'Not implemented');
46   },
47   sub (/) {
48     $self->_page_http_response(200 => $self->_find_page('index'));
49   },
50   sub (/**) {
51     [ 302, [ 'Location' => "/$_[1]/" ], [] ]
52   },
53   sub (/**/) {
54     my ($code, $page) = map {
55       $_ ? (200, $_) : (404, $self->_error_page(404))
56     } $self->_find_page($_[1]);
57     $self->_page_http_response($code => $page);
58   }
59 }
60
61 sub _error_page {
62   my ($self, $code) = @_;
63   $self->_find_page("error_${code}");
64 }
65
66 sub _find_page {
67   my ($self, $path) = @_;
68   $self->pages->get({ path => $path });
69 }
70
71 sub _http_response {
72   my ($self, $code, $type, $content) = @_;
73   [ $code, [ 'Content-type' => $type ], [ $content ] ];
74 }
75
76 sub _page_http_response {
77   my ($self, $code, $page) = @_;
78   [ $code, [ 'Content-type' => 'text/html' ], $self->_render_page($page) ];
79 }
80
81 sub _render_page {
82   my ($self, $page) = @_;
83   my $zoom = $self->_layout_zoom;
84   $zoom->select('.title')->replace_content($page->title)
85        ->select('meta[name=description]')->replace_content($page->description)
86        ->select('meta[name=keywords]')->replace_content($page->keywords)
87        ->select('.topbar_entries')->repeat_content([
88          map { my $e = $_; sub {
89            $_->select('.entry')->replace_content($e->title)
90              ->then->set_attribute(href => $e->to)
91          } } @{$self->_topbar}
92        ])
93        ->select('.main')->replace_content(\$page->body)
94        ->to_fh
95 }
96
97 sub _build__layout_zoom {
98   my ($self) = @_;
99   HTML::Zoom->from_file(
100     io->dir($self->config->{template_dir})->catfile('layout.html')
101   )->memoize;
102 }
103
104 sub run_if_script {
105   return $_[0]->to_psgi_app if caller(1);
106   my $class = shift;
107   my @config_keys = keys %{{$class->default_config}};
108   require Getopt::Long;
109   my %config;
110   Getopt::Long::GetOptions(
111     map +("$_=s" => \$config{$_}), @config_keys
112   );
113   delete $config{$_} for grep !defined($config{$_}), keys %config;
114   my $new = $class->new(config => \%config);
115   $new->run(@_)
116 }
117
118 __PACKAGE__->run_if_script;