package Zoidberg::DispatchTable; our $VERSION = '0.96'; use strict; use Zoidberg::Utils qw/debug bug error/; use Exporter::Tidy all => [qw/stack wipe tag tags/]; our $ERROR_CALLER = 1; # reserved keys _AUTOLOAD and _META # $self->[0] hash with arrays of dispatch strings/refs # $self->[1] hash with arrays of tags # $self->[2] object ref # $self->[3] object can parent bit # $self->[4] array with keys to keep them in order # $self->[5] iteration index for keys() # keys are kept in order to avoid inconsistencies # for example when iterating trough {parser} sub new { # create a blessed AND tie'ed hash my $class = shift; my %hash; tie %hash, $class, @_; bless \%hash, $class; } sub TIEHASH { my $class = shift; my $ref = shift || error 'need object ref to tie hash'; # $ref is either array ref or object ref my $self = (ref($ref) eq 'ARRAY') ? $ref : [{}, {}, $ref, $ref->can('parent'), [], 0]; bless $self, $class; while (my $hash = shift @_) { $self->STORE($_, $$hash{$_}) for keys %$hash; } return $self; } sub STORE { my ($self, $key, $value) = @_; my $tag = 'undef'; ($value, $tag) = @$value if ref($value) eq 'ARRAY'; my $t = ref $value; if ($t eq 'HASH') { unless (tied $value) { # recurs tie'ing tie %$value, __PACKAGE__, $$self[2], $value; # be careful to reuse same ref - else perl bugs :( } # else just store the tied hash } elsif (! $t) { $value =~ s/(^\s*|\s*$)//g; error "Can't use ==>$value<== as subroutine." if ! length $value or $value =~ /^\$/; # no vars } elsif ($t ne 'CODE') { bug "Can't store ref of type $t in DispatchTable" } push @{$self->[0]{$key}}, $value; push @{$self->[1]{$key}}, $tag; push @{$self->[4]}, $key; } sub add { my $self = tied %{ shift() }; $self->STORE(@_); } sub FETCH { my ($self, $key) = @_; if ( exists $$self[0]{$key} and scalar @{$$self[0]{$key}} ) { $$self[0]{$key}[-1] = $self->convert($self->[0]{$key}[-1]) unless ref $self->[0]{$key}[-1]; return $self->[0]{$key}[-1]; } elsif ($self->EXISTS('_AUTOLOAD')) { my $sub; for (@{$self->[0]{_AUTOLOAD}}) { $sub = $_->($key); next unless $sub; $self->STORE($key, $sub) unless $self->EXISTS($key); return $self->FETCH($key); } } return undef; } sub convert { my ($self, $ding) = @_; if ($ding =~ /^\s*sub\s*{.*}\s*$/) { # undocumented hack debug "going to eval: $ding"; my $closure = eval $ding; die if $@; return sub { $closure->($$self[2], @_) }; } $ding =~ s#^->((\w+)->)?# ( $self->[3] ? q/parent->/ : '' ) . ( $1 ? qq/{objects}{$2}->/ : '' ) #e; if ($ding =~ /\(\s*\)$/s) { $ding =~ s/\s*\)$/\@_\)/ } elsif ($ding =~ /\(.*\)$/s) { $ding =~ s/\)$/, \@_\)/ } else { $ding .= '(@_)' } debug "going to eval: sub { \$self->[2]->$ding }"; my $sub = eval "sub { \$\$self[2]->$ding }"; die if $@; return $sub; } sub EXISTS { exists $_[0][0]->{$_[1]} and scalar @{$_[0][0]->{$_[1]}} } sub DELETE { # doesn't really delete, merely pops my ($self, $key) = @_; return undef unless exists $self->[0]{$key}; pop @{$self->[1]{$key}}; my $re = pop @{$self->[0]{$key}}; unless (scalar @{$self->[0]{$key}}) { delete $self->[0]{$key}; delete $self->[1]{$key}; @{$self->[4]} = grep {$_ ne $key} @{$self->[4]}; } return $re; } sub pop { my $self = tied %{ shift() }; $self->DELETE(@_); } sub CLEAR { %{$_[0][0]} = (); %{$_[0][1]} = (); @{$_[0][4]} = (); $_[0][5] = 0; } sub FIRSTKEY { $_[0][5] = 0; goto \&NEXTKEY } sub NEXTKEY { my $self = shift; if ($$self[5] > $#{$$self[4]}) { $$self[5] = 0; return wantarray ? () : undef; } elsif (wantarray) { # ($key, $value) = each(%table) my $key = $$self[4][$$self[5]++]; return $key, $self->FETCH($key); } else { return $self->[4][$$self[5]++] } # for $key (keys %table) } sub stack { my $self = tied %{ shift() }; my ($key, $use_tag) = @_; return () unless exists $$self[0]{$key}; for (@{$self->[0]{$key}}) { $_ = $self->convert($_) unless ref $_ } return map [ $$self[0]{$key}[$_], $$self[1]{$key}[$_] ], (0..$#{$$self[0]{$key}}) if $use_tag; return @{$self->[0]{$key}}; } sub tag { my $self = tied %{ shift() }; my $key = shift; return undef unless exists $$self[1]{$key}; return $$self[1]{$key}[-1]; } sub tags { my $self = tied %{ shift() }; my $key = shift; return undef unless exists $$self[1]{$key}; return @{$self->[1]{$key}}; } sub wipe { my $self = tied %{ shift() }; my ($tag, @keys) = @_; @keys = keys %{$self->[0]} unless scalar @keys; my %old; for my $key (@keys) { for (my $i = 0; $i < @{$self->[1]{$key}}; $i++) { next unless $self->[1]{$key}[$i] eq $tag; $old{$key} = [$self->[0]{$key}[$i], $tag]; $self->[0]{$key}[$i] = undef; $self->[1]{$key}[$i] = undef; } @{$self->[0]{$key}} = grep {defined $_} @{$self->[0]{$key}}; @{$self->[1]{$key}} = grep {defined $_} @{$self->[1]{$key}}; unless (scalar @{$self->[0]{$key}}) { delete $self->[0]{$key}; delete $self->[1]{$key}; @{$self->[4]} = grep {$_ ne $key} @{$self->[4]}; } } return \%old; } 1; __END__ =head1 NAME Zoidberg::DispatchTable - Class to tie dispatch tables =head1 SYNOPSIS use Zoidberg::DispatchTable; my $table = Zoidberg::DispatchTable->new( $self, { cd => '->Commands->cd' } ); # The same as $self->parent->{objects}{Commands}->cd('..') if # a module can('parent'), else the same as $self->Commands->cd('..') $$table{cd}->('..'); $$table{ls} = q{ls('-al')} # The same as $self->ls('-al', '/data') $$table{ls}->('/data'); =head1 DESCRIPTION This module provides a tie interface for converting config strings to CODE references. It takes an object references (C<$self>) as starting point for resolving subroutines. If the object has a method C the refrence returned by this method is used as the root for resolving subroutines, else the object itself is used as root. The root is expected to contain a hash C<{objects}> (possibly of the class L) with references to "child" objects. Strings are converted to CODE references at first use to save time at initialisation. The following strings are supported: String Interpretation ---------- ----------------- sub Sub of the reference object ->sub Sub of the root object ->sub(qw/f00 b4r/) Sub of the root object with arguments ->object->sub Sub of a child object of the root ->sub()->.. Sub of the root object You can store either config strings or CODE references in the table. The tables is transparent to CODE references, they are used as given. ( An earlier version of this module did currying .. this behaviour is altered. ) If you store an ARRAY ref it is expected to be of the form C<[$value, $tag]>, where C<$tag> is an identifier used for handling selections of the table. The $value can again be a string or CODE ref. If you store a HASH ref it will be tied recursively as a DispatchTable. Keys are kept in the order they are first added, thus C will always return the same order. This is to keep zoid's plugins in the order they are added. Also for each key a stack is used. Deleting a key only pops it's stack. I< This modules doesn't check for security issues, it just runs arbitrary code. > =head1 EXPORT This module can export the methods C, C and C. =over 4 =item add =item pop =item C Wipes entries with tag C<$tag> from given set of kaeys or from the whole table. =item C Returns the whole stack for an given key, useful to loop trough stacks. C<$tags> is a boolean, when true all items are returned as a sub array of CODE ref with tag. =item C Returns the tag for the given key. =item C Returns an array of all tags for given key. =back =head1 AUTHOR Jaap Karssenberg || Pardus [Larus] Epardus@cpan.orgE Copyright (c) 2003 Jaap G Karssenberg. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L =cut