File Coverage

blib/lib/YATT/Lite/Factory.pm
Criterion Covered Total %
statement 376 468 80.3
branch 91 162 56.1
condition 43 95 45.2
subroutine 80 97 82.4
pod 4 60 6.6
total 594 882 67.3


line stmt bran cond sub pod time code
1             package YATT::Lite::Factory;
2 13     13   18190 use strict;
  13         30  
  13         385  
3 13     13   67 use warnings qw(FATAL all NONFATAL misc);
  13         25  
  13         393  
4 13     13   71 use Carp;
  13         26  
  13         620  
5 13     13   70 use YATT::Lite::Breakpoint;
  13         23  
  13         768  
6             sub MY () {__PACKAGE__}
7 13     13   106 use mro 'c3';
  13         1191  
  13         98  
8              
9 13     13   499 use constant DEBUG_FACTORY => $ENV{DEBUG_YATT_FACTORY};
  13         26  
  13         924  
10 13     13   73 use constant DEBUG_REFCNT => $ENV{DEBUG_YATT_REFCNT};
  13         26  
  13         644  
11 13     13   76 use if DEBUG_REFCNT, B => qw/svref_2object/;
  13         26  
  13         81  
12              
13 13     13   540 use 5.010;
  13         45  
14 13     13   64 use Scalar::Util qw(weaken);
  13         23  
  13         558  
15 13     13   116 use Encode qw/decode/;
  13         8378  
  13         939  
16              
17 13     13   76 use parent qw/File::Spec YATT::Lite::NSBuilder/;
  13         25  
  13         90  
18 13     13   743 use File::Path ();
  13         28  
  13         228  
19 13     13   62 use File::Basename qw/dirname/;
  13         27  
  13         764  
20              
21 13     13   4493 use YATT::Lite::PSGIEnv;
  13         33  
  13         61  
22              
23             # Note: Definition of default values are not yet gathered here.
24             # Some are in YATT::Lite, others are in YATT::Lite::Core, CGen.. and so on.
25              
26             use YATT::Lite::MFields
27 13         247 ([cf_namespace =>
28             (doc => "namespace prefix for yatt. (default: [yatt, perl])")]
29              
30             , [cf_doc_root =>
31             (doc => "Primary template directory")]
32              
33             , [cf_app_base =>
34             (doc => "Base dir for this siteapp")]
35              
36             , [cf_site_prefix =>
37             (doc => "Location prefix for this siteapp")]
38              
39             , [cf_index_name =>
40             (doc => "Rootname of index template. (default: index)")]
41              
42             , [cf_ext_public =>
43             (doc => "public file extension for yatt. (default: yatt)")]
44              
45             , [cf_ext_private =>
46             (doc => "hidden file extension for yatt. (default: ytmpl)")]
47              
48             , [cf_header_charset =>
49             (doc => "Charset for outgoing HTTP Content-Type. (default: utf-8)")]
50              
51             , [cf_tmpl_encoding =>
52             (doc => "Perl encoding used while reading yatt templates. (default: 'utf-8')")]
53              
54             , [cf_output_encoding =>
55             (doc => "Perl encoding used for outgoing response body."
56             ." Also this is used to decode incoming request parameters and PATH_INFO."
57             ." (default: 'utf-8')")]
58              
59             , [cf_offline =>
60             (doc => "Whether header should be emitted or not.")]
61              
62             , [cf_binary_config =>
63             (doc => "(This may be changed in future release) Whether .htyattconfig.* should be read with encoding or not.")]
64              
65             , [cf_no_unicode =>
66             (doc => "(Compatibility option) Avoid use of utf8.")]
67              
68             , [cf_no_unicode_params =>
69             (doc => "(Compatibility option) Avoid encoding conversion of input params.")]
70              
71             , [cf_use_subpath =>
72             (doc => "pass sub-path_info")]
73              
74             , qw/
75             cf_allow_missing_dir
76             cf_no_preload_app_base
77              
78             tmpldirs
79             loc2yatt
80             path2yatt
81              
82             loc2psgi_re
83             loc2psgi_dict
84              
85             tmpl_cache
86              
87             path2entns
88             entns2vfs_item
89              
90             cf_debug_cgen
91              
92             cf_only_parse
93             cf_config_filetypes
94              
95             cf_dont_map_args
96             cf_dont_debug_param
97             cf_always_refresh_deps
98             cf_no_mro_c3
99              
100             cf_special_entities
101             cf_default_lang
102             cf_no_lineinfo
103             cf_debug_parser
104             cf_check_lineno
105              
106             _outer_psgi_app
107             _my_psgi_app
108              
109             cf_match_argsroute_first
110 13     13   95 /);
  13         35  
111              
112 13     13   6258 use YATT::Lite::Util::AsBase;
  13         33  
  13         80  
113 13         949 use YATT::Lite::Util qw/lexpand globref untaint_any ckrequire dofile_in
114             lookup_dir fields_hash
115             lookup_path
116             secure_text_plain
117             psgi_error
118             globref_default
119             define_const
120             terse_dump
121 13     13   98 /;
  13         40  
122              
123 13     13   78 use YATT::Lite::XHF ();
  13         30  
  13         263  
