File Coverage

blib/lib/Mojolicious/Plugin/Directory/Stylish.pm
Criterion Covered Total %
statement 94 94 100.0
branch 28 34 82.3
condition 20 26 76.9
subroutine 15 15 100.0
pod 1 6 16.6
total 158 175 90.2


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::Directory::Stylish;
2             $Mojolicious::Plugin::Directory::Stylish::VERSION = '1.004';
3             # ABSTRACT: Serve static files from document root with directory index using Mojolicious templates
4 10     10   27076 use strict;
  10         17  
  10         281  
5 10     10   38 use warnings;
  10         12  
  10         256  
6              
7 10     10   38 use Cwd ();
  10         17  
  10         116  
8 10     10   761 use Encode ();
  10         11559  
  10         140  
9 10     10   4554 use DirHandle;
  10         5916  
  10         291  
10 10     10   679 use Mojo::Base qw{ Mojolicious::Plugin };
  10         8870  
  10         65  
11 10     10   4166 use Mojolicious::Types;
  10         807  
  10         81  
12 10     10   775 use Mojo::Asset::File;
  10         38555  
  10         103  
13              
14             my $types = Mojolicious::Types->new;
15              
16             sub register {
17 9     9 1 308 my ( $self, $app, $args ) = @_;
18              
19 9   66     91 my $root = Mojo::Home->new( $args->{root} || Cwd::getcwd );
20 9         477 my $handler = $args->{handler};
21 9         11 my $index = $args->{dir_index};
22 9         10 my $enable_json = $args->{enable_json};
23 9   100     47 my $auto_index = $args->{auto_index} // 1;
24              
25 9   100     51 my $css = $args->{css} || 'style';
26 9   50     41 my $render_opts = $args->{render_opts} || {};
27 9   100     37 $render_opts->{template} = $args->{dir_template} || 'list';
28 9         11 push @{ $app->renderer->classes }, __PACKAGE__;
  9         52  
29 9         121 push @{ $app->static->classes }, __PACKAGE__;
  9         32  
30              
31             $app->hook(
32             before_dispatch => sub {
33 26     26   197920 my $c = shift;
34              
35 26 100       98 return render_file( $c, $root ) if ( -f $root->to_string() );
36              
37 24         666 my $path = $root->rel_dir( Mojo::Util::url_unescape( $c->req->url->path ) );
38 24 100       1558 $handler->( $c, $path ) if ( ref $handler eq 'CODE' );
39              
40 24 100       2173 if ( -f $path ) {
    50          
41 11 50       22 render_file( $c, $path ) unless ( $c->tx->res->code );
42             }
43             elsif ( -d $path ) {
44 13 100 66     56 if ( $index && ( my $file = locate_index( $index, $path ) ) ) {
45 1         3 return render_file( $c, $file );
46             }
47 12 100       40 if ( $auto_index ) {
48 11 50       39 $c->stash(css => $css),
49             render_indexes( $c, $path, $render_opts, $enable_json )
50             unless ( $c->tx->res->code );
51             }
52             }
53             },
54 9         163 );
55 9         156 return $app;
56             }
57              
58             sub locate_index {
59 1   50 1 0 3 my $index = shift || return;
60 1   33     5 my $dir = shift || Cwd::getcwd;
61              
62 1         7 my $root = Mojo::Home->new($dir);
63              
64 1 50       28 $index = ( ref $index eq 'ARRAY' ) ? $index : ["$index"];
65 1         3 for (@$index) {
66 1         4 my $path = $root->rel_file($_);
67 1 50       27 return $path if ( -e $path );
68             }
69             }
70              
71             sub render_file {
72 3     3 0 49 my ( $c, $file ) = @_;
73              
74 3         22 my $asset = Mojo::Asset::File->new(path => $file);
75 3         63 $c->reply->asset($asset);
76             }
77              
78             sub render_indexes {
79 11     11 0 331 my ( $c, $dir, $render_opts, $enable_json ) = @_;
80              
81 11 100       33 my @files =
82             ( $c->req->url eq '/' )
83             ? ()
84             : ( { url => '../', name => 'Parent Directory', size => '', type => '', mtime => '' } );
85              
86 11         1077 my ( $current, $list ) = list_files( $c, $dir );
87 11         341 push @files, @$list;
88              
89 11         44 $c->stash( files => \@files );
90 11         156 $c->stash( current => $current );
91              
92 11         119 my %respond = ( any => $render_opts );
93 11 100       41 $respond{json} = { json => { files => \@files, current => $current } }
94             if ($enable_json);
95              
96 11         54 $c->respond_to(%respond);
97             }
98              
99             sub list_files {
100 11     11 0 21 my ( $c, $dir ) = @_;
101              
102 11         41 my $current = Encode::decode_utf8( Mojo::Util::url_unescape( $c->req->url->path ) );
103              
104 11 50       726 return ( $current, [] ) unless $dir;
105              
106 11         59 my $dh = DirHandle->new($dir);
107 11         565 my @children;
108 11         126 while ( defined( my $ent = $dh->read ) ) {
109 150 100 100     2205 next if $ent eq '.' or $ent eq '..';
110 128         192 push @children, Encode::decode_utf8($ent);
111             }
112              
113 11         83 my @files;
114 11         54 for my $basename ( sort { $a cmp $b } @children ) {
  343         255  
115 128         205 my $file = "$dir/$basename";
116 128         290 my $url = Mojo::Path->new($current)->trailing_slash(0);
117 128         4955 push @{ $url->parts }, $basename;
  128         213  
118              
119 128         1878 my $is_dir = -d $file;
120 128         309 my @stat = stat _;
121 128 100       227 if ($is_dir) {
122 14         30 $basename .= '/';
123 14         36 $url->trailing_slash(1);
124             }
125              
126 128 100 100     323 my $mime_type =
127             ($is_dir)
128             ? 'directory'
129             : ( $types->type( get_ext($file) || 'txt' ) || 'text/plain' );
130 128         1726 my $mtime = Mojo::Date->new( $stat[9] )->to_string();
131              
132 128   100     3835 push @files, {
133             url => $url,
134             name => $basename,
135             size => $stat[7] || 0,
136             type => $mime_type,
137             mtime => $mtime,
138             };
139             }
140              
141 11         60 return ( $current, \@files );
142             }
143              
144             sub get_ext {
145 114 100   114 0 492 $_[0] =~ /\.([0-9a-zA-Z]+)$/ || return;
146 108         527 return lc $1;
147             }
148              
149             1;
150              
151             =pod
152              
153             =encoding UTF-8
154              
155             =head1 NAME
156              
157             Mojolicious::Plugin::Directory::Stylish - Serve static files from document root with directory index using Mojolicious templates
158              
159             =head1 VERSION
160              
161             version 1.004
162              
163             =head1 SYNOPSIS
164              
165             use Mojolicious::Lite;
166             plugin 'Directory::Stylish';
167             app->start;
168              
169             or
170              
171             > perl -Mojo -E 'a->plugin("Directory::Stylish")->start' daemon
172              
173             =head1 DESCRIPTION
174              
175             L is a static file server directory index a la Apache's mod_autoindex.
176              
177             =head1 METHODS
178              
179             L inherits all methods from L.
180              
181             =head1 OPTIONS
182              
183             L supports the following options.
184              
185             =head2 C
186              
187             plugin 'Directory::Stylish' => { root => "/path/to/htdocs" };
188              
189             Document root directory. Defaults to the current directory.
190              
191             If root is a file, serve only root file.
192              
193             =head2 C
194              
195             # Mojolicious::Lite
196             plugin 'Directory::Stylish' => { auto_index => 0 };
197              
198             Automatically generate index page for directory, default true.
199              
200             =head2 C
201              
202             plugin 'Directory::Stylish' => { dir_index => [qw/index.html index.htm/] };
203              
204             Like a Apache's DirectoryIndex directive.
205              
206             =head2 C
207              
208             plugin 'Directory::Stylish' => { dir_template => 'index' };
209              
210             # with 'render_opts' option
211             plugin 'Directory::Stylish' => {
212             dir_template => 'index',
213             render_opts => { format => 'html', handler => 'ep' },
214             };
215              
216             ...
217              
218             __DATA__
219              
220             @@ index.html.ep
221             % layout 'default';
222             % title 'DirectoryIndex';
223            

