File Coverage

blib/lib/LaTeX/TikZ/Functor.pm
Criterion Covered Total %
statement 71 72 98.6
branch 16 22 72.7
condition 4 9 44.4
subroutine 12 12 100.0
pod 2 3 66.6
total 105 118 88.9


line stmt bran cond sub pod time code
1             package LaTeX::TikZ::Functor;
2              
3 10     10   38 use strict;
  10         11  
  10         254  
4 10     10   31 use warnings;
  10         11  
  10         425  
5              
6             =head1 NAME
7              
8             LaTeX::TikZ::Functor - Build functor methods that recursively visit nodes of a LaTeX::TikZ::Set tree.
9              
10             =head1 VERSION
11              
12             Version 0.03
13              
14             =cut
15              
16             our $VERSION = '0.03';
17              
18             =head1 DESCRIPTION
19              
20             A functor takes a L tree and returns a new, transmuted version of it according to certain rules.
21             It recursively visits all the nodes of the tree, building a new set out of the result of the functor on the child sets.
22              
23             Rules are stored as L objects.
24             They can apply not only to L consumer objects, but also to the L consumer objects they contain.
25             When the functor is called against a set object and that the returned set is different from the original (as told by C<==>, which defaults to object identity), then the functor is also applied to all the mods of the set, and their transformed counterparts are added to the new set.
26              
27             When the functor is called onto a set or mod object, all its associated rules are tried successively, and the handler of the first matching rule is executed with :
28              
29             =over 4
30              
31             =item *
32              
33             the functor object as its first argument ;
34              
35             =item *
36              
37             the current set or mod object as its second argument ;
38              
39             =item *
40              
41             the arguments passed to the functor itself starting at the third argument.
42              
43             =back
44              
45             The handler is expected to return the new set or mod that will replace the old one in the resulting set tree.
46              
47             If no matching rule is found, the object is returned as-is.
48              
49             =cut
50              
51 10     10   39 use Carp ();
  10         9  
  10         94  
52              
53 10     10   31 use Sub::Name ();
  10         10  
  10         99  
54              
55 10     10   4111 use LaTeX::TikZ::Functor::Rule;
  10         14  
  10         237  
56              
57 10     10   48 use LaTeX::TikZ::Interface;
  10         10  
  10         294  
58              
59 10     10   34 use LaTeX::TikZ::Tools;
  10         11  
  10         1257  
