File Coverage

blib/lib/Mojolicious/Plugin/Directory/Stylish.pm
Criterion Covered Total %
statement 99 99 100.0
branch 28 34 82.3
condition 20 26 76.9
subroutine 16 16 100.0
pod 1 6 16.6
total 164 181 90.6


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

Index of <%= $current %>

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