File Coverage

blib/lib/Macro/Micro.pm
Criterion Covered Total %
statement 89 95 93.6
branch 44 54 81.4
condition 8 12 66.6
subroutine 21 22 95.4
pod 10 10 100.0
total 172 193 89.1


line stmt bran cond sub pod time code
1 6     6   405090 use strict;
  6         74  
  6         174  
2 6     6   32 use warnings;
  6         11  
  6         282  
3             package Macro::Micro 0.055;
4             # ABSTRACT: really simple templating for really simple templates
5              
6 6     6   38 use Carp ();
  6         10  
  6         5594  
7              
8             #pod =head1 SYNOPSIS
9             #pod
10             #pod use Macro::Micro;
11             #pod
12             #pod my $expander = Macro::Micro->new;
13             #pod
14             #pod $expander->register_macros(
15             #pod ALIGNMENT => "Lawful Good",
16             #pod HEIGHT => sub {
17             #pod my ($macro, $object, $stash) = @_;
18             #pod $stash->{race}->avg_height;
19             #pod },
20             #pod );
21             #pod
22             #pod $expander->expand_macros_in($character, { race => $human_obj });
23             #pod
24             #pod # character is now a Lawful Good, 5' 6" human
25             #pod
26             #pod =head1 DESCRIPTION
27             #pod
28             #pod This module performs very basic expansion of macros in text, with a very basic
29             #pod concept of context and lazy evaluation.
30             #pod
31             #pod =method new
32             #pod
33             #pod my $mm = Macro::Micro->new(%arg);
34             #pod
35             #pod This method creates a new Macro::Micro object.
36             #pod
37             #pod There is only one valid argument:
38             #pod
39             #pod macro_format - this is the format for macros; see the macro_format method
40             #pod
41             #pod =cut
42              
43             my $DEFAULT_MACRO_FORMAT = qr/(?\]])/x;
44              
45             sub new {
46 6     6 1 569 my ($class, %arg) = @_;
47              
48 6         19 my $self = bless { } => $class;
49              
50 6 100       31 $arg{macro_format} = $DEFAULT_MACRO_FORMAT unless $arg{macro_format};
51              
52 6         24 $self->macro_format($arg{macro_format});
53              
54 6         19 return $self;
55             }
56              
57             #pod =method macro_format
58             #pod
59             #pod $mm->macro_format( qr/.../ );
60             #pod
61             #pod This method gets or sets the macro format regexp for the expander.
62             #pod
63             #pod The format must be a reference to a regular expression, and should have two
64             #pod capture groups. The first should return the entire string to be replaced in
65             #pod the text, and the second the name of the macro found.
66             #pod
67             #pod The default macro format is: C<< qr/([\[<] (\w+) [>\]])/x >>
68             #pod
69             #pod In other words: a probably-valid-identiifer inside angled or square backets.
70             #pod
71             #pod =cut
72              
73             sub macro_format {
74 25     25 1 845 my $self = shift;
75              
76 25 100       76 return $self->{macro_format} unless @_;
77              
78 8         15 my $macro_format = shift;
79 8 100       262 Carp::croak "macro format must be a regexp reference"
80             unless ref $macro_format eq 'Regexp';
81              
82 6         34 $self->{macro_format} = $macro_format;
83             }
84              
85             #pod =method register_macros
86             #pod
87             #pod $mm->register_macros($name => $value, ... );
88             #pod
89             #pod This method register one or more macros for later expansion. The macro names
90             #pod must be either strings or a references to regular expression. The values may
91             #pod be either strings or references to code.
92             #pod
93             #pod These macros may later be used for expansion by C>.
94             #pod
95             #pod =cut
96              
97             sub register_macros {
98 8     8 1 2868 my ($self, @macros) = @_;
99              
100 8         38 for (my $i = 0; $i < @macros; $i += 2) {
101 27         67 my ($name, $value) = @macros[ $i, $i+1 ];
102 27 100 100     162 Carp::croak "macro value must be a string or code reference"
103             if (ref $value) and (ref $value ne 'CODE');
104              
105 26 100       81 if (not ref $name) {
    100          
106 20         66 $self->{macro}{$name} = $value;
107             } elsif (ref $name eq 'Regexp') {
108 5         26 $self->{macro_regexp}{$name} = [ $name, $value ];
109             } else {
110 1         152 Carp::croak "macro name '$name' must be a string or a regexp";
111             }
112             }
113              
114 6         23 return $self;
115             }
116              
117             #pod =method clear_macros
118             #pod
119             #pod $mm->clear_macros;
120             #pod
121             #pod This method clears all registered macros.
122             #pod
123             #pod =cut
124              
125             sub clear_macros {
126 0     0 1 0 my ($self, @macros) = @_;
127              
128 0 0       0 if (@macros) {
129 0         0 Carp::croak "partial deletion not implemented";
130             } else {
131 0         0 delete @$self{qw(macro macro_regexp)};
132             }
133              
134 0         0 return;
135             }
136              
137             #pod =method get_macro
138             #pod
139             #pod my $macro = $mm->get_macro($macro_name);
140             #pod
141             #pod This returns the currently-registered value for the named macro. If the given
142             #pod macro name is not registered exactly, the name is checked against any regular
143             #pod expression macros that are registered. The first of these to match is
144             #pod returned.
145             #pod
146             #pod At present, the regular expression macros are checked in an arbitrary order.
147             #pod
148             #pod =cut
149              
150             sub get_macro {
151 27     27 1 675 my ($self, $macro_name) = @_;
152              
153 27 100       83 return $self->{macro}{$macro_name} if exists $self->{macro}{$macro_name};
154              
155 9         19 foreach my $regexp (values %{ $self->{macro_regexp} }) {
  9         30  
156 8 100       57 return $regexp->[1] if $macro_name =~ $regexp->[0];
157             }
158              
159 3         10 return;
160             }
161              
162             #pod =method expand_macros
163             #pod
164             #pod my $rewritten = $mm->expand_macros($text, \%stash);
165             #pod
166             #pod This method returns the result of rewriting the macros found the text. The
167             #pod stash is a set of data that may be used to expand the macros.
168             #pod
169             #pod The text is scanned for content matching the expander's L. If
170             #pod found, the macro name in the found content is looked up with C>.
171             #pod If a macro is found, it is used to replace the found content in the text.
172             #pod
173             #pod A macros whose value is text is expanded into that text. A macros whose value
174             #pod is code is expanded by calling the code as follows:
175             #pod
176             #pod $replacement = $macro_value->($macro_name, $text, \%stash);
177             #pod
178             #pod Macros are not expanded recursively.
179             #pod
180             #pod =cut
181              
182             sub expand_macros {
183 9     9 1 1330 my ($self, $object, $stash) = @_;
184              
185 9 100       17 if (eval { $object->isa('Macro::Micro::Template') }) {
  9         102  
186 1         8 return $self->_expand_template($object, $stash);
187             }
188              
189 8         26 $self->fast_expander($stash)->($object);
190             }
191              
192             sub _expand_template {
193 1     1   3 my ($self, $object, $stash) = @_;
194             # expects to be passed ($whole_macro, $macro_inside_delim, $whole_text)
195             my $expander = sub {
196 8     8   12 my $macro = $self->get_macro($_[1]);
197 8 50       24 return $_[0] unless defined $macro;
198 8 100 50     23 return ref $macro ? $macro->($_[1], $_[2], $stash)||'' : $macro;
199 1         5 };
200              
201 1 50       3 return ${ $object->_text } unless $object->_parts;
  0         0  
202              
203 1 100       3 return join '', map { ref $_ ? $expander->(@$_[0, 1], $object->_text) : $_ }
  16         66  
204             $object->_parts;
205             }
206              
207             #pod =method expand_macros_in
208             #pod
209             #pod $mm->expand_macros_in($object, \%stash);
210             #pod
211             #pod This rewrites the content of C<$object> in place, using the expander's macros
212             #pod and the provided stash of data.
213             #pod
214             #pod At present, only scalar references can be rewritten in place. In the future,
215             #pod there will be a system to define how various classes of objects should be
216             #pod rewritten in place, such as email messages.
217             #pod
218             #pod =cut
219              
220             sub expand_macros_in {
221 6     6 1 1759 my ($self, $object, $stash) = @_;
222              
223 6 100 100     177 Carp::croak "object of in-place expansion must be a scalar reference"
224             if (not ref $object)
225             or (ref $object ne 'SCALAR');
226              
227 4         12 my $fast_expander = $self->fast_expander($stash);
228              
229 4         13 $$object = $fast_expander->($$object);
230             }
231              
232             #pod =method string_expander
233             #pod
234             #pod my $string_expander = $mm->string_expander($stash);
235             #pod
236             #pod my $rewritten_text = $string_expander->($original_text);
237             #pod
238             #pod This method returns a closure which will expand the macros in text passed to
239             #pod it using the expander's macros and the passed-in stash.
240             #pod
241             #pod C is provided as an alias for legacy code.
242             #pod
243             #pod =cut
244              
245             sub string_expander {
246 13     13 1 29 my ($self, $stash) = @_;
247              
248 13         34 my $expander = $self->macro_expander($stash);
249 13         35 my $regex = $self->macro_format;
250              
251             my $applicator = sub {
252 13     13   26 my ($object) = @_;
253              
254 13 50       33 return unless defined $object;
255 13 100       466 Carp::croak "object of expansion must not be a reference" if ref $object;
256              
257 12         90 $object =~ s/$regex/$expander->($1,$2)/eg;
  24         60  
258              
259 12         130 return $object;
260             }
261 13         62 }
262              
263 6     6   3127 BEGIN { *fast_expander = \&string_expander }
264              
265             #pod =method macro_expander
266             #pod
267             #pod my $macro_expander = $mm->macro_expander(\%stash);
268             #pod
269             #pod This method returns a coderef that can be called as follows:
270             #pod
271             #pod $macro_expander->($macro_string, $macro_name);
272             #pod
273             #pod It should return the string to be used to replace the macro string that was
274             #pod found.
275             #pod
276             #pod =cut
277              
278             sub macro_expander {
279 13     13 1 26 my ($self, $stash) = @_;
280              
281 13         98 my %cached;
282              
283 13 100       22 if (values %{ $self->{macro_regexp} }) {
  13         49  
284             return sub {
285 17 50   17   45 return $cached{ $_[0] } if defined $cached{ $_[0] };
286              
287 17         36 my $macro = $self->get_macro($_[1]);
288              
289 17 100 50     68 $cached{ $_[0] } = defined $macro
    100          
290             ? ref $macro
291             ? $macro->($_[1], $_[2], $stash)||'' : $macro
292             : $_[0];
293              
294 17         142 return $cached{ $_[0] };
295 11         63 };
296             } else {
297             return sub {
298 7 50   7   16 return $cached{ $_[0] } if defined $cached{ $_[0] };
299              
300 7         14 my $macro = $self->{macro}{ $_[1] };
301              
302 7 50 0     23 $cached{ $_[0] } = defined $macro
    50          
303             ? ref $macro
304             ? $macro->($_[1], $_[2], $stash)||'' : $macro
305             : $_[0];
306              
307 7         24 return $cached{ $_[0] };
308 2         11 };
309             }
310             }
311              
312              
313             #pod =method study
314             #pod
315             #pod my $template = $expander->study($text);
316             #pod
317             #pod Given a string, this returns an object which can be used as an argument to
318             #pod C. Macro::Micro will find and mark the locations of macros in
319             #pod the text so that calls to expand the macros will not need to search the text.
320             #pod
321             #pod =cut
322              
323             sub study {
324 4     4 1 1889 my ($self, $text) = @_;
325              
326 4         8 my $macro_format = $self->macro_format;
327              
328 4         8 my @total;
329              
330             my $pos;
331 4         70 while ($text =~ m/\G(.*?)$macro_format/gsm) {
332 8         31 my ($snippet, $whole, $name) = ($1, $2, $3);
333 8 100       28 push @total, (length $snippet ? $snippet : ()),
    50          
334             ($whole ? [ $whole, $name ] : ());
335 8         50 $pos = pos $text;
336             }
337              
338 4 100       12 push @total, substr $text, $pos if defined $pos;
339              
340 4         16 return Macro::Micro::Template->_new(\$text, \@total);
341             }
342              
343             {
344             package Macro::Micro::Template 0.055;
345 4     4   15 sub _new { bless [ $_[1], $_[2] ] => $_[0] }
346 2     2   3 sub _parts { @{ $_[0][1] } }
  2         10  
347 8     8   13 sub _text { $_[0][0] }
348             }
349              
350             "[MAGIC_TRUE_VALUE]";
351              
352             __END__