File Coverage

blib/lib/Router/Dumb/Helper/FileMapper.pm
Criterion Covered Total %
statement 33 33 100.0
branch 3 6 50.0
condition 1 2 50.0
subroutine 7 7 100.0
pod 1 1 100.0
total 45 49 91.8


line stmt bran cond sub pod time code
1 1     1   495 use 5.14.0;
  1         3  
2             package Router::Dumb::Helper::FileMapper 0.006;
3 1     1   5 use Moose;
  1         1  
  1         5  
4             # ABSTRACT: something to build routes out of a dumb tree of files
5              
6 1     1   6009 use File::Find::Rule;
  1         6887  
  1         7  
7 1     1   46 use Router::Dumb::Route;
  1         2  
  1         22  
8              
9 1     1   5 use Moose::Util::TypeConstraints qw(find_type_constraint);
  1         1  
  1         8  
10              
11 1     1   399 use namespace::autoclean;
  1         2  
  1         8  
12              
13             #pod =head1 OVERVIEW
14             #pod
15             #pod The FileMapper helper looks over a tree of files and adds routes to a
16             #pod L<Router::Dumb> object based on those files.
17             #pod
18             #pod For example, imagine the following file hierarchy:
19             #pod
20             #pod templates
21             #pod templates/pages
22             #pod templates/pages/help
23             #pod templates/pages/images
24             #pod templates/pages/images/INDEX
25             #pod templates/pages/INDEX
26             #pod templates/pages/legal
27             #pod templates/pages/legal/privacy
28             #pod templates/pages/legal/tos
29             #pod
30             #pod With the following code...
31             #pod
32             #pod use Path::Class qw(dir);
33             #pod
34             #pod my $r = Router::Dumb->new;
35             #pod
36             #pod Router::Dumb::Helper::FileMapper->new({
37             #pod root => 'templates/pages',
38             #pod target_munger => sub {
39             #pod my ($self, $filename) = @_;
40             #pod dir('pages')->file( file($filename)->relative($self->root) )
41             #pod ->stringify;
42             #pod },
43             #pod })->add_routes_to($r);
44             #pod
45             #pod ...the router will have a route so that:
46             #pod
47             #pod $r->route( '/legal/privacy' )->target eq 'pages/legal/privacy';
48             #pod
49             #pod These routes never have placeholders, and if files in the tree have colons at
50             #pod the beginning of their names, an exception will be thrown. Similarly, slurpy
51             #pod routes will never be added, and files named C<*> are forbidden.
52             #pod
53             #pod Files named F<INDEX> are special: they cause a route for the directory's name
54             #pod to exist.
55             #pod
56             #pod =cut
57              
58             #pod =attr root
59             #pod
60             #pod This is the name of the root directory to scan when adding routes.
61             #pod
62             #pod =cut
63              
64             has root => (
65             is => 'ro',
66             isa => 'Str',
67             required => 1,
68             );
69              
70             #pod =attr target_munger
71             #pod
72             #pod This attribute (which has a default no-op value) must be a coderef. It is
73             #pod called like a method, with the first non-self argument being the file
74             #pod responsible for the route. It should return the target for the route to be
75             #pod added.
76             #pod
77             #pod =cut
78              
79             has target_munger => (
80             reader => '_target_munger',
81             isa => 'CodeRef',
82             default => sub { sub { $_[1] } },
83             );
84              
85             #pod =attr parts_munger
86             #pod
87             #pod This attribute (which has a default no-op value) must be a coderef. It is
88             #pod called like a method, with the first non-self argument being an arrayref of the
89             #pod path components of the file responsible for the route. It should return the
90             #pod parts for the route to be added.
91             #pod
92             #pod =cut
93              
94             has parts_munger => (
95             reader => '_parts_munger',
96             isa => 'CodeRef',
97             default => sub { sub { $_[1] } },
98             );
99              
100             #pod =method add_routes_to
101             #pod
102             #pod $helper->add_routes_to( $router, \%arg );
103             #pod
104             #pod This message tells the helper to scan its directory root and add routes to the
105             #pod given router. The helper can be used over and over.
106             #pod
107             #pod Valid arguments are:
108             #pod
109             #pod ignore_conflicts - if true, trying adding an existing route will be ignored,
110             #pod rather than fail
111             #pod
112             #pod =cut
113              
114             sub add_routes_to {
115 1     1 1 637 my ($self, $router, $arg) = @_;
116 1   50     7 $arg ||= {};
117              
118 1         26 my $dir = $self->root;
119 1         22 my @files = File::Find::Rule->file->in($dir);
120              
121             my $add_method = $arg->{ignore_conflicts}
122 1 50       1064 ? 'add_route_unless_exists'
123             : 'add_route';
124              
125 1         3 for my $file (@files) {
126 5         16 my $path = $file =~ s{/INDEX$}{/}gr;
127 5         24 $path =~ s{$dir}{};
128 5         37 $path =~ s{^/}{};
129              
130 5         15 my @parts = split m{/}, $path;
131              
132             confess "can't use placeholder-like name in route files"
133 5 50       11 if grep {; /^:/ } @parts;
  6         14  
134              
135 5 50       11 confess "can't use asterisk in file names" if grep {; $_ eq '*' } @parts;
  6         10  
136              
137 5         163 my $route = Router::Dumb::Route->new({
138             parts => $self->_parts_munger->( $self, \@parts ),
139             target => $self->_target_munger->( $self, $file ),
140             });
141              
142 5         76 $router->$add_method($route);
143             }
144             }
145              
146             1;
147              
148             __END__
149              
150             =pod
151              
152             =encoding UTF-8
153              
154             =head1 NAME
155              
156             Router::Dumb::Helper::FileMapper - something to build routes out of a dumb tree of files
157              
158             =head1 VERSION
159              
160             version 0.006
161              
162             =head1 OVERVIEW
163              
164             The FileMapper helper looks over a tree of files and adds routes to a
165             L<Router::Dumb> object based on those files.
166              
167             For example, imagine the following file hierarchy:
168              
169             templates
170             templates/pages
171             templates/pages/help
172             templates/pages/images
173             templates/pages/images/INDEX
174             templates/pages/INDEX
175             templates/pages/legal
176             templates/pages/legal/privacy
177             templates/pages/legal/tos
178              
179             With the following code...
180              
181             use Path::Class qw(dir);
182              
183             my $r = Router::Dumb->new;
184              
185             Router::Dumb::Helper::FileMapper->new({
186             root => 'templates/pages',
187             target_munger => sub {
188             my ($self, $filename) = @_;
189             dir('pages')->file( file($filename)->relative($self->root) )
190             ->stringify;
191             },
192             })->add_routes_to($r);
193              
194             ...the router will have a route so that:
195              
196             $r->route( '/legal/privacy' )->target eq 'pages/legal/privacy';
197              
198             These routes never have placeholders, and if files in the tree have colons at
199             the beginning of their names, an exception will be thrown. Similarly, slurpy
200             routes will never be added, and files named C<*> are forbidden.
201              
202             Files named F<INDEX> are special: they cause a route for the directory's name
203             to exist.
204              
205             =head1 PERL VERSION
206              
207             This library should run on perls released even a long time ago. It should work
208             on any version of perl released in the last five years.
209              
210             Although it may work on older versions of perl, no guarantee is made that the
211             minimum required version will not be increased. The version may be increased
212             for any reason, and there is no promise that patches will be accepted to lower
213             the minimum required perl.
214              
215             =head1 ATTRIBUTES
216              
217             =head2 root
218              
219             This is the name of the root directory to scan when adding routes.
220              
221             =head2 target_munger
222              
223             This attribute (which has a default no-op value) must be a coderef. It is
224             called like a method, with the first non-self argument being the file
225             responsible for the route. It should return the target for the route to be
226             added.
227              
228             =head2 parts_munger
229              
230             This attribute (which has a default no-op value) must be a coderef. It is
231             called like a method, with the first non-self argument being an arrayref of the
232             path components of the file responsible for the route. It should return the
233             parts for the route to be added.
234              
235             =head1 METHODS
236              
237             =head2 add_routes_to
238              
239             $helper->add_routes_to( $router, \%arg );
240              
241             This message tells the helper to scan its directory root and add routes to the
242             given router. The helper can be used over and over.
243              
244             Valid arguments are:
245              
246             ignore_conflicts - if true, trying adding an existing route will be ignored,
247             rather than fail
248              
249             =head1 AUTHOR
250              
251             Ricardo Signes <cpan@semiotic.systems>
252              
253             =head1 COPYRIGHT AND LICENSE
254              
255             This software is copyright (c) 2022 by Ricardo Signes.
256              
257             This is free software; you can redistribute it and/or modify it under
258             the same terms as the Perl 5 programming language system itself.
259              
260             =cut