124              
125 13     13   3498 use YATT::Lite::Partial::ErrorReporter;
  13         37  
  13         115  
126 13     13   3602 use YATT::Lite::Partial::AppPath;
  13         36  
  13         71  
127              
128 13     13   3856 use YATT::Lite qw/Entity *SYS *YATT *CON/;
  13         38  
  13         285  
129              
130              
131 13     13   5201 use YATT::Lite::Util::CycleDetector qw/Visits/;
  13         37  
  13         48490  
132              
133             #========================================
134             #
135             #
136             #
137              
138             our $want_object;
139 1     1 0 4 sub want_object { $want_object }
140              
141             sub find_load_factory_script {
142 0     0 0 0 my ($pack, %opts) = @_;
143             my ($found) = $pack->find_factory_script(delete $opts{dir})
144 0 0       0 or return;
145 0 0       0 my $self = $pack->load_factory_script($found)
146             or croak "Can't load YATT::Lite::Factory instance from $found";
147 0         0 $self->configure(%opts);
148 0         0 $self;
149             }
150              
151             sub load_factory_offline {
152 0     0 0 0 shift->find_load_factory_script(offline => 1, @_);
153             }
154              
155             sub configure_offline {
156 0     0 0 0 (my MY $self, my $value) = @_;
157 0         0 $self->{cf_offline} = $value;
158 0 0       0 if ($self->{cf_offline}) {
159             $self->configure(error_handler => sub {
160 0     0   0 my ($type, $err) = @_;
161 0         0 die $err;
162             })
163 0         0 }
164             }
165              
166             #========================================
167              
168             sub load_factory_for_psgi {
169 0     0 0 0 my ($pack, $psgi, %default) = @_;
170 0 0       0 unless (defined $psgi) {
171 0         0 croak "Usage: Factory->load_factory_for_psgi(psgi_filename, \%opts)";
172             }
173 0 0       0 unless (-r $psgi) {
174 0         0 croak "psgi is not readable: $psgi";
175             }
176 0         0 (my $app_rootname = $pack->rel2abs($psgi)) =~ s/\.psgi$//;
177              
178             #
179             # Assume app_root is safe.
180             #
181 0         0 my $app_root = untaint_any(dirname($app_rootname));
182 0 0       0 unless (-d $app_root) {
183 0         0 croak "Can't find app_root for $psgi";
184             }
185              
186 0   0     0 $default{doc_root} ||= "$app_root/html";
187 0 0       0 if (-d "$app_root/ytmpl") {
188 0   0     0 $default{app_base} ||= '@ytmpl';
189             }
190 0 0       0 if (my (@cf) = map {
191 0         0 my $cf = "$app_rootname.$_";
192 0 0       0 -e $cf ? $cf : ()
193             } $pack->default_config_filetypes) {
194 0 0       0 croak "Multiple configuration files!: @cf" if @cf > 1;
195             $pack->_with_loading_file($cf[0], sub {
196 0     0   0 $pack->new(app_root => $app_root, %default
197             , $pack->read_file($cf[0]));
198             })
199 0         0 } else {
200 0         0 $pack->new(app_root => $app_root, %default);
201             }
202             }
203              
204             #========================================
205              
206             my ($_n_created, $_n_destroyed);
207 1     1 0 21 sub n_created {$_n_created}
208 1     1 0 6 sub n_destroyed {$_n_destroyed}
209             {
210             our %sub2self;
211             DESTROY {
212 32     32   47908 (my MY $self) = @_;
213 32         67 print STDERR "DESTROY $self\n" if DEBUG_FACTORY;
214 32         89 delete $self->{_my_psgi_app};
215 32 50       148 if (my $outer = delete $self->{_outer_psgi_app}) {
216 0         0 delete $sub2self{$outer};
217             }
218 32         3122 ++$_n_destroyed;
219             };
220             sub to_app_and_forget {
221 0     0 0 0 (my MY $self) = @_;
222 0         0 my $sub = $self->to_app;
223 0         0 delete $self->{_my_psgi_app};
224 0         0 delete $self->{_outer_psgi_app};
225 0         0 delete $sub2self{$sub};
226 0         0 $sub;
227             }
228             sub to_app {
229 8     8 0 32 (my MY $self) = @_;
230 8 50       40 if (@_ >= 2) {
231 0         0 croak "cascade support is dropped.Use wrapped_by(builder {}) instead.";
232             }
233 8   33     45 $self->{_outer_psgi_app} // do {
234 8 100       37 if (my $old = delete $self->{_my_psgi_app}) {
235 1         5 delete $sub2self{$old};
236             }
237 8         43 $self->prepare_app;
238 8     58   55 my $sub = sub { $self->call(@_) };
  58         69795  
239 8         32 $self->{_my_psgi_app} = $sub;
240 8 50       66 weaken($self->{_my_psgi_app}) if not $want_object;
241 8         44 weaken($sub2self{$sub} = $self);
242 8         18 print STDERR "to_app($self) returned $sub\n" if DEBUG_FACTORY;
243 8         57 $sub;
244             };
245             }
246             sub wrapped_by {
247 2     2 0 225 my ($self, $outer_app) = @_;
248 2 50       10 unless ($self->{_my_psgi_app}) {
249 0         0 croak "wrapped_by is called without calling Site->to_app";
250             }
251 2         8 delete $sub2self{$self->{_my_psgi_app}};
252 2         7 $self->{_outer_psgi_app} = $outer_app;
253 2         6 $sub2self{$outer_app} = $self;
254 2 50       13 weaken($self->{_outer_psgi_app}) if not $want_object;
255 2         9 weaken($sub2self{$outer_app});
256 2         6 $outer_app;
257             }
258             sub load_psgi_script {
259 1     1 0 3 my ($pack, $fn) = @_;
260 1         2 local $want_object = 1;
261 1         7 local $0 = $fn;
262 1         5 my $sub = $pack->sandbox_dofile($fn);
263 1 50 33     14 if (ref $sub eq 'CODE') {
    50          
264 0         0 $sub2self{$sub};
265             } elsif ($sub->isa($pack) or $sub->isa(MY)) {
266 1         20 $sub;
267             } else {
268 0         0 die "Unknown load result from: $fn";
269             }
270             }
271 0     0 0 0 sub prepare_app { return }
272              
273             our $load_count;
274             sub sandbox_dofile {
275 1     1 0 2 my ($pack, $file) = @_;
276 1         6 my $sandbox = sprintf "%s::Sandbox::S%d", __PACKAGE__, ++$load_count;
277 1         2 my @__result__;
278 1 50       3 if (wantarray) {
279 0         0 @__result__ = dofile_in($sandbox, $file);
280             } else {
281 1         6 $__result__[0] = dofile_in($sandbox, $file);
282             }
283 1         4 my $sym = globref($sandbox, 'filename');
284 1 50       2 unless (*{$sym}{CODE}) {
  1         4  
285 1     0   5 *$sym = sub {$file};
  0         0  
286             }
287 1 50       5 wantarray ? @__result__ : $__result__[0];
288             }
289             }
290              
291             sub load_factory_script {
292 1     1 0 11 my ($pack, $fn) = @_;
293 1         3 local $want_object = 1;
294 1         28 local $0 = $fn;
295 1         4 local ($FindBin::Bin, $FindBin::Script
296             , $FindBin::RealBin, $FindBin::RealScript);
297 1 50       18 FindBin->again if FindBin->can("again");
298 1 50       106 if ($fn =~ /\.psgi$/) {
299 1         5 $pack->load_psgi_script($fn);
300             } else {
301 0         0 $pack->sandbox_dofile($fn);
302             }
303             }
304              
305             sub find_factory_script {
306 0     0 0 0 my $pack = shift;
307 0   0     0 my $dir = $pack->rel2abs($_[0] // $pack->curdir);
308 0         0 my @path = $pack->no_upwards($pack->splitdir($dir));
309 0         0 my $rootdir = $pack->rootdir;
310 0   0     0 while (@path and length($dir = $pack->catdir(@path)) > length($rootdir)) {
311 0 0       0 if (my ($found) = grep {-r} map {"$dir/$_.psgi"} qw(runyatt app)) {
  0         0  
  0         0  
312 0         0 return $found;
313             }
314 0         0 } continue { pop @path }
315 0         0 return;
316             }
317              
318             #========================================
319              
320             sub new {
321 37     37 1 10054 my ($class) = shift;
322 37         442 my MY $self = $class->SUPER::new(@_);
323 36 50       255 $self->preload_app_base unless $self->{cf_no_preload_app_base};
324 36         99 ++$_n_created;
325 36         280 $self;
326             }
327              
328             #
329             # preload app_base (to avoid potential double-loading bug for base loading)
330             #
331             sub preload_app_base {
332 36     36 0 88 (my MY $self) = @_;
333              
334 36         414 foreach my $dir (lexpand($self->{cf_app_base})) {
335 8 100       35 next if $dir =~ m{^::};
336 6         43 $self->load_yatt($self->app_path_expand($dir));
337             }
338             }
339              
340             sub init_app_ns {
341 37     37 0 110 (my MY $self) = @_;
342 37         241 $self->SUPER::init_app_ns;
343              
344             # EntNS is initialized here.
345             # Note: CGEN_perl is not initialized here and delayed until it is required.
346             # This helps to avoid loading CGen::Perl for *.ydo in CGI.
347 37         170 $self->{default_app}->ensure_entns($self->{app_ns});
348             }
349              
350             sub after_new {
351 37     37 1 93 (my MY $self) = @_;
352 37         216 $self->SUPER::after_new;
353 37   33     278 $self->{cf_index_name} //= $self->default_index_name;
354 37   33     253 $self->{cf_ext_public} //= $self->default_ext_public;
355 37   33     233 $self->{cf_ext_private} //= $self->default_ext_private;
356 37 50       112 if ($self->{cf_no_unicode}) {
357 0         0 $self->{cf_no_unicode_params} = 1;
358 0         0 $self->{cf_binary_config} = 1;
359             $self->{cf_header_charset}
360 0   0     0 //= ($self->{cf_output_encoding} || $self->default_header_charset);
      0        
361             $self->{cf_output_encoding}
362 0   0     0 //= $self->compat_default_output_encoding;
363             } else {
364             $self->{cf_header_charset}
365 37   66     328 //= ($self->{cf_output_encoding} // $self->default_header_charset);
      66        
366             $self->{cf_tmpl_encoding}
367 37   66     304 //= ($self->{cf_output_encoding} // $self->default_tmpl_encoding);
      66        
368 37   66     194 $self->{cf_output_encoding} //= $self->default_output_encoding;
369             }
370 37   100     222 $self->{cf_use_subpath} //= 1;
371             }
372              
373 0     0 0 0 sub compat_default_output_encoding { '' }
374 34     34 0 110 sub default_output_encoding { 'utf-8' }
375 33     33 0 225 sub default_header_charset { 'utf-8' }
376 34     34 0 176 sub default_tmpl_encoding { 'utf-8' }
377 37     37 0 152 sub default_index_name { 'index' }
378 37     37 0 139 sub default_ext_public {'yatt'}
379 37     37 0 126 sub default_ext_private {'ytmpl'}
380              
381             sub _after_after_new {
382 37     37   89 (my MY $self) = @_;
383 37         248 $self->SUPER::_after_after_new;
384              
385 37 50 33     1082 if (not $self->{cf_allow_missing_dir}
      33        
386             and $self->{cf_doc_root}
387             and not -d $self->{cf_doc_root}) {
388 0         0 croak "document_root '$self->{cf_doc_root}' is missing!";
389             }
390 37 50       156 if ($self->{cf_doc_root}) {
391 37         191 trim_slash($self->{cf_doc_root});
392             }
393             # XXX: $self->{cf_tmpldirs}
394              
395 37   100     232 $self->{cf_site_prefix} //= "";
396              
397 37         110 $self->{tmpldirs} = [];
398 37 50       139 if (my $dir = $self->{cf_doc_root}) {
399 37         81 push @{$self->{tmpldirs}}, $dir;
  37         123  
400 37         77 my $refcnt;
401 37         69 if (DEBUG_REFCNT) {
402             $refcnt = svref_2object($self)->REFCNT;
403             }
404 37         210 $self->get_yatt('/');
405 36         94 if (DEBUG_REFCNT) {
406             if (svref_2object($self)->REFCNT != $refcnt) {
407             croak "Reference count of $self is increased from $refcnt to "
408             . svref_2object($self)->REFCNT . "!";
409             }
410             }
411             }
412 36         91 $self;
413             }
414              
415             #========================================
416              
417             sub render {
418 37     37 1 12947 my MY $self = shift;
419 37         105 my $raw_bytes = $self->render_encoded(@_);
420 37         144 decode(utf8 => $raw_bytes);
421             }
422              
423             sub render_encoded {
424 37     37 0 86 (my MY $self, my ($reqrec, $args, @opts)) = @_;
425             # [$path_info, $subpage, $action]
426 37 50       101 my ($path_info, @rest) = ref $reqrec ? @$reqrec : $reqrec;
427              
428 37         159 $path_info =~ s,^/*,/,;
429              
430 37         99 my ($tmpldir, $loc, $file, $trailer, $is_index)
431             = my @pi = $self->lookup_split_path_info($path_info);
432 37 50       123 unless (@pi) {
433 0         0 die "No such location: $path_info";
434             }
435              
436 37 50       80 my $dh = $self->get_lochandler(map {untaint_any($_)} $loc, $tmpldir) or do {
  74         192  
437 0         0 die "No such directory: $path_info";
438             };
439              
440 37         153 my $con = $self->make_simple_connection
441             (
442             \@pi, yatt => $dh, noheader => 1, path_info => $path_info
443             , $self->make_debug_params($reqrec, $args)
444             );
445              
446 37 50       178 $self->invoke_dirhandler
447             (
448             $dh, $con
449             , render_into => $con
450             , @rest ? [$file, @rest] : $file
451             , $args, @opts
452             );
453              
454 37         124 $con->buffer;
455             }
456              
457             sub lookup_split_path_info {
458 65     65 0 160 (my MY $self, my $path_info) = @_;
459             lookup_path($path_info
460             , $self->{tmpldirs}
461             , $self->{cf_index_name}, ".$self->{cf_ext_public}"
462 65         503 , $self->{cf_use_subpath});
463             }
464              
465             #========================================
466              
467             sub K_MOUNT_MATCH () { "__yatt" }
468              
469             sub lookup_psgi_mount {
470 24     24 0 55 (my MY $self, my $path_info) = @_;
471 24   66     81 $self->{loc2psgi_re} // $self->rebuild_psgi_mount;
472             $path_info =~ $self->{loc2psgi_re}
473 24 100       219 or return;
474 4 50   4   153 my @mount_match = grep {/^@{[K_MOUNT_MATCH()]}/o} keys %+
  4         3287  
  4         10085  
  3         23  
  3         28  
  1         16  
475             or return;
476 3 50       11 if (@mount_match >= 2) {
477             croak "Multiple match found for psgi_mount: \n"
478 0         0 . join("\n ", map {$self->{loc2psgi_dict}{$_}[0]} @mount_match);
  0         0  
479             }
480              
481 3         16 my $path_prefix = $+{$mount_match[0]};
482              
483 3         11 my $item = $self->{loc2psgi_dict}{$path_prefix};
484              
485 3 50       20 wantarray ? @{$item}[1..$#$item] : $item->[2];
  0         0  
486             }
487              
488             sub mount_psgi {
489 3     3 0 2082 (my MY $self, my ($path_prefix, $app, @opts)) = @_;
490 3 50       10 unless (defined $path_prefix) {
491 0         0 croak "path_prefix is empty! mount_psgi(path_prefix, psgi_app)";
492             }
493 3 50       9 if (not ref $path_prefix) {
494 3         11 $path_prefix =~ s,^/*,/,;
495             }
496 3   100     13 my $dict = $self->{loc2psgi_dict} //= +{};
497 3         8 my $key = K_MOUNT_MATCH() . (keys %$dict);
498 3         9 $dict->{$path_prefix} = [$key => $path_prefix => $app, @opts];
499              
500 3         11 undef $self->{loc2psgi_re};
501              
502             # For cascading call
503 3         7 $self;
504             }
505              
506             sub rebuild_psgi_mount {
507 3     3 0 7 (my MY $self) = @_;
508 3         5 my @re;
509 3   50     6 foreach my $path_prefix (keys %{$self->{loc2psgi_dict} //= +{}}) {
  3         13  
510 5         9 my ($key, undef, $app) = @{$self->{loc2psgi_dict}{$path_prefix}};
  5         12  
511 5         62 push @re, qr{(?<$key>$path_prefix)};
512             }
513 3         11 my $all = join("|", @re);
514 3         49 $self->{loc2psgi_re} = qr{^(?:$all)(?:/|$)};
515             }
516              
517             sub psgi_file_app {
518 2     2 0 54 my ($pack, $path) = @_;
519 2         56 require Plack::App::File;
520 2         14578 Plack::App::File->new(root => $path)->to_app;
521             }
522              
523             sub mount_static {
524 0     0 0 0 (my MY $self, my ($location, $realpath)) = @_;
525 0 0       0 my $app = ref $realpath eq 'CODE' ? $realpath
526             : $self->psgi_file_app($realpath);
527             $self->mount_psgi
528             ($location, sub {
529 0     0   0 (my Env $env) = @_;
530 0         0 local $env->{PATH_INFO} = _trim_prefix($env->{PATH_INFO}, $location);
531 0         0 $app->($env);
532 0         0 });
533             }
534              
535             sub _trim_prefix {
536 0     0   0 substr($_[0], length($_[1]));
537             }
538              
539              
540             #========================================
541              
542             sub mount_action {
543 28     28 0 111523 (my MY $self, my ($path_info, $action)) = @_;
544 28 50       150 if (my $ref = ref $path_info) {
545 0         0 croak "mount_action doesn't support $ref path_info, sorry";
546             }
547 28         167 my ($tmpldir, $loc, $file, $trailer, $is_index)
548             = my @pi = $self->lookup_split_path_info($path_info);
549 28 50       124 unless (@pi) {
550 0         0 croak "Can't find acutal directory for $path_info";
551             }
552 28 50       103 unless ($is_index) {
553 0         0 croak "Conflicting mount_action($path_info) with file=$file\n";
554             }
555 28         86 my $realdir = $tmpldir.$loc;
556 28         142 my $dh = $self->get_dirhandler($realdir);
557 28         243 $dh->set_action_handler($trailer, $action);
558              
559             # For cascading call.
560 28         239 $self;
561             }
562              
563             #========================================
564              
565             sub Connection () {'YATT::Lite::Connection'};
566              
567             sub make_simple_connection {
568 37     37 0 123 (my MY $self, my ($quad, @rest)) = @_;
569 37         109 my ($tmpldir, $loc, $file, $trailer) = @$quad;
570 37         96 my $virtdir = "$self->{cf_doc_root}$loc";
571 37         72 my $realdir = "$tmpldir$loc";
572 37         155 my @params = $self->connection_quad([$virtdir, $loc, $file, $trailer]);
573 37         166 $self->make_connection(undef, @params, @rest);
574             }
575              
576             sub make_debug_params {
577 37     37 0 77 (my MY $self, my ($reqrec, $args)) = @_;
578 37         108 ();
579             }
580              
581             sub make_connection {
582 230     230 0 1339 (my MY $self, my ($fh, @params)) = @_;
583 230         1757 require YATT::Lite::Connection;
584             $self->Connection->create(
585             $fh, @params, system => $self, root => $self->{cf_doc_root}
586 230         2291 );
587             }
588              
589       0 0   sub finalize_connection {}
590              
591             sub connection_param {
592 0     0 0 0 croak "Use of YATT::Lite::Factory::connection_param is deprecated!\n";
593             }
594             sub connection_quad {
595 215     215 0 557 (my MY $self, my ($quad)) = @_;
596 215         584 my ($virtdir, $loc, $file, $subpath) = @$quad;
597 215         1557 (dir => $virtdir
598             , location => $loc
599             , file => $file
600             , subpath => $subpath);
601             }
602              
603             #========================================
604             #
605             # Hook for subclassing
606             #
607             sub run_dirhandler {
608 178     178 0 492 (my MY $self, my ($dh, $con, $file)) = @_;
609 178         689 local ($SYS, $YATT, $CON) = ($self, $dh, $con);
610 178         693 $self->before_dirhandler($dh, $con, $file);
611 178         3325 $self->invoke_dirhandler($dh, $con
612             , handle => $dh->cut_ext($file), $con, $file);
613 156         689 $self->after_dirhandler($dh, $con, $file);
614             }
615              
616 162     162 0 602 sub before_dirhandler { &maybe::next::method; }
617 156     156 0 608 sub after_dirhandler { &maybe::next::method; }
618              
619             sub invoke_dirhandler {
620 215     215 0 739 (my MY $self, my ($dh, $con, $method, @args)) = @_;
621 215         1011 $dh->with_system($self, $method, @args);
622             }
623              
624             #========================================
625              
626             sub get_lochandler {
627 201     201 0 510 (my MY $self, my ($location, $tmpldir)) = @_;
628 201   66     669 $tmpldir //= $self->{cf_doc_root};
629 201 50       687 $self->get_yatt($location) || do {
630 0         0 $self->{loc2yatt}{$location} = $self->load_yatt("$tmpldir$location");
631             };
632             }
633              
634             # location => yatt (dirhandler, dirapp)
635              
636             sub get_yatt {
637 261     261 1 7470 (my MY $self, my $loc) = @_;
638 261 50       1059 if (my $yatt = $self->{loc2yatt}{$loc}) {
639 0         0 return $yatt;
640             }
641             # print STDERR Carp::longmess("get_yatt for $loc"
642             # , YATT::Lite::Util::terse_dump($self->{tmpldirs}));
643 261         927 my ($realdir, $basedir) = lookup_dir(trim_slash($loc), $self->{tmpldirs});
644 261 50       920 unless ($realdir) {
645 0         0 $self->error("Can't find template directory for location '%s'", $loc);
646             }
647 261         1012 $self->{loc2yatt}{$loc} = $self->load_yatt($realdir, $basedir);
648             }
649              
650             # phys-path => yatt
651              
652             *get_dirhandler = *load_yatt; *get_dirhandler = *load_yatt;
653              
654             sub load_yatt {
655 393     393 0 3119 (my MY $self, my ($path, $basedir, $visits, $from)) = @_;
656              
657 393 50 33     2324 unless (defined $path and $path ne '') {
658 0         0 croak "empty path for load_yatt!"
659             }
660              
661 393         4698 $path = $self->rel2abs($path, $self->{cf_app_root});
662 393 100       1934 if (my $yatt = $self->{path2yatt}{$path}) {
663 322         1946 return $yatt;
664             }
665 71 100       221 if (not $visits) {
    100          
666 54         500 $visits = Visits->start($path);
667             } elsif (my $preds = $visits->check_cycle($path, $from)) {
668 1         5 $self->error("Template config error! base has cycle!:\n %s\n"
669             , join "\n -> ", $from, @$preds);
670             }
671             #-- DFS-visits --
672 70 50 33     1122 if (not $self->{cf_allow_missing_dir} and not -d $path) {
673 0         0 croak "Can't find '$path'!";
674             }
675 70 100       328 if (my (@cf) = map {
676 140         491 my $cf = untaint_any($path) . "/.htyattconfig.$_";
677 140 100       2760 -e $cf ? $cf : ()
678             } $self->config_filetypes) {
679 20 50       74 $self->error("Multiple configuration files!", @cf) if @cf > 1;
680 20         173 _with_loading_file {$self} $cf[0], sub {
681 20     20   59 $self->build_yatt($path, $basedir, $visits, $self->read_file($cf[0]));
682 20         38 };
683             } else {
684 50         259 $self->build_yatt($path, $basedir, $visits);
685             }
686             }
687              
688             sub build_yatt {
689 70     70 0 294 (my MY $self, my ($path, $basedir, $visits, %opts)) = @_;
690 70         215 trim_slash($path);
691              
692 70         262 my $app_name = $self->app_name_for($path, $basedir);
693              
694             #
695             # base package と base vfs object の決定
696             #
697 70         156 my (@basepkg, @basevfs);
698 70         392 $self->_list_base_spec_in($path, delete $opts{base}, $visits
699             , \@basepkg, \@basevfs);
700              
701 67         363 my $app_ns = $self->buildns(my @log = (INST => \@basepkg, $path));
702              
703 67         125 print STDERR "# Factory::buildns("
704             , terse_dump(@log), ") => $app_ns\n" if DEBUG_FACTORY;
705              
706 67         120 my $has_rc;
707 67 100       1710 if ($has_rc = (-e (my $rc = "$path/.htyattrc.pl"))) {
708             # Note: This can do "use fields (...)"
709 4         18 dofile_in($app_ns, $rc);
710             }
711              
712             my @args = (vfs => [dir => $path
713             , entns => $self->{path2entns}{$path}
714             , encoding => $self->{cf_tmpl_encoding}
715             , @basevfs ? (base => \@basevfs) : ()]
716             , dir => $path
717             , app_ns => $app_ns
718             , app_name => $app_name
719             , factory => $self
720              
721             # XXX: Design flaw! Use of tmpl_cache will cause problem.
722             # because VFS->create for base do not respect Factory->get_yatt.
723             # To solve this, I should redesign all Factory/VFS related stuffs.
724             , tmpl_cache => $self->{tmpl_cache} //= {}
725             , entns2vfs_item => $self->{entns2vfs_item} //= {}
726              
727 67 100 100     951 , $self->configparams_for(fields_hash($app_ns)));
      100        
728              
729 67 50       460 if (my @unk = $app_ns->YATT::Lite::Object::cf_unknowns(%opts)) {
730 0         0 $self->error("Unknown option for yatt app '%s': '%s'"
731             , $path, join(", ", @unk));
732             }
733              
734 67         539 my $yatt = $self->{path2yatt}{$path} = $app_ns->new(@args, %opts);
735              
736 67 100       214 if ($has_rc) {
737 4         19 $yatt->setup_rc_actions;
738             }
739              
740 67         799 $yatt;
741             }
742              
743             sub _list_base_spec_in {
744 70     70   242 (my MY $self, my ($in, $desc, $visits, $basepkg, $basevfs)) = @_;
745              
746             print STDERR "# Factory::list_base_in("
747 70         115 , terse_dump($in, $desc, $self->{cf_app_base}), ")\n" if DEBUG_FACTORY;
748              
749             #
750             # YATT::Lite->base can be either specified explicitly
751             # or implicitly copied from YATT::Lite::Factory->app_base.
752             #
753             # Later case can lead circular inheritance for app_base itself.
754             # To avoid this, $is_implicit flag is used.
755             #
756 70         176 my $is_implicit = not defined $desc;
757              
758 70   100     375 $desc //= $self->{cf_app_base};
759              
760             #
761             # First item in base is treated *primary* base.
762             # Rest of them are treated mixin.
763             #
764 70 100       252 my ($base, @mixin) = lexpand($desc)
765             or return;
766              
767             #
768             # This builds [$package => $path] pairs and separately store
769             # as primary and mixin.
770             #
771 31         70 my (@primary_pair, @mixin_pair);
772 31         99 foreach my $task ([1, $base], [0, @mixin]) {
773 62         157 my ($is_primary, @spec) = @$task;
774 62         125 foreach my $basespec (@spec) {
775 50         78 my ($pkg, $yatt);
776 50 100       249 if ($basespec =~ /^::(.*)/) {
    50          
777 5         17 ckrequire($1);
778 5 50       10 push @{$is_primary ? \@primary_pair : \@mixin_pair}, [$1, undef];
  5         30  
779             } elsif (my $realpath = $self->app_path_find_dir_in($in, $basespec)) {
780              
781 45 100       123 if ($is_implicit) {
782             #
783             # Simply drop circular inheritance for implicit case.
784             #
785 9 100       45 next if $visits->has_node($realpath);
786             }
787 40         135 $visits->ensure_make_node($realpath);
788              
789 40 100       62 push @{$is_primary ? \@primary_pair : \@mixin_pair}, [undef, $realpath];
  40         160  
790             } else {
791 0         0 $self->error("Invalid base spec: %s", $basespec);
792             }
793             }
794             }
795              
796             #
797             # This builds $basevfs for YATT::Lite::VFS::Folder::vivify_base_descs()
798             # This preallocates YATT::Lite and its entns for each realpath.
799             #
800 31         97 foreach my $pair (@primary_pair, @mixin_pair) {
801 45         93 my ($pkg, $dir) = @$pair;
802 45 100       109 next unless $dir;
803 40         124 my $yatt = $self->load_yatt($dir, undef, $visits, $in);
804 37         97 $pair->[0] = ref $yatt;
805 37         172 my $realdir = $yatt->cget('dir');
806 37         151 push @$basevfs, [dir => $realdir, entns => $self->{path2entns}{$realdir}];
807             }
808              
809             #
810             # This builds $basepkg for buildns()
811             #
812 28 50       59 push @$basepkg, map {defined $_->[0] ? $_->[0] : ()} do {
  30         92  
813 28 50       80 if (not $self->{cf_no_mro_c3}) {
814 28         59 my %known_pkg;
815 28         55 foreach my $pair (grep {defined $_->[0]} @primary_pair) {
  23         62  
816 23         38 $known_pkg{$_} = 1 for @{mro::get_linear_isa($pair->[0])};
  23         198  
817             }
818 28   66     134 (grep(!$_->[0] || !$known_pkg{$_->[0]}, @mixin_pair)
819             , @primary_pair);
820             } else {
821 0         0 @primary_pair;
822             }
823             };
824              
825 28         113 $visits->finish_node($in);
826             }
827              
828             #========================================
829              
830             sub buildns {
831 67     67 0 194 (my MY $self, my ($kind, $baselist, $path)) = @_;
832 67         351 my $newns = $self->SUPER::buildns($kind, $baselist, $path);
833              
834             # EntNS を足し、Entity も呼べるようにする。
835             $self->{default_app}->define_Entity(undef, $newns
836 67         391 , map {$_->EntNS} @$baselist);
  30         147  
837              
838             # instns には MY を定義しておく。
839 67         359 my $my = globref($newns, 'MY');
840 67 100       545 unless (*{$my}{CODE}) {
  67         229  
841 66         306 define_const($my, $newns);
842             }
843              
844             # もし $newns の EntNS が Factory(SiteApp, app.psgi) の EntNS を継承していない
845             # なら、継承する
846 67 100       893 unless ($newns->EntNS->isa($self->EntNS)) {
847 6         12 push @{globref_default(globref($newns->EntNS, 'ISA')
  6         23  
848             , [])}, $self->EntNS;
849             }
850              
851             # basevfs に entns を渡せるように。
852 67         569 $self->{path2entns}{$path} = $newns->EntNS;
853              
854 67         203 $newns;
855             }
856              
857             sub _cf_delegates {
858 68     68   754 qw(no_unicode
859             no_unicode_params
860             output_encoding
861             header_charset
862             tmpl_encoding
863             debug_cgen
864             at_done
865             app_root
866             namespace
867             index_name
868             ext_public
869             ext_private
870             only_parse
871             use_subpath
872             dont_map_args
873             dont_debug_param
874             always_refresh_deps
875             no_mro_c3
876             die_in_error
877             special_entities
878             default_lang
879             no_lineinfo
880             ext_pattern
881             debug_parser
882             check_lineno
883             match_argsroute_first
884             );
885             }
886              
887             sub configparams_for {
888 67     67 0 167 (my MY $self, my $hash) = @_;
889             # my @base = map { [dir => $_] } lexpand($self->{cf_tmpldirs});
890             # (@base ? (base => \@base) : ())
891             (
892             $self->cf_delegate_known(0, $hash, $self->_cf_delegates)
893             , (exists $hash->{cf_error_handler}
894 67 50       249 ? (error_handler => \ $self->{cf_error_handler}) : ())
895             , die_in_error => ! YATT::Lite::Util::is_debugging());
896             }
897              
898             # XXX: Should have better interface.
899             sub error {
900 1     1 0 4 (my MY $self, my ($fmt, @args)) = @_;
901 1         305 croak sprintf $fmt, @args;
902             }
903              
904             #========================================
905              
906             sub app_name_for {
907 70     70 0 198 (my MY $self, my ($path, $basedir)) = @_;
908 70         227 ensure_slash($path);
909 70 100       182 if ($basedir) {
910 49         149 ensure_slash($basedir);
911 49   33     214 $self->_extract_app_name($path, $basedir)
912             // $self->error("Can't extract app_name path=%s, base=%s"
913             , $path, $basedir);
914             } else {
915 21         82 foreach my $tmpldir (lexpand($self->{tmpldirs})) {
916 21         57 ensure_slash(my $cp = $tmpldir);
917 21 100       73 if (defined(my $app_name = $self->_extract_app_name($path, $cp))) {
918             # Can be empty string.
919 11         31 return $app_name;
920             }
921             }
922 10         29 return '';
923             }
924             }
925              
926             sub _extract_app_name {
927 73     73   1673 (my MY $self, my ($path, $basedir)) = @_;
928 73         461 my ($bs, $name) = unpack('A'.length($basedir).'A*', $path);
929 73 100       272 return undef unless $bs eq $basedir;
930 62         214 $name =~ s{[/\\]+$}{};
931 62         274 $name;
932             }
933              
934             #========================================
935              
936             sub read_file {
937 22     22 0 59 (my MY $self, my $fn) = @_;
938 22 50       172 my ($ext) = $fn =~ /\.(\w+)$/
939             or croak "Can't extract fileext from filename: $fn";
940 22 50       141 my $sub = $self->can("read_file_$ext")
941             or croak "filetype $ext is not supported: $fn";
942 22         59 $sub->($self, $fn);
943             }
944              
945 70     70 0 234 sub default_config_filetypes {qw/xhf yml/}
946             sub config_filetypes {
947 70     70 0 178 (my MY $self) = @_;
948 70 50       218 if (my $item = $self->{cf_config_filetypes}) {
949 0         0 lexpand($item)
950             } else {
951 70         248 $self->default_config_filetypes
952             }
953             }
954              
955             sub read_file_xhf {
956 20     20 0 38 (my MY $self, my $fn) = @_;
957 20   66     92 my $bytes_semantics = ref $self && $self->{cf_binary_config};
958 20         106 $self->YATT::Lite::XHF::read_file_xhf
959             ($fn, bytes => $bytes_semantics);
960             }
961              
962             sub read_file_yml {
963 2     2 0 5 (my MY $self, my $fn) = @_;
964 2         11 require YAML::Tiny;
965 2         13 my $yaml = YAML::Tiny->read($fn);
966 2 50       1039 wantarray ? lexpand($yaml->[0]) : $yaml;
967             }
968              
969             #========================================
970              
971             sub trim_slash {
972 368     368 0 2159 $_[0] =~ s,/*$,,;
973 368         1578 $_[0];
974             }
975              
976             sub ensure_slash {
977 140 50 33 140 0 692 unless (defined $_[0] and $_[0] ne '') {
978 0           $_[0] = '/';
979             } else {
980 140         1193 my $abs = File::Spec->rel2abs($_[0]);
981 140 50       544 my $sep = $^O =~ /^MSWin/ ? "\\" : "/";
982 140         1091 $abs =~ s{(?:\Q$sep\E)?$}{$sep}; # Should end with path-separator.
983 140         399 $_[0] = $abs;
984             }
985             }
986              
987             #========================================
988             {
989             Entity site_prefix => sub {
990 0     0     my MY $self = $SYS;
991 0           $self->{cf_site_prefix};
992             };
993             }
994              
995              
996             1;