/usr/local/CPAN/YATT/YATT/Registry.pm
# -*- 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: ã¤ãã§ã« <!yatt:widget> ãè§£é. â 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;