File Coverage

blib/lib/Plack/App/Directory/Markdown.pm
Criterion Covered Total %
statement 114 121 94.2
branch 14 22 63.6
condition 11 23 47.8
subroutine 21 21 100.0
pod 2 7 28.5
total 162 194 83.5


line stmt bran cond sub pod time code
1             package Plack::App::Directory::Markdown;
2 3     3   93349 use strict;
  3         4  
  3         72  
3 3     3   10 use warnings;
  3         3  
  3         61  
4 3     3   1354 use utf8;
  3         32  
  3         10  
5             our $VERSION = '0.10';
6              
7 3     3   466 use parent 'Plack::App::Directory';
  3         211  
  3         10  
8 3     3   197571 use Encode qw/encode_utf8/;
  3         7  
  3         112  
9 3     3   1264 use Data::Section::Simple;
  3         1194  
  3         110  
10 3     3   1383 use Text::Xslate;
  3         21097  
  3         132  
11 3     3   16 use HTTP::Date;
  3         4  
  3         131  
12 3     3   11 use URI::Escape qw/uri_escape/;
  3         4  
  3         115  
13 3     3   1193 use Plack::Middleware::Bootstrap;
  3         165448  
  3         104  
14 3     3   1348 use Plack::Builder;
  3         7429  
  3         200  
15              
16 3     3   15 use Plack::Util::Accessor;
  3         4  
  3         11  
17             Plack::Util::Accessor::mk_accessors(__PACKAGE__, qw(title tx tx_path markdown_class markdown_ext callback));
18              
19             sub new {
20 2     2 1 27 my $cls = shift;
21              
22 2         26 my $self = $cls->SUPER::new(@_);
23 2   33     42 $self->tx(
24             Text::Xslate->new(
25             path => [
26             ($self->tx_path || ()),
27             Data::Section::Simple->new->get_data_section,
28             ],
29             function => { process_path => \&process_path, }
30             )
31             );
32 2         1229 $self;
33             }
34              
35             sub to_app {
36 6     6 1 53340 my $self = shift;
37              
38 6         36 my $app = $self->SUPER::to_app;
39              
40             builder {
41 6     6   174 enable 'Bootstrap';
42 6         260 $app;
43 6         68 };
44             }
45              
46             sub markdown {
47 2     2 0 4 my $self = shift;
48              
49 2   33     93 my $md = $self->{_md} ||= do {
50 2   50     7 my $cls = $self->markdown_class || 'Text::Markdown';
51 2         21 Plack::Util::load_class($cls);
52              
53 2         28689 $cls->new;
54             };
55              
56 2         41 $md->markdown(@_);
57             }
58              
59             sub serve_path {
60 4     4 0 675 my($self, $env, $dir) = @_;
61              
62 4 100       30 if (-f $dir) {
63 2 50       7 if ($self->is_markdown($dir)) {
64 2 50   2   3 my $content = do {local $/;open my $fh,'<:encoding(UTF-8)',$dir or die $!;<$fh>};
  2         6  
  2         63  
  2         1902  
  2         13  
  2         1  
  2         24  
65 2         50 $content = $self->markdown($content);
66              
67 2 100       3645 if ($self->callback) {
68 1         9 $self->callback->(\$content, $env, $dir);
69             }
70              
71 2         24 my $path = $self->remove_root_path($dir);
72 2         10 $path =~ s/\.(?:markdown|mk?dn?)$//;
73              
74 2   50     8 my $page = $self->tx->render('md.tx', {
75             path => $path,
76             title => ($self->title || 'Markdown'),
77             content => $content,
78             });
79 2         20 $page = encode_utf8($page);
80              
81 2         45 my @stat = stat $dir;
82 2         13 return [ 200, [
83             'Content-Type' => 'text/html; charset=utf-8',
84             'Last-Modified' => HTTP::Date::time2str( $stat[9] ),
85             ], [ $page ] ];
86             }
87             else {
88 0         0 return $self->SUPER::serve_path($env, $dir);
89             }
90             }
91              
92 2         6 my $dir_url = $env->{SCRIPT_NAME} . $env->{PATH_INFO};
93              
94 2 50       12 if ($dir_url !~ m{/$}) {
95 0         0 return $self->return_dir_redirect($env);
96             }
97              
98 2         3 my @files;
99 2 50       10 push @files, ({ link => "../", name => "Parent Directory" }) if $env->{PATH_INFO} ne '/';
100              
101 2         15 my $dh = DirHandle->new($dir);
102 2         115 my @children;
103 2         8 while (defined(my $ent = $dh->read)) {
104 6 100 100     67 next if $ent eq '.' or $ent eq '..';
105 2         6 push @children, $ent;
106             }
107              
108 2         33 for my $basename (sort { $a cmp $b } @children) {
  0         0  
109 2         5 my $file = "$dir/$basename";
110 2         4 my $url = $dir_url . $basename;
111              
112 2         19 my $is_dir = -d $file;
113 2 50 33     10 next if !$is_dir && !$self->is_markdown($file);
114              
115 2         11 my @stat = stat _;
116              
117 2         7 $url = join '/', map {uri_escape($_)} split m{/}, $url;
  4         46  
118              
119 2 50       33 if ($is_dir) {
120 0         0 $basename .= "/";
121 0         0 $url .= "/";
122             }
123 2         10 push @files, { link => $url, name => $basename, mtime => HTTP::Date::time2str($stat[9]) };
124             }
125              
126 2         42 my $path = Plack::Util::encode_html( $env->{PATH_INFO} );
127 2         17 $path =~ s{^/}{};
128 2   50     7 my $page = $self->tx->render('index.tx', {
129             title => ($self->title || 'Markdown'),
130             files => \@files,
131             path => $path
132             });
133 2         21 $page = encode_utf8($page);
134 2         24 return [ 200, ['Content-Type' => 'text/html; charset=utf-8'], [ $page ] ];
135             }
136              
137             sub is_markdown {
138 4     4 0 8 my ($self, $file) = @_;
139 4 50       13 if ($self->markdown_ext) {
140 0         0 my $ext = quotemeta $self->markdown_ext;
141 0         0 $file =~ /$ext$/;
142             }
143             else {
144 4         43 $file =~ /\.(?:markdown|mk?dn?)$/;
145             }
146             }
147              
148             sub remove_root_path {
149 2     2 0 3 my ($self, $path) = @_;
150              
151 2         5 $path =~ s!^\./?!!;
152 2   50     5 my $root = $self->root || '';
153 2         12 $root =~ s!^\./?!!;
154 2 50 33     15 $root .= '/' if $root && $root !~ m!/$!;
155 2         3 $root = quotemeta $root;
156 2         18 $path =~ s!^$root!!;
157              
158 2         4 $path;
159             }
160              
161             sub process_path {
162 4     4 0 79940 my $path = shift;
163              
164 4         7 my @out;
165 4         6 my $i = 0;
166 4         16 foreach my $part (reverse(split('/',$path))) {
167 4         8 my $link = '../' x $i;
168              
169 4         10 push @out,
170             {
171             name => $part,
172             link => "${link}",
173             };
174 4         5 $i++;
175             }
176 4         11 $out[0]->{link} = ''; # Last element should link to itself
177 4         48 return [ reverse @out ];
178             }
179              
180             1;
181              
182             __DATA__