File Coverage

web/cgi-bin/yatt.lib/YATT/Registry.pm
Criterion Covered Total %
statement 570 631 90.3
branch 165 242 68.1
condition 53 84 63.1
subroutine 117 125 93.6
pod 0 62 0.0
total 905 1144 79.1


line stmt bran cond sub pod time code
1             # -*- mode: perl; coding: utf-8 -*-
2              
3             package YATT::Registry;
4 5     5   2801 use strict;
  5         9  
  5         148  
5 5     5   17 use warnings FATAL => qw(all);
  5         7  
  5         160  
6 5     5   17 use Carp;
  5         5  
  5         169  
7 5     5   1392 use UNIVERSAL;
  5         35  
  5         47  
8              
9             # Debugging aid.
10             require YATT;
11 5     5   1114 use YATT::Exception;
  5         9  
  5         234  
12              
13             {
14 5     5   19 package YATT::Registry::NS; use YATT::Inc;
  5         7  
  5         27  
15 4     4   13 BEGIN {require Exporter; *import = \&Exporter::import}
  4         52  
16 4     4   9 use base qw(YATT::Class::Configurable);
  4         6  
  4         272  
17 4         22 use YATT::Fields qw(Widget
18             cf_nsid cf_parent_nsid cf_base_nsid
19             cf_pkg cf_special_entities
20             cf_name cf_vpath cf_loadkey
21             cf_mtime cf_age
22             ^is_loaded
23 4     4   13 );
  4         5  
24             # When fields is empty, %FIELDS doesn't blessed.
25             # This causes "Pseudo-hashes are deprecated"
26              
27             use YATT::Types
28 4         45 ([Dir => [qw(cf_base_template)]
29             , 'Dir'
30             , [Template => [qw(tree cf_base_template ^widget_list
31             ^cf_metainfo)]]
32             ]
33             , -base => [NS => __PACKAGE__]
34             , -alias => [Root => 'YATT::Registry'
35             , Registry => 'YATT::Registry']
36             , -default => [loader => 'YATT::Registry::Loader']
37             , -debug => $ENV{YATT_DEBUG_TYPES}
38             , qw(:type_name :export_alias)
39 4     4   289 );
  4         4  
40             }
41              
42 4     4   16 use YATT::Util qw(checked_eval checked lsearch);
  4         6  
  4         160  
43 4     4   13 use YATT::Util::Taint;
  4         6  
  4         306  
44 4     4   12 use YATT::Registry::NS;
  4         4  
  4         174  
45 4     4   13 use YATT::Util::Symbol;
  4         6  
  4         257  
46              
47 4     4   14 use base Dir;
  4         7  
  4         470  
48 4         17 use YATT::Fields qw(^Loader NS last_nsid
49             cf_auto_reload
50             cf_type_map
51             cf_debug_registry
52             cf_rc_global
53             cf_template_global
54             cf_no_lineinfo
55             current_parser
56             cf_default_base_class
57             cf_use
58             loading
59             nspattern
60             )
61             , ['^cf_namespace' => qw(yatt perl)]
62             , ['^cf_app_prefix' => "::"]
63 4     4   16 ;
  4         5  
