1 package LaTeX::TikZ::Functor;
8 LaTeX::TikZ::Functor - Build functor methods that recursively visit nodes of a LaTeX::TikZ::Set tree.
16 our $VERSION = '0.01';
20 A functor takes a L<LaTeX::TikZ::Set> tree and clones it according to certain rules.
28 use LaTeX::TikZ::Interface;
30 use LaTeX::TikZ::Tools;
32 use Any::Moose 'Util' => [ qw[find_meta does_role] ];
34 my $lts_tc = LaTeX::TikZ::Tools::type_constraint('LaTeX::TikZ::Set');
36 my @default_set_rules;
37 my @default_mod_rules;
39 my ($validate_rule, $insert_rule);
41 $validate_rule = Sub::Name::subname('validate_rule' => sub {
42 my ($spec, $handler) = @_;
44 my ($replace, $target);
45 if (defined $spec and ref $spec eq ''
46 and $spec =~ /^(\+?)([A-Za-z][A-Za-z0-9_]*(?:::[A-Za-z][A-Za-z0-9_]*)*)$/) {
47 $replace = defined($1) && $1 eq '+';
50 Carp::confess("Invalid rule spec $spec");
53 (my $pm = $target) =~ s{::}{/}g;
57 my $meta = find_meta($target);
58 Carp::confess("No meta object associated with target $target")
60 my $is_role = $meta->isa(any_moose('Meta::Role'));
63 if (does_role($target, 'LaTeX::TikZ::Set')) {
65 } elsif (does_role($target, 'LaTeX::TikZ::Mod')) {
68 Carp::confess("Target $target is neither a set nor a mod");
71 Carp::confess("Invalid handler for rule spec $spec")
72 unless defined $handler and ref $handler eq 'CODE';
74 return [ $target, $handler, $replace, $is_role, $is_set ];
77 $insert_rule = Sub::Name::subname('insert_rule' => sub {
78 my ($rule, $list, $overwrite) = @_;
80 my ($target, $replace, $is_role) = @{$rule}[0, 2, 3];
85 for my $i (0 .. $#$list) {
86 my $old_target = $list->[$i]->[0];
87 if ($is_role ? does_role($old_target, $target)
88 : $old_target->isa($target)) {
90 splice @$list, $i, 1, $rule;
100 splice @$list, $_ - $shift, 1;
103 return 1 unless defined $rule;
105 } else { # Replace only an existent rule
107 for my $i (0 .. $#$list) {
108 my $old_target = $list->[$i]->[0];
109 if ($old_target eq $target) {
110 Carp::confess("Default rule already defined for target $target")
112 splice @$list, $i, 1, $rule;
125 =head2 C<< new rules => [ $spec1 => $handler1, $spec2 => $handler2, ... ] >>
127 Creates a new functor object that will use both the default and these user-specified rules.
128 The functor is also a code reference that expects to be called against L<LaTeX::TikZ::Set> objects.
130 The default set and mod rules clone their relevant objects, so you get a clone functor (for the default set types) if you don't specify any user rule.
132 # The default is a clone method
133 my $clone = Tikz->functor;
134 my $dup = $set->$clone;
136 If there is already a default rule for one of the C<$spec>s, it is replaced by the new one ; otherwise, the user rule is appended to the list of default rules.
139 my $translate = Tikz->functor(
140 # Only replace the way point sets are cloned
141 'LaTeX::TikZ::Set::Point' => sub {
142 my ($functor, $set, $x, $y) = @_;
149 label => $set->label,
154 my $shifted = $set->$translate(1, 1);
156 But if one of the C<$spec>s begins with C<'+'>, the rule will replace I<all> default rules that apply to subclasses or subroles of C<$spec> (including C<$spec> itself).
159 my $strip = Tikz->functor(
160 # Replace all existent mod rules by this simple one
161 '+LaTeX::TikZ::Mod' => sub { return },
163 my $naked = $set->$strip;
165 The functor will map unhandled sets and mods to themselves without cloning them, since it has no way to know how to do it.
166 Thus, if you define your own L<LaTeX::TikZ::Set> or L<LaTeX::TikZ::Mod> object, be sure to register a default rule for it with the L</default_rule> method.
171 my ($class, %args) = @_;
173 my @set_rules = @default_set_rules;
174 my @mod_rules = @default_mod_rules;
176 my @user_rules = @{$args{rules} || []};
177 while (@user_rules) {
178 my ($spec, $handler) = splice @user_rules, 0, 2;
180 my $rule = $validate_rule->($spec, $handler);
182 $insert_rule->($rule, $rule->[4] ? \@set_rules : \@mod_rules, 1);
185 my %dispatch = map { $_->[0] => $_ } @set_rules, @mod_rules;
192 $lts_tc->assert_valid($set);
194 my $rule = $dispatch{ref($set)};
197 if ($_->[2] ? $set->does($_->[0]) : $set->isa($_->[0])) {
202 $rule = [ undef, sub { $_[1] } ] unless $rule;
204 my $new_set = $rule->[1]->($self, $set, @_);
205 my $is_new = $new_set ne $set;
209 for my $mod ($set->mods) {
210 my $rule = $dispatch{ref($mod)};
213 if ($_->[2] ? $mod->does($_->[0]) : $mod->isa($_->[0])) {
219 push @new_mods, $mod;
223 push @new_mods, $rule->[1]->($self, $mod, @_);
226 $new_set->mod(@new_mods) if $is_new;
232 LaTeX::TikZ::Interface->register(
236 __PACKAGE__->new(rules => \@_);
240 =head2 C<< default_rule $spec => $handler >>
242 Adds to all subsequently created functors a default rule for the class or role C<$spec>.
244 An exception is thrown if there is already a default rule for C<$spec> ; otherwise, the new rule is appended to the current list of rules.
245 But if C<$spec> begins with C<'+'>, the rule will replace I<all> default rules that apply to subclasses or subroles of C<$spec> (including C<$spec> itself).
247 Returns true if and only if an existent rule was replaced.
254 my $rule = $validate_rule->(@_);
258 $rule->[4] ? \@default_set_rules : \@default_mod_rules,
269 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
271 You can contact me by mail or on C<irc.perl.org> (vincent).
275 Please report any bugs or feature requests to C<bug-latex-tikz at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=LaTeX-TikZ>.
276 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
280 You can find documentation for this module with the perldoc command.
284 =head1 COPYRIGHT & LICENSE
286 Copyright 2010 Vincent Pit, all rights reserved.
288 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
292 1; # End of LaTeX::TikZ::Functor