File Coverage

blib/lib/HTML/MasonX/Free/Resolver.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package HTML::MasonX::Free::Resolver;
2             {
3             $HTML::MasonX::Free::Resolver::VERSION = '0.005';
4             }
5 3     3   212631 use Moose;
  0            
  0            
6             # ABSTRACT: a resolver that lets you specialize components with dir overlays
7              
8              
9             use Carp qw(carp cluck croak);
10             use HTML::Mason::Tools qw(read_file_ref);
11             use List::AllUtils 'max';
12              
13             use namespace::autoclean;
14              
15             sub isa {
16             my ($self, $class) = @_;
17             return 1 if $class eq 'HTML::Mason::Resolver';
18             return $self->SUPER::isa($class);
19             }
20              
21             sub glob_path { croak "unimplemented; $_[0] cannot preload" }
22              
23             # [ [ foo => $path1 ], [ bar => $path2 ] ]
24             # This is really anemic validation.
25             has resolver_roots => (
26             isa => 'ArrayRef',
27             required => 1,
28             traits => [ 'Array' ],
29             handles => { resolver_roots => 'elements' },
30             );
31              
32             has allow_unusual_comp_roots => (
33             is => 'ro',
34             isa => 'Bool',
35             default => 0,
36             );
37              
38             has add_next_call => (
39             is => 'ro',
40             isa => 'Bool',
41             default => 1,
42             );
43              
44             has comp_class => (
45             is => 'ro',
46             isa => 'Str',
47             default => 'HTML::Mason::Component::FileBased',
48             );
49              
50              
51             sub get_info {
52             my ($self, $given_path, $comp_root_key, $comp_root_path) = @_;
53              
54             # It's unfortunate that comp roots are a property of the interpreter and not
55             # the resolver. -- rjbs, 2012-09-19
56             if (
57             ! $self->allow_unusual_comp_roots
58             and ($comp_root_key ne 'MAIN' or $comp_root_path !~ m{\A[\\/]-\z})
59             ) {
60             croak "when using HTML::MasonX::Free::Resolver, you must either "
61             . "set the comp_root to '/-' or set allow_unusual_comp_roots to true";
62             }
63              
64             my ($want_root, $path);
65              
66             if ($given_path =~ /=/) { ($want_root, $path) = split /=/, $given_path;
67             $want_root =~ s{^/}{}; }
68             else { ($want_root, $path) = (undef, $given_path) }
69              
70             my $saw_me;
71             my @seen_in;
72             for my $root ($self->resolver_roots) {
73             my ($root_name, $root_path) = @$root;
74             next if $want_root and ! $saw_me and $want_root ne $root_name;
75             $saw_me = 1;
76              
77             my $fn = File::Spec->canonpath( File::Spec->catfile($root_path, $path) );
78              
79             push @seen_in, [ $root_name, $fn ] if -e $fn;
80             }
81              
82             return unless @seen_in;
83              
84             my $modified = (stat $seen_in[0][1])[9];
85              
86             my $base = $comp_root_key eq 'MAIN' ? '' : "/$comp_root_key";
87             $comp_root_key = undef if $comp_root_key eq 'MAIN';
88              
89             my $srcfile = $seen_in[0][1];
90             return unless -f $srcfile;
91              
92             return HTML::Mason::ComponentSource->new(
93             friendly_name => $srcfile,
94             comp_id => "$base:$seen_in[0][0]=$seen_in[0][1]",
95             last_modified => $modified,
96             comp_path => $given_path,
97             comp_class => $self->comp_class,
98             extra => { comp_root => $comp_root_key },
99             source_callback => sub {
100             my $body .= ${ read_file_ref($srcfile) };
101             if (@seen_in > 1) {
102             $body = qq{<%flags>inherit => "/$seen_in[1][0]=$path"</%flags>}
103             . $body . "\n"
104             . ($self->add_next_call ? "% \$m->call_next if \$m->fetch_next;\n"
105             : '');
106             }
107              
108             \$body;
109             },
110             );
111             }
112              
113             1;
114              
115             __END__
116              
117             =pod
118              
119             =head1 NAME
120              
121             HTML::MasonX::Free::Resolver - a resolver that lets you specialize components with dir overlays
122              
123             =head1 VERSION
124              
125             version 0.005
126              
127             =head1 OVERVIEW
128              
129             This class is a replacement for L<HTML::Mason::Resolver::File>. If you don't
130             know anything about what the resolver does or what comp roots are, this whole
131             thing might make no sense. If you really like L<Mason|HTML::Mason>, though, it
132             might be worth reading about it. Right now.
133              
134             Okay, are you caught up?
135              
136             The next thing you need to keep in mind is that the C<comp_roots> parameter is
137             part of the I<interp> and not part of the I<resolver>. Does this seem weird to
138             you? Me too, but that's how it is.
139              
140             So, let's say you had this set of C<comp_roots>:
141              
142             my_app => /usr/myapp/mason
143             shared => /usr/share/mason
144              
145             The idea is that you can have stuff in the C<my_app> root that specializes
146             generalized stuff in the C<shared> root. Unfortunately, it's not really very
147             useful. You can't have F<foo> in the first comp root inherit from F<foo> in
148             the second. You can't easily take an existing set of templates and specialize
149             them with an overlay.
150              
151             I<That> is the problem that this resolver is meant to solve. Instead of having
152             the resolver try to find each path in each comp root independenly, the
153             C<comp_roots> are instead stored in the resolver's C<resolver_roots>. When
154             looking for a path, it looks in each root in turn. When it finds one, it
155             returns that. If there's another one in one of the later paths, the one that
156             was found will automatically be made to inherit from it and (by default) to
157             call it by default.
158              
159             Because you don't want the interp object to confuse things with comp roots, you
160             must signal that you know that its comp roots will be ignored by setting
161             C<comp_root> to "C</->".
162              
163             Say you set up your resolver roots like this:
164              
165             my_app => /usr/myapp/mason
166             shared => /usr/share/mason
167              
168             Then you have these two files:
169              
170             B<F</usr/share/mason/welcome>>:
171              
172             <h1>Welcome to <& SELF:site &>, <& SELF:user &>!</h1>
173             <%method site>the site</%method>
174             <%method user><% $m->user->name |h %></%method>
175              
176             B<F</usr/myapp/mason>>:
177              
178             <%method site>my guestbook</%method>
179              
180             If you resolve and render F</welcome>, it will say:
181              
182             Welcome to my guestbook, User Name.
183              
184             If you absolutely must render the shared welcome component directly, you can
185             refer to F</shared=/welcome>.
186              
187             This is pretty experimental code. It also probably doesn't work with some
188             Mason options that I don't use, like preloading, because I haven't implemented
189             the C<glob_path> method.
190              
191             =head1 ATTRIBUTES
192              
193             =head2 comp_class
194              
195             This argument is the class that will be used for components created by this
196             resolver. The default is HTML::Mason::Component::FileBased.
197              
198             Because HTML::MasonX::Resolver::AutoInherit is not (right now) part of
199             Class::Container, you can't pass this as an argument to the interp constructor.
200              
201             =head1 AUTHOR
202              
203             Ricardo Signes <rjbs@cpan.org>
204              
205             =head1 COPYRIGHT AND LICENSE
206              
207             This software is copyright (c) 2013 by Ricardo Signes.
208              
209             This is free software; you can redistribute it and/or modify it under
210             the same terms as the Perl 5 programming language system itself.
211              
212             =cut