64              
65             sub new {
66 20     20 0 99 my $nsid = 0;
67 20         113 my Root $root = shift->SUPER::new(@_, vpath => '/', nsid => $nsid);
68              
69 20 50       68 if (defined $ENV{YATT_CF_LINEINFO}) {
70 0         0 $root->{cf_no_lineinfo} = not $ENV{YATT_CF_LINEINFO};
71             }
72              
73             # $root->{NS}{$nsid} = $root; # ← サイクルするってば。
74             # 一回、空呼び出し。
75 20         41 $root->get_package($root);
76              
77             # root は new 時に強制 refresh.
78             # after_configure だと、configure の度なので、new のみに。
79 20         68 $root->refresh($root);
80              
81             # Now safe to lift @ISA.
82 20         40 $root->{is_loaded} = 1;
83              
84 20         91 $root;
85             }
86              
87             sub configure_loader {
88 20     20 0 35 (my Root $root, my ($desc)) = @_;
89 20         45 my ($type, $loadkey, @args) = @$desc;
90 20         611 $root->{Loader} = $root->default_loader->$type->new($loadkey, @args);
91 20         70 $root->{cf_loadkey} = $loadkey;
92             }
93              
94             sub configure_DIR {
95 0     0 0 0 (my Root $root, my ($dir)) = @_;
96 0         0 $root->{Loader} = $root->default_loader->DIR->new($dir);
97 0         0 $root->{cf_loadkey} = $dir;
98             }
99              
100             sub after_configure {
101 20     20 0 27 (my Root $root) = @_;
102 20         33 my $nspat = join("|" , @{$root->namespace});
  20         70  
103 20         198 $root->{nspattern} = qr{^(?:$nspat)$};
104             }
105              
106             #========================================
107             # use YATT::Registry ** => ** 系.
108              
109             {
110             our Root $ROOT;
111             our NS $CURRENT;
112              
113             sub eval_in_dir {
114             # XXX: should take care for variable capture.
115 17     17 0 39 (my Root $root, my NS $target, my ($script, @args)) = @_;
116 17 50       59 if (is_tainted($script)) {
117 0         0 confess "script is tainted: $script\n";
118             }
119              
120 17         34 my $targetClass = $root->get_package($target);
121              
122 17         72 my $prog = "package $targetClass;"
123             . " use strict;"
124             . " use warnings FATAL => qw(all);"
125             . " $script";
126 17         37 local @_ = (@args);
127 17         37 local ($ROOT, $CURRENT) = ($root, $target);
128 17         37 &YATT::break_eval;
129 17         17 my @result;
130 17 50       40 if (wantarray) {
131 0         0 @result = eval $prog;
132             } else {
133 17     2   1106 $result[0] = eval $prog;
  2     2   11  
  2     2   3  
  2     2   71  
  2     2   10  
  2     2   3  
  2     2   420  
  2     2   9  
  2     2   2  
  2     2   34  
  2     2   10  
  2     2   3  
  2     2   365  
  2     2   10  
  2     2   2  
  2     2   63  
  2     2   6  
  2     2   2  
  2     2   387  
  2     2   8  
  2         2  
  2         35  
  2         6  
  2         1  
  2         702  
  2         12  
  2         3  
  2         73  
  2         7  
  2         2  
  2         484  
  2         9  
  2         3  
  2         55  
  2         9  
  2         4  
  2         773  
  2         9  
  2         3  
  2         70  
  2         9  
  2         2  
  2         59  
  2         10  
  2         4  
  2         68  
  2         8  
  2         2  
  2         72  
  2         9  
  2         2  
  2         57  
  2         10  
  2         4  
  2         68  
  2         10  
  2         2  
  2         68  
  2         9  
  2         5  
  2         51  
134             }
135             # XXX: $prog をどう見せたいかが、状況で色々変化する。
136 17 50       54 die $@ if $@;
137 17 50       79 wantarray ? @result : $result[0];
138             }
139              
140             sub import {
141 16     16   35 my $modpack = shift;
142 16         42 my $callpack = caller;
143 16         56 $modpack->install_builtins($callpack);
144              
145 16 100       48 return unless @_;
146              
147 16 50       56 croak "Odd number of arguments for 'use $modpack @_'" if @_ % 2;
148              
149 17         67 my $fields = $CURRENT->fields_hash;
150 17         70 while (my ($name, $value) = splice @_, 0, 2) {
151 17 100       109 if (my $sub = $modpack->can("import_$name")) {
    50          
    50          
152 16         50 $sub->($modpack, $callpack, $value);
153             } elsif ($sub = $CURRENT->can("configure_$name")) {
154 4         29 $sub->($CURRENT, $value);
155             } elsif ($fields->{"cf_$name"}) {
156 5         199 $CURRENT->{"cf_$name"} = $value;
157             } else {
158 1         2 croak "Unknown YATT::Registry parameter: $name";
159             }
160             }
161             }
162              
163             # Root 以外の Dir では、こちらが呼ばれる(はず)
164             sub import_base {
165 13 50   15 0 48 croak "Can't find current registry" unless defined $ROOT;
166 15         27 my ($modpack, $targetClass, $vpath) = @_;
167 15 50       65 my Dir $dir = $CURRENT->lookup_dir($ROOT, split '/', $vpath)
168             or croak "Can't find directory: $vpath";
169 15         46 $CURRENT->{cf_base_nsid} = $dir->{cf_nsid};
170 15         42 lift_isa_to($ROOT->get_package($dir), $targetClass);
171             }
172             }
173              
174             # これが呼ばれるのは Root の時だけ。
175             sub configure_base {
176 3     1 0 103 (my MY $root, my $realdir) = @_;
177 3 0       7 unless (-d $realdir) {
178 8         22 croak "No such directory for base='$realdir'";
179             }
180              
181 1         2 my $base_nsid = $root->createNS
182             (Dir => loadkey => untaint_any($realdir));
183              
184 1         5 $root->{cf_base_nsid} = $base_nsid;
185 1         2 lift_isa_to($root->get_package(my $base = $root->nsobj($base_nsid))
186             , $root->get_package($root));
187              
188 1         1 $root->refresh($base);
189              
190 1         6 $root;
191             }
192              
193             #----------------------------------------
194              
195             {
196             our $IS_RELOADING;
197 7     4 0 18 sub is_reloading { $IS_RELOADING }
198             sub with_reloading_flag {
199 18     17 0 37 (my Root $root, my ($flag, $sub)) = @_;
200 17         31 local $IS_RELOADING = $flag;
201 17         32 $sub->();
202             }
203             }
204              
205             #----------------------------------------
206              
207             sub Entity (*$) {
208 3     3 0 5 my ($name, $sub) = @_;
209 3         6 my ($instClass) = caller;
210 3         11 my $glob = globref($instClass, "entity_$name");
211 3 50 33     11 if (MY->is_reloading and defined *{$glob}{CODE}) {
  0         0  
212             # To avoid 'Subroutine MyApp5::entity_bar redefined'.
213 0         0 undef *$glob;
214             }
215 3         33 *$glob = $sub;
216             }
217              
218             sub ElementMacro (*$) {
219 0     0 0 0 my ($name, $sub) = @_;
220 0         0 my ($instClass) = caller;
221 0         0 *{globref($instClass, "macro_$name")} = $sub;
  0         0  
222             }
223              
224 13     13 0 37 sub list_builtins { qw(Entity ElementMacro) }
225              
226             sub install_builtins {
227 13     13 0 18 my ($modpack, $destpack) = @_;
228 13         38 foreach my $name ($modpack->list_builtins) {
229 26 50       134 my $sub = $modpack->can($name)
230             or die "Can't find builtin: $name";
231 26         28 *{globref($destpack, $name)} = $sub;
  26         62  
232             }
233             }
234              
235             #========================================
236              
237             sub next_nsid {
238 255     255 0 262 my Root $root = shift;
239 255         401 ++$root->{last_nsid};
240             }
241              
242             sub createNS {
243 255     255 0 425 (my Root $root, my ($type)) = splice @_, 0, 2;
244             # class_id は?
245 255         431 my $nsid = $root->next_nsid;
246 255         1160 my NS $nsobj = $root->{NS}{$nsid} = $root->$type->new(nsid => $nsid, @_);
247 255         448 my $pkg = $root->get_package($nsobj);
248 255 100       437 foreach my $name (map {defined $_ ? @$_ : ()} $root->{cf_rc_global}) {
  255         608  
249 14         10 *{globref($pkg, $name)} = *{globref($root->{cf_app_prefix}, $name)};
  14         19  
  14         21  
250             }
251 255         1562 $nsid;
252             }
253              
254             sub nsobj {
255 2277     2277 0 2450 (my Root $root, my ($nsid)) = @_;
256 2277 50       3409 unless (defined $nsid) {
257 0         0 croak "nsobj: undefined nsid!";
258             }
259 2277 100       3373 return $root if $nsid == 0;
260 1804         4872 $root->{NS}{$nsid};
261             }
262              
263             sub get_package {
264 1523     1523 0 1701 (my Root $root, my NS $nsobj, my ($sep)) = @_;
265             # nsid のまま渡しても良いように。
266 1523 100       2669 $nsobj = $root->nsobj($nsobj) unless ref $nsobj;
267              
268 1523   66     5879 $nsobj->{cf_pkg} ||= do {
269 275         174 my $pkg = do {
270 275 100       466 if ($root == $nsobj) {
271 20 100       62 $root->{cf_app_prefix} || '::'
272             } else {
273 255   50     1568 join $sep || "::"
      100        
274             , $root->{cf_app_prefix} || '::'
275             , sprintf '%.1s%d', $nsobj->type_name
276             , $nsobj->{cf_nsid};
277             }
278             };
279 275         705 $root->checked_eval(qq{package $pkg});
280 275         790 $pkg;
281             };
282             }
283              
284             sub refresh {
285 551     551 0 685 (my Root $root, my NS $node) = @_;
286 551   33     936 $node ||= $root;
287 551 50       1100 return unless $node->{cf_loadkey};
288 551 100 100     1961 return if $node->{cf_age} and not $root->{cf_auto_reload};
289 229 50       484 return unless $root->{Loader};
290              
291             # age があるのに、 is_loaded に達してない == まだ構築の途中。
292 229 100 100     577 return if $node->{cf_age} and not $node->{is_loaded};
293 222         619 $root->{loading}{$node->{cf_nsid}} = 1;
294              
295 222 50       474 print STDERR "Referesh: $node->{cf_loadkey}\n"
296             if $root->{cf_debug_registry};
297              
298 222         579 $root->{Loader}->handle_refresh($root, $node);
299 216         665 $node->{is_loaded} = 1;
300 216         787 delete $root->{loading}{$node->{cf_nsid}};
301             }
302              
303             sub mark_load_failure {
304 1     1 0 2 my Root $root = shift;
305 1         3 while ((my $nsid, undef) = each %{$root->{loading}}) {
  2         12  
306 1         4 my NS $ns = $root->nsobj($nsid);
307             # 仮に、一度は load 済みだとする。
308 1         2 $ns->{is_loaded} = 1;
309 1         3 delete $root->{loading}{$nsid};
310             }
311             }
312              
313             sub get_ns {
314 6     6 0 15 (my Root $root, my ($elempath)) = @_;
315 6         21 $root->vivify_ns($root, @$elempath);
316             }
317              
318             sub get_package_from_node {
319 21     21 0 32 (my Root $root, my ($node)) = @_;
320 21         65 my Dir $dir = $root->get_dir_from_node($node);
321 21         54 $root->get_package($dir);
322             }
323              
324             sub get_dir_from_node {
325 21     21 0 22 (my Root $root, my ($node)) = @_;
326 21         57 my Template $tmpl = $root->get_template_from_node($node);
327 21         57 $root->nsobj($tmpl->{cf_parent_nsid});
328             }
329              
330             sub get_template_from_node {
331 284     284 0 348 (my Root $root, my ($node)) = @_;
332 284         775 $root->nsobj($node->metainfo->cget('nsid'));
333             }
334              
335             sub get_widget {
336 161     161 0 221 my Root $root = shift;
337 161         414 $root->get_widget_from_dir($root, @_);
338             }
339              
340             sub get_widget_from_template {
341 101     101 0 311 (my Root $root, my Template $tmpl, my ($nsname)) = splice @_, 0, 3;
342 101         101 my $widget;
343              
344             # Relative lookup. ($nsname case is for [delegate])
345 101 100       441 $widget = $tmpl->lookup_widget($root, @_ ? @_ : $nsname)
    100          
346             and return $widget;
347              
348             # Absolute, ns-specific lookup.
349 2 50       12 if ($root->has_ns($root, $nsname)) {
350 0 0       0 $widget = $root->get_widget_from_dir($root, $nsname, @_)
351             and return $widget;
352             }
353              
354             # Absolute, general lookup.
355 2         7 return $root->get_widget_from_dir($root, @_);
356             }
357              
358             sub get_widget_from_dir {
359 164     164 0 378 (my Root $root, my Dir $dir) = splice @_, 0, 2;
360 164         222 my @elempath = @_;
361 164         561 $dir = $dir->vivify_ns($root, splice @elempath, 0, @elempath - 2);
362 164 50       405 unless ($dir) {
363 0         0 croak "Can't find widget: ", join(":", @_);
364             }
365 164 100       487 if (@elempath == 2) {
    50          
366 19         84 $dir->widget_by_nsname($root, @elempath);
367             } elsif (@elempath == 1) {
368 145         363 $dir->widget_by_name($root, @elempath);
369             } else {
370 0         0 return;
371             }
372             }
373              
374             {
375             sub YATT::Registry::NS::list_declared_widget_names {
376 0     0 0 0 (my NS $tmpl) = @_;
377 0         0 my @result;
378 0         0 foreach my $name (keys %{$tmpl->{Widget}}) {
  0         0  
379 0         0 my $w = $tmpl->{Widget}{$name};
380 0 0       0 next unless $w->declared;
381 0         0 push @result, $name;
382             }
383 0         0 @result;
384             }
385              
386             # For relative lookup.
387             sub YATT::Registry::NS::Template::lookup_widget {
388 101     101 0 318 (my Template $tmpl, my Root $root) = splice @_, 0, 2;
389 101 50 33     1051 croak "lookup_widget: argument type mismatch for \$root."
      33        
390             unless defined $root and ref $root and $root->isa(Root);
391 101 50       213 return unless @_;
392              
393 101         321 foreach my NS $start ($tmpl, $root->nsobj($tmpl->{cf_parent_nsid})) {
394 103         213 my @elempath = @_;
395              
396 103         156 my NS $ns = do {
397 103 50       255 if (@elempath <= 2) {
398 103         141 $start;
399             } else {
400 0         0 $start->lookup_dir($root, splice @elempath, 0, @elempath - 2);
401             }
402             };
403              
404 103         150 my $found = do {
405 103 100       232 if (@elempath == 2) {
406 1         3 $ns->widget_by_nsname($root, @elempath);
407             } else {
408 102         359 $ns->widget_by_name($root, @elempath);
409             }
410             };
411 102 100       619 return $found if $found;
412             }
413             }
414              
415             sub YATT::Registry::NS::Template::lookup_template {
416 4     4 0 5 (my Template $tmpl, my Root $root, my ($name)) = @_;
417 4         11 $root->nsobj($tmpl->{cf_parent_nsid})->lookup_template($root, $name)
418             }
419              
420             sub YATT::Registry::NS::Template::lookup_dir {
421 0     0 0 0 (my Template $tmpl, my Root $root) = splice @_, 0, 2;
422 0         0 $root->nsobj($tmpl->{cf_parent_nsid})->lookup_dir($root, @_);
423             }
424              
425             sub YATT::Registry::NS::Dir::has_ns {
426 2     2 0 4 (my Dir $dir, my Root $root, my ($nsname)) = @_;
427 2         1 my $nsid;
428              
429 2 50 33     15 $nsid = $dir->{Dir}{$nsname} || $dir->{Template}{$nsname}
430             and return $root->nsobj($nsid);
431              
432 2 50       10 return unless $dir->{cf_base_nsid};
433              
434 0         0 $root->nsobj($dir->{cf_base_nsid})->has_ns($root, $nsname);
435             }
436              
437             sub YATT::Registry::NS::Dir::lookup_template {
438 4     4 0 8 (my Dir $dir, my Root $root, my ($name)) = @_;
439 4         7 my $nsid;
440 4   66     23 while (not($nsid = $dir->{Template}{$name})
441             and $dir->{cf_base_nsid}) {
442 2         5 $dir = $root->nsobj($dir->{cf_base_nsid});
443 2         6 $root->refresh($dir);
444             }
445 4 50       12 return unless $nsid;
446 4         9 $root->nsobj($nsid);
447             }
448              
449 4     4   18 use Carp;
  4         6  
  4         3673  
450             sub YATT::Registry::NS::Dir::lookup_dir {
451 12     12 0 27 (my Dir $dir, my Root $root, my (@nspath)) = @_;
452 12 50       58 croak "argtype mismatch! not a Root." unless UNIVERSAL::isa($root, Root);
453 12 50       31 return $root unless @nspath;
454 12         26 (my Dir $start, my (@orig)) = ($dir, @nspath);
455 12         30 $root->refresh($dir);
456 12   66     67 while ($dir and defined (my $ns = shift @nspath)) {
457 19 100 50     74 $dir = $root and next if $ns eq '';
458 12         31 my $nsid = $dir->{Dir}{$ns};
459 12 50       30 unless ($nsid) {
460 0 0       0 return $start->{cf_base_nsid}
461             ? $root->nsobj($start->{cf_base_nsid})->lookup_dir($root, @orig)
462             : undef;
463             }
464 12         27 $dir = $root->nsobj($nsid);
465 12         28 $root->refresh($dir);
466             }
467 12         42 $dir;
468             }
469              
470             sub YATT::Registry::NS::Dir::list_ns {
471 4     4 0 15 (my Dir $dir, my ($dict)) = @_;
472 4   50     16 $dict ||= {};
473 4         5 my @list;
474 4         7 foreach my $type (qw(Template Dir)) {
475 8         7 foreach my $key (keys %{$dir->{$type}}) {
  8         21  
476 9 100       30 push @list, $key unless $dict->{$key}++;
477             }
478             }
479 4 50       38 wantarray ? @list : \@list;
480             }
481              
482             sub YATT::Registry::NS::Dir::vivify_ns {
483 170     170 0 310 (my Dir $dir, my Root $root, my (@nspath)) = @_;
484 170         261 my @orig = @nspath;
485 170         387 while (@nspath) {
486 7         17 $root->refresh($dir);
487 7         8 $dir = do {
488 7         13 my $ns = shift @nspath;
489 7         8 my Dir $d = $dir;
490 7         8 my $nsid;
491 7   100     58 while (not($nsid = $d->{Dir}{$ns})
      66        
492             and not($nsid = $d->{Template}{$ns})
493             and $d->{cf_base_nsid}) {
494 2         7 $d = $root->nsobj($d->{cf_base_nsid});
495 2         5 $root->refresh($d);
496             }
497 7 50       17 unless ($nsid) {
498 0         0 croak "No such ns '$ns': " . join ":", @orig;
499             }
500 7         12 $root->nsobj($nsid);
501             };
502             }
503 170         300 $dir;
504             }
505              
506             sub YATT::Registry::NS::Dir::after_rc_loaded {
507 17     17 0 28 (my Dir $dir, my Root $root) = @_;
508 17 100       67 if (defined(my $base = $dir->{cf_base_nsid})) {
509 12         18 foreach my Template $tmpl (map {$root->nsobj($_)}
  13         30  
  12         41  
510             values %{$dir->{Template}}) {
511 13         36 $tmpl->{cf_base_nsid} = $base;
512             }
513             }
514             }
515              
516             sub YATT::Registry::NS::Dir::widget_by_nsname {
517 22     22 0 79 (my Dir $dir, my Root $root, my ($ns, $name)) = @_;
518 22         75 $root->refresh($dir);
519 22 50 66     335 if (defined $dir->{cf_name} and $dir->{cf_name} eq $ns) {
520 0         0 my $widget = $dir->widget_by_name($root, $name);
521 0 0       0 return $widget if $widget;
522             }
523             # [1] dir:template
524             # [2] template:widget
525 22         45 foreach my $type (qw(Dir Template)) {
526 23 100       101 next unless my $nsid = $dir->{$type}{$ns};
527 21 50       65 next unless my $widget = $root->nsobj($nsid)
528             ->widget_by_name($root, $name);
529 21         200 return $widget;
530             }
531 1 50       3 return unless $dir->{cf_base_nsid};
532 1         2 $root->nsobj($dir->{cf_base_nsid})->widget_by_nsname($root, $ns, $name);
533             }
534              
535             sub YATT::Registry::NS::Dir::widget_by_name {
536 192     192 0 276 (my Dir $dir, my Root $root, my ($name)) = @_;
537 192         1148 $root->refresh($dir);
538 192 100       635 if (my $nsid = $dir->{Template}{$name}) {
539 175         326 $root->refresh($root->nsobj($nsid));
540             }
541 186 100 100     1520 $dir->{Widget}{$name}
542             || $dir->{cf_base_nsid}
543             && $root->nsobj($dir->{cf_base_nsid})->widget_by_name($root, $name);
544             }
545              
546             sub YATT::Registry::NS::Template::widget_by_nsname {
547 1     1 0 2 (my Template $tmpl, my Root $root, my ($ns, $name)) = @_;
548 1 50       3 if ($tmpl->{cf_name} eq $ns) {
549 0         0 my $widget = $tmpl->widget_by_name($root, $name);
550 0 0       0 return $widget if $widget;
551             }
552 1         3 my Dir $parent = $root->nsobj($tmpl->{cf_parent_nsid});
553 1 50 33     4 if (defined $parent->{cf_name} and $parent->{cf_name} eq $ns) {
554 0         0 my $widget = $tmpl->widget_by_name($root, $name);
555 0 0       0 return $widget if $widget;
556             }
557 1         2 $parent->widget_by_nsname($root, $ns, $name);
558             }
559              
560             sub YATT::Registry::NS::Template::widget_by_name {
561 103     103 0 166 (my Template $tmpl, my Root $root, my ($name)) = @_;
562 103         284 $root->refresh($tmpl);
563 103         111 my $widget;
564 103 100       408 $widget = $tmpl->{Widget}{$name}
565             and return $widget;
566              
567             # 同一ディレクトリのテンプレートを先に検索するため。
568             # XXX: しかし、継承順序に問題が出ているはず。
569 16 100       40 $widget = $root->nsobj($tmpl->{cf_parent_nsid})
570             ->widget_by_name($root, $name)
571             and return $widget;
572              
573 5 100       17 if ($tmpl->{cf_base_template}) {
574 3 50       8 $widget = $root->nsobj($tmpl->{cf_base_template})
575             ->widget_by_name($root, $name)
576             and return $widget;
577             }
578              
579 2 50       7 if ($tmpl->{cf_base_nsid}) {
580 0 0       0 $widget = $root->nsobj($tmpl->{cf_base_nsid})
581             ->widget_by_name($root, $name)
582             and return $widget;
583             }
584              
585 2         4 return;
586             }
587             }
588              
589             sub node_error {
590 18     18 0 55 (my Root $root, my ($node, $fmt)) = splice @_, 0, 3;
591 18 50       140 $root->node_error_obj($node
592             , error_fmt => ref $fmt ? join(" ", $fmt) : $fmt
593             , error_param => [@_]
594             , caller => [caller]);
595             }
596              
597             sub node_error_obj {
598 18     18 0 49 (my Root $root, my ($node, @param)) = @_;
599             # XXX: $root->{cf_backtrace} なら longmess も append, とか。
600             # XXX: Error オブジェクトにするべきかもね。でも依存は嫌。
601             # ← die を $root->raise で wrap すれば良い?
602 18         75 my $stringify = $root->checked(stringify => "(Can't stringify: %s)", $node);
603 18         58 my $filename = $root->checked(filename => "(Can't get filename %s)", $node);
604 18         81 my $linenum = $root->checked(linenum => "(Can't get linenum %s)", $node);
605 18         159 $root->Exception->new(@param
606             , node_obj => $node
607             , node => $stringify, file => $filename
608             , line => $linenum);
609             }
610              
611             sub node_nimpl {
612 0     0 0 0 (my Root $root, my ($node, $msg)) = @_;
613 0         0 my $caller = [my ($pack, $file, $line) = caller];
614 0   0     0 $root->node_error_obj($node
615             , error_fmt => join(' '
616             , ($msg || "Not yet implemented")
617             , "(perl file $file line $line)")
618             , caller => $caller);
619             }
620              
621             sub strip_ns {
622 811     811 0 799 (my Root $root, my ($list)) = @_;
623 811         1602 $root->shift_ns_by($root->{nspattern}, $list);
624             }
625              
626             sub shift_ns_by {
627 862     862 0 1095 (my Root $root, my ($pattern, $list)) = @_;
628 862 100       1420 return unless @$list;
629 854 50       1285 return unless defined $pattern;
630 854 100       1278 if (ref $pattern) {
631 809 100       4397 return unless $list->[0] =~ $pattern
632             } else {
633 45 100       150 return unless $list->[0] eq $pattern;
634             }
635 818         1635 shift @$list;
636             }
637              
638             #========================================
639              
640 4     4   18 use YATT::LRXML::Node qw(DECLARATOR_TYPE node_path create_node);
  4         5  
  4         219  
