# -*- mode: perl; coding: utf-8 -*- package YATT::Registry; use strict; use warnings FATAL => qw(all); use Carp; use UNIVERSAL; # Debugging aid. require YATT; use YATT::Exception; { package YATT::Registry::NS; use YATT::Inc; BEGIN {require Exporter; *import = \&Exporter::import} use base qw(YATT::Class::Configurable); use YATT::Fields qw(Widget cf_nsid cf_parent_nsid cf_base_nsid cf_pkg cf_special_entities cf_name cf_vpath cf_loadkey cf_mtime cf_age ^is_loaded ); # When fields is empty, %FIELDS doesn't blessed. # This causes "Pseudo-hashes are deprecated" use YATT::Types ([Dir => [qw(cf_base_template)] , 'Dir' , [Template => [qw(tree cf_base_template ^widget_list ^cf_metainfo)]] ] , -base => [NS => __PACKAGE__] , -alias => [Root => 'YATT::Registry' , Registry => 'YATT::Registry'] , -default => [loader => 'YATT::Registry::Loader'] , -debug => $ENV{YATT_DEBUG_TYPES} , qw(:type_name :export_alias) ); } use YATT::Util qw(checked_eval checked lsearch); use YATT::Util::Taint; use YATT::Registry::NS; use YATT::Util::Symbol; use base Dir; use YATT::Fields qw(^Loader NS last_nsid cf_auto_reload cf_type_map cf_debug_registry cf_rc_global cf_template_global cf_no_lineinfo current_parser cf_default_base_class cf_use loading nspattern ) , ['^cf_namespace' => qw(yatt perl)] , ['^cf_app_prefix' => "::"] ; sub new { my $nsid = 0; my Root $root = shift->SUPER::new(@_, vpath => '/', nsid => $nsid); if (defined $ENV{YATT_CF_LINEINFO}) { $root->{cf_no_lineinfo} = not $ENV{YATT_CF_LINEINFO}; } # $root->{NS}{$nsid} = $root; # ← サイクルするってば。 # 一回、空呼び出し。 $root->get_package($root); # root は new 時に強制 refresh. # after_configure だと、configure の度なので、new のみに。 $root->refresh($root); # Now safe to lift @ISA. $root->{is_loaded} = 1; $root; } sub configure_loader { (my Root $root, my ($desc)) = @_; my ($type, $loadkey, @args) = @$desc; $root->{Loader} = $root->default_loader->$type->new($loadkey, @args); $root->{cf_loadkey} = $loadkey; } sub configure_DIR { (my Root $root, my ($dir)) = @_; $root->{Loader} = $root->default_loader->DIR->new($dir); $root->{cf_loadkey} = $dir; } sub after_configure { (my Root $root) = @_; my $nspat = join("|" , @{$root->namespace}); $root->{nspattern} = qr{^(?:$nspat)$}; } #======================================== # use YATT::Registry ** => ** 系. { our Root $ROOT; our NS $CURRENT; sub eval_in_dir { # XXX: should take care for variable capture. (my Root $root, my NS $target, my ($script, @args)) = @_; if (is_tainted($script)) { confess "script is tainted: $script\n"; } my $targetClass = $root->get_package($target); my $prog = "package $targetClass;" . " use strict;" . " use warnings FATAL => qw(all);" . " $script"; local @_ = (@args); local ($ROOT, $CURRENT) = ($root, $target); &YATT::break_eval; my @result; if (wantarray) { @result = eval $prog; } else { $result[0] = eval $prog; } # XXX: $prog をどう見せたいかが、状況で色々変化する。 die $@ if $@; wantarray ? @result : $result[0]; } sub import { my $modpack = shift; my $callpack = caller; $modpack->install_builtins($callpack); return unless @_; croak "Odd number of arguments for 'use $modpack @_'" if @_ % 2; my $fields = $CURRENT->fields_hash; while (my ($name, $value) = splice @_, 0, 2) { if (my $sub = $modpack->can("import_$name")) { $sub->($modpack, $callpack, $value); } elsif ($sub = $CURRENT->can("configure_$name")) { $sub->($CURRENT, $value); } elsif ($fields->{"cf_$name"}) { $CURRENT->{"cf_$name"} = $value; } else { croak "Unknown YATT::Registry parameter: $name"; } } } # Root 以外の Dir では、こちらが呼ばれる(はず) sub import_base { croak "Can't find current registry" unless defined $ROOT; my ($modpack, $targetClass, $vpath) = @_; my Dir $dir = $CURRENT->lookup_dir($ROOT, split '/', $vpath) or croak "Can't find directory: $vpath"; $CURRENT->{cf_base_nsid} = $dir->{cf_nsid}; lift_isa_to($ROOT->get_package($dir), $targetClass); } } # これが呼ばれるのは Root の時だけ。 sub configure_base { (my MY $root, my $realdir) = @_; unless (-d $realdir) { croak "No such directory for base='$realdir'"; } my $base_nsid = $root->createNS (Dir => loadkey => untaint_any($realdir)); $root->{cf_base_nsid} = $base_nsid; lift_isa_to($root->get_package(my $base = $root->nsobj($base_nsid)) , $root->get_package($root)); $root->refresh($base); $root; } #---------------------------------------- { our $IS_RELOADING; sub is_reloading { $IS_RELOADING } sub with_reloading_flag { (my Root $root, my ($flag, $sub)) = @_; local $IS_RELOADING = $flag; $sub->(); } } #---------------------------------------- sub Entity (*$) { my ($name, $sub) = @_; my ($instClass) = caller; my $glob = globref($instClass, "entity_$name"); if (MY->is_reloading and defined *{$glob}{CODE}) { # To avoid 'Subroutine MyApp5::entity_bar redefined'. undef *$glob; } *$glob = $sub; } sub ElementMacro (*$) { my ($name, $sub) = @_; my ($instClass) = caller; *{globref($instClass, "macro_$name")} = $sub; } sub list_builtins { qw(Entity ElementMacro) } sub install_builtins { my ($modpack, $destpack) = @_; foreach my $name ($modpack->list_builtins) { my $sub = $modpack->can($name) or die "Can't find builtin: $name"; *{globref($destpack, $name)} = $sub; } } #======================================== sub next_nsid { my Root $root = shift; ++$root->{last_nsid}; } sub createNS { (my Root $root, my ($type)) = splice @_, 0, 2; # class_id は? my $nsid = $root->next_nsid; my NS $nsobj = $root->{NS}{$nsid} = $root->$type->new(nsid => $nsid, @_); my $pkg = $root->get_package($nsobj); foreach my $name (map {defined $_ ? @$_ : ()} $root->{cf_rc_global}) { *{globref($pkg, $name)} = *{globref($root->{cf_app_prefix}, $name)}; } $nsid; } sub nsobj { (my Root $root, my ($nsid)) = @_; unless (defined $nsid) { croak "nsobj: undefined nsid!"; } return $root if $nsid == 0; $root->{NS}{$nsid}; } sub get_package { (my Root $root, my NS $nsobj, my ($sep)) = @_; # nsid のまま渡しても良いように。 $nsobj = $root->nsobj($nsobj) unless ref $nsobj; $nsobj->{cf_pkg} ||= do { my $pkg = do { if ($root == $nsobj) { $root->{cf_app_prefix} || '::' } else { join $sep || "::" , $root->{cf_app_prefix} || '::' , sprintf '%.1s%d', $nsobj->type_name , $nsobj->{cf_nsid}; } }; $root->checked_eval(qq{package $pkg}); $pkg; }; } sub refresh { (my Root $root, my NS $node) = @_; $node ||= $root; return unless $node->{cf_loadkey}; return if $node->{cf_age} and not $root->{cf_auto_reload}; return unless $root->{Loader}; # age があるのに、 is_loaded に達してない == まだ構築の途中。 return if $node->{cf_age} and not $node->{is_loaded}; $root->{loading}{$node->{cf_nsid}} = 1; print STDERR "Referesh: $node->{cf_loadkey}\n" if $root->{cf_debug_registry}; $root->{Loader}->handle_refresh($root, $node); $node->{is_loaded} = 1; delete $root->{loading}{$node->{cf_nsid}}; } sub mark_load_failure { my Root $root = shift; while ((my $nsid, undef) = each %{$root->{loading}}) { my NS $ns = $root->nsobj($nsid); # 仮に、一度は load 済みだとする。 $ns->{is_loaded} = 1; delete $root->{loading}{$nsid}; } } sub get_ns { (my Root $root, my ($elempath)) = @_; $root->vivify_ns($root, @$elempath); } sub get_package_from_node { (my Root $root, my ($node)) = @_; my Dir $dir = $root->get_dir_from_node($node); $root->get_package($dir); } sub get_dir_from_node { (my Root $root, my ($node)) = @_; my Template $tmpl = $root->get_template_from_node($node); $root->nsobj($tmpl->{cf_parent_nsid}); } sub get_template_from_node { (my Root $root, my ($node)) = @_; $root->nsobj($node->metainfo->cget('nsid')); } sub get_widget { my Root $root = shift; $root->get_widget_from_dir($root, @_); } sub get_widget_from_template { (my Root $root, my Template $tmpl, my ($nsname)) = splice @_, 0, 3; my $widget; # Relative lookup. ($nsname case is for [delegate]) $widget = $tmpl->lookup_widget($root, @_ ? @_ : $nsname) and return $widget; # Absolute, ns-specific lookup. if ($root->has_ns($root, $nsname)) { $widget = $root->get_widget_from_dir($root, $nsname, @_) and return $widget; } # Absolute, general lookup. return $root->get_widget_from_dir($root, @_); } sub get_widget_from_dir { (my Root $root, my Dir $dir) = splice @_, 0, 2; my @elempath = @_; $dir = $dir->vivify_ns($root, splice @elempath, 0, @elempath - 2); unless ($dir) { croak "Can't find widget: ", join(":", @_); } if (@elempath == 2) { $dir->widget_by_nsname($root, @elempath); } elsif (@elempath == 1) { $dir->widget_by_name($root, @elempath); } else { return; } } { sub YATT::Registry::NS::list_declared_widget_names { (my NS $tmpl) = @_; my @result; foreach my $name (keys %{$tmpl->{Widget}}) { my $w = $tmpl->{Widget}{$name}; next unless $w->declared; push @result, $name; } @result; } # For relative lookup. sub YATT::Registry::NS::Template::lookup_widget { (my Template $tmpl, my Root $root) = splice @_, 0, 2; croak "lookup_widget: argument type mismatch for \$root." unless defined $root and ref $root and $root->isa(Root); return unless @_; foreach my NS $start ($tmpl, $root->nsobj($tmpl->{cf_parent_nsid})) { my @elempath = @_; my NS $ns = do { if (@elempath <= 2) { $start; } else { $start->lookup_dir($root, splice @elempath, 0, @elempath - 2); } }; my $found = do { if (@elempath == 2) { $ns->widget_by_nsname($root, @elempath); } else { $ns->widget_by_name($root, @elempath); } }; return $found if $found; } } sub YATT::Registry::NS::Template::lookup_template { (my Template $tmpl, my Root $root, my ($name)) = @_; $root->nsobj($tmpl->{cf_parent_nsid})->lookup_template($root, $name) } sub YATT::Registry::NS::Template::lookup_dir { (my Template $tmpl, my Root $root) = splice @_, 0, 2; $root->nsobj($tmpl->{cf_parent_nsid})->lookup_dir($root, @_); } sub YATT::Registry::NS::Dir::has_ns { (my Dir $dir, my Root $root, my ($nsname)) = @_; my $nsid; $nsid = $dir->{Dir}{$nsname} || $dir->{Template}{$nsname} and return $root->nsobj($nsid); return unless $dir->{cf_base_nsid}; $root->nsobj($dir->{cf_base_nsid})->has_ns($root, $nsname); } sub YATT::Registry::NS::Dir::lookup_template { (my Dir $dir, my Root $root, my ($name)) = @_; my $nsid; while (not($nsid = $dir->{Template}{$name}) and $dir->{cf_base_nsid}) { $dir = $root->nsobj($dir->{cf_base_nsid}); $root->refresh($dir); } return unless $nsid; $root->nsobj($nsid); } use Carp; sub YATT::Registry::NS::Dir::lookup_dir { (my Dir $dir, my Root $root, my (@nspath)) = @_; croak "argtype mismatch! not a Root." unless UNIVERSAL::isa($root, Root); return $root unless @nspath; (my Dir $start, my (@orig)) = ($dir, @nspath); $root->refresh($dir); while ($dir and defined (my $ns = shift @nspath)) { $dir = $root and next if $ns eq ''; my $nsid = $dir->{Dir}{$ns}; unless ($nsid) { return $start->{cf_base_nsid} ? $root->nsobj($start->{cf_base_nsid})->lookup_dir($root, @orig) : undef; } $dir = $root->nsobj($nsid); $root->refresh($dir); } $dir; } sub YATT::Registry::NS::Dir::list_ns { (my Dir $dir, my ($dict)) = @_; $dict ||= {}; my @list; foreach my $type (qw(Template Dir)) { foreach my $key (keys %{$dir->{$type}}) { push @list, $key unless $dict->{$key}++; } } wantarray ? @list : \@list; } sub YATT::Registry::NS::Dir::vivify_ns { (my Dir $dir, my Root $root, my (@nspath)) = @_; my @orig = @nspath; while (@nspath) { $root->refresh($dir); $dir = do { my $ns = shift @nspath; my Dir $d = $dir; my $nsid; while (not($nsid = $d->{Dir}{$ns}) and not($nsid = $d->{Template}{$ns}) and $d->{cf_base_nsid}) { $d = $root->nsobj($d->{cf_base_nsid}); $root->refresh($d); } unless ($nsid) { croak "No such ns '$ns': " . join ":", @orig; } $root->nsobj($nsid); }; } $dir; } sub YATT::Registry::NS::Dir::after_rc_loaded { (my Dir $dir, my Root $root) = @_; if (defined(my $base = $dir->{cf_base_nsid})) { foreach my Template $tmpl (map {$root->nsobj($_)} values %{$dir->{Template}}) { $tmpl->{cf_base_nsid} = $base; } } } sub YATT::Registry::NS::Dir::widget_by_nsname { (my Dir $dir, my Root $root, my ($ns, $name)) = @_; $root->refresh($dir); if (defined $dir->{cf_name} and $dir->{cf_name} eq $ns) { my $widget = $dir->widget_by_name($root, $name); return $widget if $widget; } # [1] dir:template # [2] template:widget foreach my $type (qw(Dir Template)) { next unless my $nsid = $dir->{$type}{$ns}; next unless my $widget = $root->nsobj($nsid) ->widget_by_name($root, $name); return $widget; } return unless $dir->{cf_base_nsid}; $root->nsobj($dir->{cf_base_nsid})->widget_by_nsname($root, $ns, $name); } sub YATT::Registry::NS::Dir::widget_by_name { (my Dir $dir, my Root $root, my ($name)) = @_; $root->refresh($dir); if (my $nsid = $dir->{Template}{$name}) { $root->refresh($root->nsobj($nsid)); } $dir->{Widget}{$name} || $dir->{cf_base_nsid} && $root->nsobj($dir->{cf_base_nsid})->widget_by_name($root, $name); } sub YATT::Registry::NS::Template::widget_by_nsname { (my Template $tmpl, my Root $root, my ($ns, $name)) = @_; if ($tmpl->{cf_name} eq $ns) { my $widget = $tmpl->widget_by_name($root, $name); return $widget if $widget; } my Dir $parent = $root->nsobj($tmpl->{cf_parent_nsid}); if (defined $parent->{cf_name} and $parent->{cf_name} eq $ns) { my $widget = $tmpl->widget_by_name($root, $name); return $widget if $widget; } $parent->widget_by_nsname($root, $ns, $name); } sub YATT::Registry::NS::Template::widget_by_name { (my Template $tmpl, my Root $root, my ($name)) = @_; $root->refresh($tmpl); my $widget; $widget = $tmpl->{Widget}{$name} and return $widget; # 同一ディレクトリのテンプレートを先に検索するため。 # XXX: しかし、継承順序に問題が出ているはず。 $widget = $root->nsobj($tmpl->{cf_parent_nsid}) ->widget_by_name($root, $name) and return $widget; if ($tmpl->{cf_base_template}) { $widget = $root->nsobj($tmpl->{cf_base_template}) ->widget_by_name($root, $name) and return $widget; } if ($tmpl->{cf_base_nsid}) { $widget = $root->nsobj($tmpl->{cf_base_nsid}) ->widget_by_name($root, $name) and return $widget; } return; } } sub node_error { (my Root $root, my ($node, $fmt)) = splice @_, 0, 3; $root->node_error_obj($node , error_fmt => ref $fmt ? join(" ", $fmt) : $fmt , error_param => [@_] , caller => [caller]); } sub node_error_obj { (my Root $root, my ($node, @param)) = @_; # XXX: $root->{cf_backtrace} なら longmess も append, とか。 # XXX: Error オブジェクトにするべきかもね。でも依存は嫌。 # ← die を $root->raise で wrap すれば良い? my $stringify = $root->checked(stringify => "(Can't stringify: %s)", $node); my $filename = $root->checked(filename => "(Can't get filename %s)", $node); my $linenum = $root->checked(linenum => "(Can't get linenum %s)", $node); $root->Exception->new(@param , node_obj => $node , node => $stringify, file => $filename , line => $linenum); } sub node_nimpl { (my Root $root, my ($node, $msg)) = @_; my $caller = [my ($pack, $file, $line) = caller]; $root->node_error_obj($node , error_fmt => join(' ' , ($msg || "Not yet implemented") , "(perl file $file line $line)") , caller => $caller); } sub strip_ns { (my Root $root, my ($list)) = @_; $root->shift_ns_by($root->{nspattern}, $list); } sub shift_ns_by { (my Root $root, my ($pattern, $list)) = @_; return unless @$list; return unless defined $pattern; if (ref $pattern) { return unless $list->[0] =~ $pattern } else { return unless $list->[0] eq $pattern; } shift @$list; } #======================================== use YATT::LRXML::Node qw(DECLARATOR_TYPE node_path create_node); sub DEFAULT_WIDGET () {''} use YATT::LRXML::MetaInfo; use YATT::Widget; use YATT::LRXML; # for Builder. use YATT::Types ([WidgetBuilder => [qw(cf_widget ^cf_template cf_root_builder)]] , -base => qw(YATT::LRXML::Builder) , -alias => [Builder => __PACKAGE__ . '::WidgetBuilder' , Scanner => 'YATT::LRXML::Scanner'] ); # XXX: 名前が紛らわしい。lrxml tree の root か、Registry の root か、と。 sub new_root_builder { (my Root $root, my $parser, my Scanner $scan) = @_; my MetaInfo $meta = $parser->metainfo; my Template $tmpl = $root->nsobj($meta->{cf_nsid}); my $widget = $root->create_widget_in ($tmpl, DEFAULT_WIDGET , filename => $meta->cget('filename') , decl_start => $scan->{cf_linenum} , body_start => $scan->{cf_linenum} + $scan->number_of_lines); # 親ディレクトリに登録。 my Dir $parent = $root->nsobj($tmpl->{cf_parent_nsid}); $parent->{Widget}{$tmpl->{cf_name}} = $widget; $parser->configure(tree => my $sink = $widget->cget('root')); $root->Builder->new($sink, undef , widget => $widget , template => $tmpl , startpos => 0 , startline => $scan->{cf_linenum} , linenum => $scan->{cf_linenum}); } sub fake_cursor_from { (my MY $trans, my ($cursor, $node, $is_opened)) = @_; my $parent = $cursor->Path->new($node, $cursor->cget('path')); my $path = $is_opened ? $parent : $cursor->Path->new($trans->create_node(unknown => undef, $node) , $parent); $cursor->clone($path); } sub fake_cursor { (my MY $gen, my Widget $widget, my ($metainfo)) = splice @_, 0, 3; my $cursor = $widget->cursor(metainfo => $metainfo); my $node = $gen->create_node(unknown => undef, @_); $cursor->clone($cursor->Path->new($node, $cursor->cget('path'))); } sub fake_cursor_to_build { (my MY $root, my Builder $builder, my Scanner $scan , my ($elem)) = @_; $root->fake_cursor($builder->{cf_widget} , $builder->{cf_template}->metainfo ->clone(startline => $scan->{cf_linenum}) , $elem); } sub new_decl_builder { (my MY $root, my Builder $builder, my Scanner $scan , my ($elem, $parser)) = @_; foreach my $shift (0, 1) { my $path = [node_path($elem)]; $root->strip_ns($path) if $shift; my $handler_name = join("_", declare => @$path); if (my $handler = $root->can($handler_name)) { my $nc = $root->fake_cursor_to_build($builder, $scan, $elem)->open; return $handler->($root, $builder, $scan, $nc, $parser); } } die $root->node_error($root->fake_cursor_to_build($builder, $scan, $elem) , "Unknown declarator"); } sub declare_base { (my Root $root, my Builder $builder, my ($scan, $args, $parser)) = @_; if ($builder->{parent}) { die $scan->token_error("Misplaced yatt:base"); } my $path = $args->node_body; my Template $this = $builder->{cf_template}; my Template $base = $this->lookup_template($root, $path) or die $scan->token_error("Can't find template $path"); # XXX: refresh は lookup_template の中ですべきか? $root->refresh($base); # 名前は保存しなくていいの? $this->{cf_base_template} = $base->{cf_nsid}; $root->add_isa($root->get_package($this) , $root->get_package($base)); # builder を返すことを忘れずに。 $builder; } sub declare_args { (my Root $root, my Builder $builder , my ($scan, $nc, $parser, @configs)) = @_; if ($builder->{parent}) { die $scan->token_error("Misplaced yatt:args"); } # widget -> args の順番で出現する場合もある。 # root 用の builder を取り出し直す if ($builder->{cf_root_builder}) { $builder = $builder->{cf_root_builder}; } my Widget $widget = $builder->{cf_widget}; $widget->{cf_declared} = 1; $widget->{cf_decl_start} = $scan->{cf_last_linenum}; $widget->{cf_body_start} = $scan->{cf_last_linenum} + $scan->{cf_last_nol}; $widget->configure(@configs) if @configs; $root->define_args($widget, $nc); $root->after_define_args($widget); $builder; } sub declare_params { shift->declare_args(@_, public => 1); } sub declare_widget { (my Root $root, my Builder $builder, my Scanner $scan , my ($args, $parser)) = @_; if ($builder->{parent}) { die $root->node_error($root->fake_cursor_to_build($builder, $scan , $builder->product) , "Misplaced yatt:widget in:"); } defined (my $name = $args->node_name) or die $root->node_error($args, "widget name is missing"); # XXX: filename, lineno my Widget $widget = $root->create_widget_in ($builder->{cf_template}, $name , declared => 1 , filename => $builder->{cf_template}->metainfo->cget('filename') , decl_start => $scan->{cf_last_linenum} , body_start => $scan->{cf_last_linenum} + $scan->{cf_last_nol}); $root->define_args($widget, $args->go_next); $root->after_define_args($widget); $root->Builder->new($widget->cget('root'), undef , widget => $widget , template => $builder->{cf_template} , startpos => $scan->{cf_index} , startline => $scan->{cf_linenum} , linenum => $scan->{cf_linenum} # widget -> args に戻るためには root_builder を # 渡さねばならぬ , root_builder => $builder->{cf_root_builder} || $builder ); } sub create_widget_in { (my Root $root, my Template $tmpl, my ($name)) = splice @_, 0, 3; my $widget = YATT::Widget->new (name => $name, template_nsid => $tmpl->{cf_nsid} , @_); $tmpl->{Widget}{$name} = $widget; push @{$tmpl->{widget_list}}, $widget; $widget; } sub current_parser { my Root $root = shift; $root->{current_parser}[0]; } sub after_define_args {shift; shift} sub define_args { (my Root $root, my ($target, $args)) = @_; # $target は has_arg($name) と add_arg($name, $arg) を実装しているもの。 # *: widget # *: codevar for (; $args->readable; $args->next) { # マクロ引数呼び出し %name(); がここで出現 # comment も現れうる。 # body = [code title=html] みたいなグループ引数もここで。 my $sub = $root->can("add_decl_" . $args->node_type_name) or next; $sub->($root, $target, $args); } # おまけ。使わないけど、デバッグ時に少し幸せ。 $root; } sub add_decl_attribute { (my Root $root, my ($target, $args)) = @_; my $argname = $args->node_name; unless (defined $argname) { die $root->node_error($args, "Undefined att name!"); } if ($target->has_arg($argname)) { die $root->node_error($args, "Duplicate argname: $argname"); } my ($type, @param) = $args->parse_typespec; my ($typename, $subtype) = do { if (ref $type) { ($type->[0], [@{$type}[1 .. $#$type]]) } else { ($type, undef); } }; if (defined $typename and my $sub = $root->can("attr_declare_$typename")) { $sub->($root, $target, $args, $argname, $subtype, @param); } else { $target->add_arg($argname, $root->create_var($type, $args, @param)); } } sub create_var { (my Root $root, my ($type, $args, @param)) = @_; $type = '' unless defined $type; my ($primary, @subtype) = ref $type ? @$type : $type; defined (my $class = $root->{cf_type_map}{$primary}) or croak $root->node_error($args, "No such type: %s", $primary); unshift @param, subtype => @subtype >= 2 ? \@subtype : $subtype[0] if @subtype; if (my $sub = $root->can("create_var_$primary")) { $sub->($root, $args, @param); } else { $class->new(@param); } } #======================================== { package YATT::Registry::Loader; use YATT::Inc; use base qw(YATT::Class::Configurable); use YATT::Fields qw(Cache); use Carp; use YATT::Registry::NS; sub DIR () { 'YATT::Registry::Loader::DIR' } sub handle_refresh { (my MY $loader, my Root $root, my NS $node) = @_; my $type = $node->type_name; if (my $sub = $loader->can("refresh_$type")) { $sub->($loader, $root, $node); } else { confess "Can't refresh type: $type"; } } sub is_modified { my MY $loader = shift; my ($item, $old) = @_; my $mtime = $loader->mtime($item); return if defined $old and $old >= $mtime; $_[1] = $mtime; return 1; } package YATT::Registry::Loader::DIR; use base qw(YATT::Registry::Loader File::Spec); use YATT::Fields qw(cf_DIR cf_LIB); sub initargs { qw(cf_DIR) } sub init { my ($self, $dir) = splice @_, 0, 2; $self->SUPER::init($dir, @_); if (-d (my $libdir = "$dir/lib")) { require lib; import lib $libdir } $self; } use YATT::Registry::NS; use YATT::Util; use YATT::Util::Taint; sub mtime { shift; (stat(shift))[9]; } sub RCFILE () {'.htyattrc'} sub Parser () {'YATT::LRXML::Parser'} use Carp; sub checked_read_file { (my MY $loader, my ($fn, $layer)) = @_; croak "Given path is tainted! $fn" if is_tainted($fn); open my $fh, '<' . ($layer || ''), $fn or die "Can't open $fn! $!"; local $/; scalar <$fh>; } sub refresh_Dir { (my MY $loader, my Root $root, my Dir $dir) = @_; my $dirname = $dir->{cf_loadkey}; # ファイルリストの処理. return unless $loader->is_modified($dirname, $dir->{cf_mtime}{$dirname}); my $is_reload = $dir->{cf_age}++; undef $dir->{is_loaded}; if (is_tainted($dirname)) { croak "Directory $dirname is tainted" } if ($root == $dir) { foreach my $d ($dirname, map {!defined $_ ? () : ref $_ ? @$_ : $_} $loader->{cf_LIB}) { $loader->load_dir($root, $dir, $d); } } else { $loader->load_dir($root, $dir, $dirname); } # RC 読み込みの前に、 default_base_class を設定。 if ($root->{cf_default_base_class} and ($root->{cf_default_base_class} ne $root->{cf_pkg} or $root->{is_loaded})) { # XXX: add_isa じゃなくて ensure_isa だね。 #print STDERR "loading default_base_class $root->{cf_default_base_class}" # . " for dir $dirname\n"; $root->checked_eval(qq{require $root->{cf_default_base_class}}); $root->add_isa(my $pkg = $root->get_package($dir) , $root->{cf_default_base_class}); } # RC 読み込みは、最後に my $rcfile = $loader->catfile($dirname, $loader->RCFILE); if (-r $rcfile) { my $script = ""; $script .= ";no warnings 'redefine';" if $is_reload; $script .= sprintf(qq{\n#line 1 "%s"\n}, $rcfile) unless $root->{cf_no_lineinfo}; $script .= untaint_any($loader->checked_read_file($rcfile)); &YATT::break_rc; $root->with_reloading_flag ($is_reload, sub { $root->eval_in_dir($dir, $script); }); &YATT::break_after_rc; $dir->after_rc_loaded($root); } $dir; } sub load_dir { (my MY $loader, my Root $root, my Dir $dir, my ($dirname)) = @_; local *DIR; opendir DIR, $dirname or die "Can't open dir '$dirname': $!"; while (my $name = readdir(DIR)) { next if $name =~ /^\./; my $path = $loader->catfile($dirname, $name); # entry を作るだけ。load はしない。→ mtime も、子供側で。 if (-d $path) { next unless $name =~ /^(?:\w|-)+$/; # Not CC for future widechar. $dir->{Dir}{$name} ||= $loader->{Cache}{$path} ||= $root->createNS(Dir => name => $name , loadkey => untaint_any($path) , parent_nsid => $dir->{cf_nsid} , base_nsid => $dir->{cf_base_nsid} ); } elsif ($name =~ /^(\w+)\.html?$/) { # XXX: Should allow '-'. $dir->{Template}{$1} ||= $loader->{Cache}{$path} ||= $root->createNS(Template => name => $1 , loadkey => untaint_any($path) , parent_nsid => $dir->{cf_nsid} , base_nsid => $dir->{cf_base_nsid} ); } } # XXX: 無くなったファイルの開放は? closedir DIR; } sub refresh_Template { (my MY $loader, my Root $root, my Template $tmpl) = @_; my $path = $tmpl->{cf_loadkey}; unless ($loader->is_modified($path, $tmpl->{cf_mtime}{$path})) { print STDERR "refresh_Template: not modified: $path\n" if $root->{cf_debug_registry}; return; } if (is_tainted($path)) { croak "template path $path is tainted"; } if (my $cleaner = $root->can("forget_template")) { $cleaner->($root, $tmpl); } my $is_reload = $tmpl->{cf_age}++; undef $tmpl->{is_loaded}; $root->add_isa(my $pkg = $root->get_package($tmpl) , $root->get_package($tmpl->{cf_parent_nsid})); foreach my $name (map {defined $_ ? @$_ : ()} $root->{cf_template_global}) { *{globref($pkg, $name)} = *{globref($root->{cf_app_prefix}, $name)}; } # XXX: There can be a race. (mtime vs open) my $parser = $loader->call_type (Parser => new => untaint => 1 , registry => $root , special_entities => $root->{cf_special_entities}); local $root->{current_parser}[0] = $parser; open my $fh, '<', $path or die "Can't open $path"; $tmpl->{cf_metainfo} = $parser->configure_metainfo (nsid => $tmpl->{cf_nsid} , namespace => $root->namespace , filename => $path); $tmpl->{tree} = $parser->parse_handle($fh); # XXX: ついでに を解釈. ← parser に前倒し。 # $root->process_declarations($tmpl); } } #======================================== sub _lined { my $i = 1; my $result; foreach my $line (split /\n/, $_[0]) { if ($line =~ /^\#line (\d+)/) { $i = $1; $result .= $line . "\n"; } else { $result .= sprintf "% 3d %s\n", $i++, $line; } } $result } 1;