60              
61             my $lts_tc = LaTeX::TikZ::Tools::type_constraint('LaTeX::TikZ::Set');
62              
63             my $validate_spec;
64             BEGIN {
65             $validate_spec = Sub::Name::subname('validate_spec' => sub {
66 116         121 my ($spec) = @_;
67              
68 116         101 my ($replace, $target);
69 116 50 33     1019 if (defined $spec and ref $spec eq ''
      33        
70             and $spec =~ /^(\+?)([A-Za-z][A-Za-z0-9_]*(?:::[A-Za-z][A-Za-z0-9_]*)*)$/) {
71 116   66     490 $replace = defined($1) && $1 eq '+';
72 116         199 $target = $2;
73             } else {
74 0         0 Carp::confess("Invalid rule spec $spec");
75             }
76              
77 116         257 return $target, $replace;
78 10     10   3807 });
79             }
80              
81             =head1 METHODS
82              
83             =head2 C
84              
85             my $functor = LaTeX::TikZ::Functor->new(
86             rules => [ $spec1 => $handler1, $spec2 => $handler2, ... ],
87             );
88              
89             Creates a new functor object that will use both the default and the user-specified rules.
90             The functor is also a code reference that expects to be called against L objects.
91              
92             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.
93              
94             # The default is a clone method
95             my $clone = Tikz->functor;
96             my $dup = $set->$clone;
97              
98             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 inserted into the list of default rules after all its descendants' rules and before all its ancestors' rules.
99              
100             # A translator
101             my $translate = Tikz->functor(
102             # Only replace the way point sets are cloned
103             'LaTeX::TikZ::Set::Point' => sub {
104             my ($functor, $set, $x, $y) = @_;
105              
106             $set->new(
107             point => [
108             $set->x + $x,
109             $set->y + $y,
110             ],
111             label => $set->label,
112             pos => $set->pos,
113             );
114             },
115             );
116             my $shifted = $set->$translate(1, 1);
117              
118             But if one of the C<$spec>s begins with C<'+'>, the rule will replace I default rules that apply to subclasses or subroles of C<$spec> (including C<$spec> itself).
119              
120             # A mod stripper
121             my $strip = Tikz->functor(
122             # Replace all existent mod rules by this simple one
123             '+LaTeX::TikZ::Mod' => sub { return },
124             );
125             my $naked = $set->$strip;
126              
127             The functor will map unhandled sets and mods to themselves without cloning them, since it has no way to know how to do it.
128             Thus, if you define your own L or L object, be sure to register a default rule for it with the L method.
129              
130             =cut
131              
132             my @default_set_rules;
133             my @default_mod_rules;
134              
135             sub new {
136 12     12 1 35 my ($class, %args) = @_;
137              
138 12         55 my @set_rules = @default_set_rules;
139 12         26 my @mod_rules = @default_mod_rules;
140              
141 12 50       17 my @user_rules = @{$args{rules} || []};
  12         55  
142 12         34 while (@user_rules) {
143 16         46 my ($spec, $handler) = splice @user_rules, 0, 2;
144              
145 16         46 my ($target, $replace) = $validate_spec->($spec);
146              
147 16         146 my $rule = LaTeX::TikZ::Functor::Rule->new(
148             target => $target,
149             handler => $handler,
150             );
151              
152 16 100       116 $rule->insert(
153             into => $rule->is_set ? \@set_rules : \@mod_rules,
154             overwrite => 1,
155             replace => $replace,
156             );
157             }
158              
159 12         30 my %dispatch = map { $_->target => $_ } @set_rules, @mod_rules;
  106         240  
160              
161 12         22 my $self;
162              
163             $self = bless sub {
164 60     60   441 my $set = shift;
165              
166 60         109 $lts_tc->assert_valid($set);
167              
168 60         2453 my $rule = $dispatch{ref($set)};
169 60 100       91 unless ($rule) {
170 2         5 for (@set_rules) {
171 3 100       10 if ($_->handles($set)) {
172 2         95 $rule = $_;
173 2         4 last;
174             }
175             }
176             }
177 60 50       80 return $set unless $rule;
178              
179 60         170 my $new_set = $rule->handler->($self, $set, @_);
180 59 50       1825 return $set if $new_set == $set;
181              
182 59         47 my @new_mods;
183             MOD:
184 59         117 for my $mod ($set->mods) {
185 11         17 my $rule = $dispatch{ref($mod)};
186 11 100       29 unless ($rule) {
187 7         8 for (@mod_rules) {
188 7 50       15 if ($_->handles($mod)) {
189 7         183 $rule = $_;
190 7         7 last;
191             }
192             }
193             }
194 11 50       46 push @new_mods, $rule ? $rule->handler->($self, $mod, @_)
195             : $mod;
196             }
197 59         244 $new_set->mod(@new_mods);
198              
199 59         252 return $new_set;
200 12         105 }, $class;
201             }
202              
203             LaTeX::TikZ::Interface->register(
204             functor => sub {
205 4     4 0 2273 shift;
206              
207 4         21 __PACKAGE__->new(rules => \@_);
208             },
209             );
210              
211             =head2 C
212              
213             LaTeX::TikZ::Functor->default_rule($spec => $handler)
214              
215             Adds to all subsequently created functors a default rule for the class or role C<$spec>.
216              
217             An exception is thrown if there is already a default rule for C<$spec> ; otherwise, the new rule is inserted into the current list of default rules after all its descendants' rules and before all its ancestors' rules.
218             But if C<$spec> begins with C<'+'>, the rule will replace I default rules that apply to subclasses or subroles of C<$spec> (including C<$spec> itself).
219              
220             Returns true if and only if an existent rule was replaced.
221              
222             =cut
223              
224             sub default_rule {
225 100     100 1 117 shift;
226 100         105 my ($spec, $handler) = @_;
227              
228 100         175 my ($target, $replace) = $validate_spec->($spec);
229              
230 100         633 my $rule = LaTeX::TikZ::Functor::Rule->new(
231             target => $target,
232             handler => $handler,
233             );
234              
235 100 100       503 $rule->insert(
236             into => $rule->is_set ? \@default_set_rules : \@default_mod_rules,
237             overwrite => 0,
238             replace => $replace,
239             );
240             }
241              
242             =head1 SEE ALSO
243              
244             L, L.
245              
246             =head1 AUTHOR
247              
248             Vincent Pit, C<< >>, L.
249              
250             You can contact me by mail or on C (vincent).
251              
252             =head1 BUGS
253              
254             Please report any bugs or feature requests to C, or through the web interface at L.
255             I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
256              
257             =head1 SUPPORT
258              
259             You can find documentation for this module with the perldoc command.
260              
261             perldoc LaTeX::TikZ
262              
263             =head1 COPYRIGHT & LICENSE
264              
265             Copyright 2010,2011,2012,2013,2014,2015 Vincent Pit, all rights reserved.
266              
267             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
268              
269             =cut
270              
271             1; # End of LaTeX::TikZ::Functor