641             sub DEFAULT_WIDGET () {''}
642              
643 4     4   1765 use YATT::LRXML::MetaInfo;
  4         7  
  4         155  
644 4     4   1304 use YATT::Widget;
  4         8  
  4         149  
645              
646 4     4   1261 use YATT::LRXML; # for Builder.
  4         5  
  4         32  
647             use YATT::Types
648 4         24 ([WidgetBuilder => [qw(cf_widget ^cf_template cf_root_builder)]]
649             , -base => qw(YATT::LRXML::Builder)
650             , -alias => [Builder => __PACKAGE__ . '::WidgetBuilder'
651             , Scanner => 'YATT::LRXML::Scanner']
652 4     4   144 );
  4         5  
653              
654             # XXX: 名前が紛らわしい。lrxml tree の root か、Registry の root か、と。
655             sub new_root_builder {
656 156     156 0 268 (my Root $root, my $parser, my Scanner $scan) = @_;
657 156         494 my MetaInfo $meta = $parser->metainfo;
658 156         541 my Template $tmpl = $root->nsobj($meta->{cf_nsid});
659              
660 156         448 my $widget = $root->create_widget_in
661             ($tmpl, DEFAULT_WIDGET
662             , filename => $meta->cget('filename')
663             , decl_start => $scan->{cf_linenum}
664             , body_start => $scan->{cf_linenum} + $scan->number_of_lines);
665              
666             # 親ディレクトリに登録。
667 156         460 my Dir $parent = $root->nsobj($tmpl->{cf_parent_nsid});
668              
669 156         505 $parent->{Widget}{$tmpl->{cf_name}} = $widget;
670              
671 156         521 $parser->configure(tree => my $sink = $widget->cget('root'));
672              
673 156         1870 $root->Builder->new($sink, undef
674             , widget => $widget
675             , template => $tmpl
676             , startpos => 0
677             , startline => $scan->{cf_linenum}
678             , linenum => $scan->{cf_linenum});
679             }
680              
681             sub fake_cursor_from {
682 19     19 0 42 (my MY $trans, my ($cursor, $node, $is_opened)) = @_;
683 19         82 my $parent = $cursor->Path->new($node, $cursor->cget('path'));
684 19 100       98 my $path = $is_opened ? $parent
685             : $cursor->Path->new($trans->create_node(unknown => undef, $node)
686             , $parent);
687 19         59 $cursor->clone($path);
688             }
689              
690             sub fake_cursor {
691 194     194 0 413 (my MY $gen, my Widget $widget, my ($metainfo)) = splice @_, 0, 3;
692 194         554 my $cursor = $widget->cursor(metainfo => $metainfo);
693 194         744 my $node = $gen->create_node(unknown => undef, @_);
694 194         857 $cursor->clone($cursor->Path->new($node, $cursor->cget('path')));
695             }
696              
697             sub fake_cursor_to_build {
698 184     184 0 273 (my MY $root, my Builder $builder, my Scanner $scan
699             , my ($elem)) = @_;
700 184         818 $root->fake_cursor($builder->{cf_widget}
701             , $builder->{cf_template}->metainfo
702             ->clone(startline => $scan->{cf_linenum})
703             , $elem);
704             }
705              
706             sub new_decl_builder {
707 183     183 0 369 (my MY $root, my Builder $builder, my Scanner $scan
708             , my ($elem, $parser)) = @_;
709 183         342 foreach my $shift (0, 1) {
710 366         826 my $path = [node_path($elem)];
711 366 100       923 $root->strip_ns($path) if $shift;
712 366         677 my $handler_name = join("_", declare => @$path);
713              
714 366 100       1511 if (my $handler = $root->can($handler_name)) {
715 181         523 my $nc = $root->fake_cursor_to_build($builder, $scan, $elem)->open;
716 181         1079 return $handler->($root, $builder, $scan, $nc, $parser);
717             }
718             }
719              
720 2         10 die $root->node_error($root->fake_cursor_to_build($builder, $scan, $elem)
721             , "Unknown declarator");
722             }
723              
724             sub declare_base {
725 4     4 0 9 (my Root $root, my Builder $builder, my ($scan, $args, $parser)) = @_;
726 4 50       15 if ($builder->{parent}) {
727 0         0 die $scan->token_error("Misplaced yatt:base");
728             }
729 4         14 my $path = $args->node_body;
730 4         8 my Template $this = $builder->{cf_template};
731 4 50       16 my Template $base = $this->lookup_template($root, $path)
732             or die $scan->token_error("Can't find template $path");
733              
734             # XXX: refresh は lookup_template の中ですべきか?
735 4         13 $root->refresh($base);
736              
737             # 名前は保存しなくていいの?
738 4         12 $this->{cf_base_template} = $base->{cf_nsid};
739              
740 4         14 $root->add_isa($root->get_package($this)
741             , $root->get_package($base));
742              
743             # builder を返すことを忘れずに。
744 4         62 $builder;
745             }
746              
747             sub declare_args {
748 87     87 0 287 (my Root $root, my Builder $builder
749             , my ($scan, $nc, $parser, @configs)) = @_;
750 87 50       324 if ($builder->{parent}) {
751 0         0 die $scan->token_error("Misplaced yatt:args");
752             }
753             # widget -> args の順番で出現する場合もある。
754             # root 用の builder を取り出し直す
755 87 100       246 if ($builder->{cf_root_builder}) {
756 2         5 $builder = $builder->{cf_root_builder};
757             }
758 87         137 my Widget $widget = $builder->{cf_widget};
759 87         174 $widget->{cf_declared} = 1;
760 87         131 $widget->{cf_decl_start} = $scan->{cf_last_linenum};
761 87         226 $widget->{cf_body_start} = $scan->{cf_last_linenum} + $scan->{cf_last_nol};
762 87 50       170 $widget->configure(@configs) if @configs;
763 87         274 $root->define_args($widget, $nc);
764 87         364 $root->after_define_args($widget);
765 87         1117 $builder;
766             }
767              
768             sub declare_params {
769 0     0 0 0 shift->declare_args(@_, public => 1);
770             }
771              
772             sub declare_widget {
773 90     90 0 219 (my Root $root, my Builder $builder, my Scanner $scan
774             , my ($args, $parser)) = @_;
775              
776 90 100       240 if ($builder->{parent}) {
777 1         11 die $root->node_error($root->fake_cursor_to_build($builder, $scan
778             , $builder->product)
779             , "Misplaced yatt:widget in:");
780             }
781              
782 89 50       282 defined (my $name = $args->node_name)
783             or die $root->node_error($args, "widget name is missing");
784              
785             # XXX: filename, lineno
786 89         386 my Widget $widget = $root->create_widget_in
787             ($builder->{cf_template}, $name
788             , declared => 1
789             , filename => $builder->{cf_template}->metainfo->cget('filename')
790             , decl_start => $scan->{cf_last_linenum}
791             , body_start => $scan->{cf_last_linenum} + $scan->{cf_last_nol});
792              
793 89         315 $root->define_args($widget, $args->go_next);
794 88         324 $root->after_define_args($widget);
795              
796 88   66     442 $root->Builder->new($widget->cget('root'), undef
797             , widget => $widget
798             , template => $builder->{cf_template}
799             , startpos => $scan->{cf_index}
800             , startline => $scan->{cf_linenum}
801             , linenum => $scan->{cf_linenum}
802             # widget -> args に戻るためには root_builder を
803             # 渡さねばならぬ
804             , root_builder =>
805             $builder->{cf_root_builder} || $builder
806             );
807             }
808              
809             sub create_widget_in {
810 245     245 0 516 (my Root $root, my Template $tmpl, my ($name)) = splice @_, 0, 3;
811 245         1279 my $widget = YATT::Widget->new
812             (name => $name, template_nsid => $tmpl->{cf_nsid}
813             , @_);
814 245         690 $tmpl->{Widget}{$name} = $widget;
815 245         252 push @{$tmpl->{widget_list}}, $widget;
  245         509  
816 245         371 $widget;
817             }
818              
819             sub current_parser {
820 0     0 0 0 my Root $root = shift;
821 0         0 $root->{current_parser}[0];
822             }
823              
824 2     2 0 2 sub after_define_args {shift; shift}
  2         2  