Index of <%= $current %>

224            
225             % for my $file (@$files) {
226            
  • <%== $file->{name} %>
  • 227             % }
    228              
    229             @@ layouts/default.html.ep
    230            
    231            
    232             <%= title %>
    233             <%= content %>
    234             %= include $css;
    235            
    236              
    237             A name for the template to use for the index page.
    238              
    239             "$files", "$current", and "$css" are passed in stash.
    240              
    241             =over 2
    242              
    243             =item * $files: Array[Hash]
    244              
    245             list of files and directories
    246              
    247             =item * $current: String
    248              
    249             current path
    250              
    251             =item * $css: String
    252              
    253             name of template with css that you want to include
    254              
    255             =back
    256              
    257             =head2 C
    258              
    259             use Text::Markdown qw{ markdown };
    260             use Path::Class;
    261             use Encode qw{ decode_utf8 };
    262              
    263             plugin 'Directory::Stylish' => {
    264             handler => sub {
    265             my ($c, $path) = @_;
    266             if ($path =~ /\.(md|mkdn)$/) {
    267             my $text = file($path)->slurp;
    268             my $html = markdown( decode_utf8($text) );
    269             $c->render( inline => $html );
    270             }
    271             }
    272             };
    273              
    274             CODEREF for handle a request file.
    275              
    276             If not rendered in CODEREF, serve as static file.
    277              
    278             =head2 C
    279              
    280             # http://host/directory?format=json
    281             plugin 'Directory::Stylish' => { enable_json => 1 };
    282              
    283             enable json response.
    284              
    285             =head2 C
    286              
    287             plugin 'Directory::Stylish' => { css => 'custom_template' };
    288              
    289             ...
    290             __DATA__
    291              
    292             @@ custom_template.html.ep
    293            
    296              
    297             A name for the template with css that will be included by the default template
    298             for the index.
    299              
    300             This name will be available as C<$css> in the stash.
    301              
    302             =head1 CONTRIBUTORS
    303              
    304             Many thanks to the contributors for their work.
    305              
    306             =over 2
    307              
    308             =item * ChinaXing
    309              
    310             =item * Su-Shee
    311              
    312             =back
    313              
    314             =head1 SEE ALSO
    315              
    316             =over 2
    317              
    318             =item * L
    319              
    320             =item * L
    321              
    322             =back
    323              
    324             =head1 ORIGINAL AUTHOR
    325              
    326             hayajo Ehayajo@cpan.orgE - Original author of L
    327              
    328             =head1 AUTHOR
    329              
    330             Andreas Guldstrand
    331              
    332             =head1 COPYRIGHT AND LICENSE
    333              
    334             This software is copyright (c) 2016 by Hayato Imai, Andreas Guldstrand.
    335              
    336             This is free software; you can redistribute it and/or modify it under
    337             the same terms as the Perl 5 programming language system itself.
    338              
    339             =cut
    340              
    341             __DATA__