825              
826             sub define_args {
827 194     194 0 330 (my Root $root, my ($target, $args)) = @_;
828              
829             # $target は has_arg($name) と add_arg($name, $arg) を実装しているもの。
830             # *: widget
831             # *: codevar
832              
833 194         644 for (; $args->readable; $args->next) {
834             # マクロ引数呼び出し %name(); がここで出現
835             # comment も現れうる。
836             # body = [code title=html] みたいなグループ引数もここで。
837              
838 309 100       748 my $sub = $root->can("add_decl_" . $args->node_type_name)
839             or next;
840              
841 290         624 $sub->($root, $target, $args);
842             }
843              
844             # おまけ。使わないけど、デバッグ時に少し幸せ。
845 193         327 $root;
846             }
847              
848             sub add_decl_attribute {
849 239     239 0 348 (my Root $root, my ($target, $args)) = @_;
850 239         529 my $argname = $args->node_name;
851 239 50       473 unless (defined $argname) {
852 0         0 die $root->node_error($args, "Undefined att name!");
853             }
854 239 50       570 if ($target->has_arg($argname)) {
855 0         0 die $root->node_error($args, "Duplicate argname: $argname");
856             }
857              
858 239         601 my ($type, @param) = $args->parse_typespec;
859 239         291 my ($typename, $subtype) = do {
860 239 100       434 if (ref $type) {
861 3         9 ($type->[0], [@{$type}[1 .. $#$type]])
  3         7  
862             } else {
863 236         366 ($type, undef);
864             }
865             };
866 239 100 100     1311 if (defined $typename and my $sub = $root->can("attr_declare_$typename")) {
867 7         38 $sub->($root, $target, $args, $argname, $subtype, @param);
868             } else {
869 232         499 $target->add_arg($argname, $root->create_var($type, $args, @param));
870             }
871             }
872              
873             sub create_var {
874 427     427 0 953 (my Root $root, my ($type, $args, @param)) = @_;
875 427 100       819 $type = '' unless defined $type;
876 427 100       739 my ($primary, @subtype) = ref $type ? @$type : $type;
877 427 50       1300 defined (my $class = $root->{cf_type_map}{$primary})
878             or croak $root->node_error($args, "No such type: %s", $primary);
879 427 50       736 unshift @param, subtype => @subtype >= 2 ? \@subtype : $subtype[0]
    100          
880             if @subtype;
881 427 100       1687 if (my $sub = $root->can("create_var_$primary")) {
882 172         450 $sub->($root, $args, @param);
883             } else {
884 255         1251 $class->new(@param);
885             }
886             }
887              
888             #========================================
889             {
890 4     4   17 package YATT::Registry::Loader; use YATT::Inc;
  4         6  
  4         17  
891 4     4   12 use base qw(YATT::Class::Configurable);
  4         7  
  4         227  
892 4     4   16 use YATT::Fields qw(Cache);
  4         5  
  4         12  
893 4     4   16 use Carp;
  4         3  
  4         164  
894 4     4   15 use YATT::Registry::NS;
  4         5  
  4         685  
895              
896             sub DIR () { 'YATT::Registry::Loader::DIR' }
897              
898             sub handle_refresh {
899 222     222 0 268 (my MY $loader, my Root $root, my NS $node) = @_;
900 222         641 my $type = $node->type_name;
901 222 50       994 if (my $sub = $loader->can("refresh_$type")) {
902 222         518 $sub->($loader, $root, $node);
903             } else {
904 0         0 confess "Can't refresh type: $type";
905             }
906             }
907              
908             sub is_modified {
909 222     222 0 270 my MY $loader = shift;
910 222         393 my ($item, $old) = @_;
911 222         540 my $mtime = $loader->mtime($item);
912 222 100 100     750 return if defined $old and $old >= $mtime;
913 199         542 $_[1] = $mtime;
914 199         563 return 1;
915             }
916              
917             package YATT::Registry::Loader::DIR;
918              
919 4     4   15 use base qw(YATT::Registry::Loader File::Spec);
  4         5  
  4         388  
920 4     4   14 use YATT::Fields qw(cf_DIR cf_LIB);
  4         6  
  4         14  
921 20     20   64 sub initargs { qw(cf_DIR) }
922             sub init {
923 20     20   45 my ($self, $dir) = splice @_, 0, 2;
924 20         93 $self->SUPER::init($dir, @_);
925 20 100       383 if (-d (my $libdir = "$dir/lib")) {
926 1         5 require lib; import lib $libdir
  1         8  
927             }
928 20         159 $self;
929             }
930              
931 4     4   15 use YATT::Registry::NS;
  4         7  
  4         190  
932 4     4   21 use YATT::Util;
  4         5  
  4         401  
933 4     4   17 use YATT::Util::Taint;
  4         4  
  4         442  
934              
935 222     222   212 sub mtime { shift; (stat(shift))[9]; }
  222         6311  
936              
937             sub RCFILE () {'.htyattrc'}
938             sub Parser () {'YATT::LRXML::Parser'}
939              
940 4     4   16 use Carp;
  4         4  
  4         3488  
941              
942             sub checked_read_file {
943 17     17   40 (my MY $loader, my ($fn, $layer)) = @_;
944 17 50       60 croak "Given path is tainted! $fn" if is_tainted($fn);
945 17 50 50     590 open my $fh, '<' . ($layer || ''), $fn
946             or die "Can't open $fn! $!";
947 17         63 local $/;
948 17         367 scalar <$fh>;
949             }
950              
951             sub refresh_Dir {
952 64     64   118 (my MY $loader, my Root $root, my Dir $dir) = @_;
953 64         92 my $dirname = $dir->{cf_loadkey};
954             # ファイルリストの処理.
955 64 100       210 return unless $loader->is_modified($dirname, $dir->{cf_mtime}{$dirname});
956              
957 43         97 my $is_reload = $dir->{cf_age}++;
958 43         66 undef $dir->{is_loaded};
959              
960 43 50       122 if (is_tainted($dirname)) {
961 0         0 croak "Directory $dirname is tainted"
962             }
963              
964 43 100       92 if ($root == $dir) {
965 21 50       44 foreach my $d ($dirname, map {!defined $_ ? () : ref $_ ? @$_ : $_}
  21 100       95  
966             $loader->{cf_LIB}) {
967 38         105 $loader->load_dir($root, $dir, $d);
968             }
969             } else {
970 22         106 $loader->load_dir($root, $dir, $dirname);
971             }
972              
973             # RC 読み込みの前に、 default_base_class を設定。
974 43 100 100     161 if ($root->{cf_default_base_class}
      66        
975             and ($root->{cf_default_base_class} ne $root->{cf_pkg}
976             or $root->{is_loaded})) {
977             # XXX: add_isa じゃなくて ensure_isa だね。
978             #print STDERR "loading default_base_class $root->{cf_default_base_class}"
979             # . " for dir $dirname\n";
980 4         577 $root->checked_eval(qq{require $root->{cf_default_base_class}});
981 4         11 $root->add_isa(my $pkg = $root->get_package($dir)
982             , $root->{cf_default_base_class});
983             }
984              
985             # RC 読み込みは、最後に
986 43         386 my $rcfile = $loader->catfile($dirname, $loader->RCFILE);
987 43 100       803 if (-r $rcfile) {
988 17         32 my $script = "";
989 17 100       50 $script .= ";no warnings 'redefine';" if $is_reload;
990 17 100       74 $script .= sprintf(qq{\n#line 1 "%s"\n}, $rcfile)
991             unless $root->{cf_no_lineinfo};
992 17         54 $script .= untaint_any($loader->checked_read_file($rcfile));
993 17         55 &YATT::break_rc;
994             $root->with_reloading_flag
995             ($is_reload, sub {
996 17     17   57 $root->eval_in_dir($dir, $script);
997 17         122 });
998 17         74 &YATT::break_after_rc;
999              
1000 17         63 $dir->after_rc_loaded($root);
1001             }
1002              
1003 43         93 $dir;
1004             }
1005              
1006             sub load_dir {
1007 60     60   85 (my MY $loader, my Root $root, my Dir $dir, my ($dirname)) = @_;
1008 60         110 local *DIR;
1009 60 50       1321 opendir DIR, $dirname or die "Can't open dir '$dirname': $!";
1010 60         1207 while (my $name = readdir(DIR)) {
1011 542 100       1404 next if $name =~ /^\./;
1012 391         2227 my $path = $loader->catfile($dirname, $name);
1013             # entry を作るだけ。load はしない。→ mtime も、子供側で。
1014 391 100       5694 if (-d $path) {
    100          
1015 96 50       474 next unless $name =~ /^(?:\w|-)+$/; # Not CC for future widechar.
1016 96   33     679 $dir->{Dir}{$name} ||= $loader->{Cache}{$path}
      66        
1017             ||= $root->createNS(Dir => name => $name
1018             , loadkey => untaint_any($path)
1019             , parent_nsid => $dir->{cf_nsid}
1020             , base_nsid => $dir->{cf_base_nsid}
1021             );
1022             } elsif ($name =~ /^(\w+)\.html?$/) { # XXX: Should allow '-'.
1023 163   33     1238 $dir->{Template}{$1} ||= $loader->{Cache}{$path}
      66        
1024             ||= $root->createNS(Template => name => $1
1025             , loadkey => untaint_any($path)
1026             , parent_nsid => $dir->{cf_nsid}
1027             , base_nsid => $dir->{cf_base_nsid}
1028             );
1029             }
1030             }
1031             # XXX: 無くなったファイルの開放は?
1032 60         1687 closedir DIR;
1033             }
1034              
1035             sub refresh_Template {
1036 158     158   206 (my MY $loader, my Root $root, my Template $tmpl) = @_;
1037 158         254 my $path = $tmpl->{cf_loadkey};
1038 158 100       815 unless ($loader->is_modified($path, $tmpl->{cf_mtime}{$path})) {
1039 2 50       8 print STDERR "refresh_Template: not modified: $path\n"
1040             if $root->{cf_debug_registry};
1041 2         4 return;
1042             }
1043              
1044 156 50       621 if (is_tainted($path)) {
1045 0         0 croak "template path $path is tainted";
1046             }
1047              
1048 156 100       707 if (my $cleaner = $root->can("forget_template")) {
1049 146         424 $cleaner->($root, $tmpl);
1050             }
1051              
1052 156         361 my $is_reload = $tmpl->{cf_age}++;
1053 156         267 undef $tmpl->{is_loaded};
1054              
1055 156         372 $root->add_isa(my $pkg = $root->get_package($tmpl)
1056             , $root->get_package($tmpl->{cf_parent_nsid}));
1057 156 50       360 foreach my $name (map {defined $_ ? @$_ : ()}
  156         580  
1058             $root->{cf_template_global}) {
1059 0         0 *{globref($pkg, $name)} = *{globref($root->{cf_app_prefix}, $name)};
  0         0  
  0         0  
1060             }
1061              
1062             # XXX: There can be a race. (mtime vs open)
1063 156         666 my $parser = $loader->call_type
1064             (Parser => new => untaint => 1
1065             , registry => $root
1066             , special_entities => $root->{cf_special_entities});
1067 156         515 local $root->{current_parser}[0] = $parser;
1068              
1069 156 50       6857 open my $fh, '<', $path or die "Can't open $path";
1070              
1071 156         917 $tmpl->{cf_metainfo} = $parser->configure_metainfo
1072             (nsid => $tmpl->{cf_nsid}
1073             , namespace => $root->namespace
1074             , filename => $path);
1075              
1076 156         608 $tmpl->{tree} = $parser->parse_handle($fh);
1077              
1078             # XXX: ついでに を解釈. ← parser に前倒し。
1079             # $root->process_declarations($tmpl);
1080             }
1081             }
1082              
1083             #========================================
1084              
1085             sub _lined {
1086 0     0   0 my $i = 1;
1087 0         0 my $result;
1088 0         0 foreach my $line (split /\n/, $_[0]) {
1089 0 0       0 if ($line =~ /^\#line (\d+)/) {
1090 0         0 $i = $1;
1091 0         0 $result .= $line . "\n";
1092             } else {
1093 0         0 $result .= sprintf "% 3d %s\n", $i++, $line;
1094             }
1095             }
1096             $result
1097 0         0 }
1098              
1099             1;