fill example code stubbed out, pluss more docs;
[catagits/HTML-Zoom.git] / lib / HTML / Zoom.pm
CommitLineData
d80786d0 1package HTML::Zoom;
2
1cf03540 3use strictures 1;
d80786d0 4
5use HTML::Zoom::ZConfig;
bf5a23d0 6use HTML::Zoom::ReadFH;
655965b3 7use HTML::Zoom::Transform;
eeeb0921 8use HTML::Zoom::TransformBuilder;
b9c27a5a 9use Scalar::Util ();
d80786d0 10
f107bef7 11our $VERSION = '0.009004';
7af7362d 12
13$VERSION = eval $VERSION;
14
d80786d0 15sub new {
16 my ($class, $args) = @_;
17 my $new = {};
18 $new->{zconfig} = HTML::Zoom::ZConfig->new($args->{zconfig}||{});
19 bless($new, $class);
20}
21
22sub zconfig { shift->_self_or_new->{zconfig} }
23
24sub _self_or_new {
25 ref($_[0]) ? $_[0] : $_[0]->new
26}
27
28sub _with {
29 bless({ %{$_[0]}, %{$_[1]} }, ref($_[0]));
30}
31
7567494d 32sub from_events {
d80786d0 33 my $self = shift->_self_or_new;
34 $self->_with({
7567494d 35 initial_events => shift,
d80786d0 36 });
37}
38
7567494d 39sub from_html {
40 my $self = shift->_self_or_new;
41 $self->from_events($self->zconfig->parser->html_to_events($_[0]))
42}
43
bf5a23d0 44sub from_file {
45 my $self = shift->_self_or_new;
46 my $filename = shift;
47 $self->from_html(do { local (@ARGV, $/) = ($filename); <> });
48}
49
d80786d0 50sub to_stream {
51 my $self = shift;
52 die "No events to build from - forgot to call from_html?"
53 unless $self->{initial_events};
54 my $sutils = $self->zconfig->stream_utils;
55 my $stream = $sutils->stream_from_array(@{$self->{initial_events}});
2f0c6a86 56 $stream = $_->apply_to_stream($stream) for @{$self->{transforms}||[]};
d80786d0 57 $stream
58}
59
bf5a23d0 60sub to_fh {
61 HTML::Zoom::ReadFH->from_zoom(shift);
62}
63
7567494d 64sub to_events {
65 my $self = shift;
66 [ $self->zconfig->stream_utils->stream_to_array($self->to_stream) ];
67}
68
bf5a23d0 69sub run {
70 my $self = shift;
7567494d 71 $self->to_events;
bf5a23d0 72 return
73}
74
75sub apply {
76 my ($self, $code) = @_;
77 local $_ = $self;
78 $self->$code;
79}
80
fdb039c6 81sub apply_if {
82 my ($self, $predicate, $code) = @_;
83 if($predicate) {
84 local $_ = $self;
85 $self->$code;
86 }
87 else {
88 $self;
89 }
90}
91
d80786d0 92sub to_html {
93 my $self = shift;
94 $self->zconfig->producer->html_from_stream($self->to_stream);
95}
96
97sub memoize {
98 my $self = shift;
99 ref($self)->new($self)->from_html($self->to_html);
100}
101
eeeb0921 102sub with_transform {
1c4455ae 103 my $self = shift->_self_or_new;
eeeb0921 104 my ($transform) = @_;
d80786d0 105 $self->_with({
2f0c6a86 106 transforms => [
107 @{$self->{transforms}||[]},
eeeb0921 108 $transform
2f0c6a86 109 ]
d80786d0 110 });
111}
eeeb0921 112
113sub with_filter {
114 my $self = shift->_self_or_new;
115 my ($selector, $filter) = @_;
116 $self->with_transform(
117 HTML::Zoom::Transform->new({
118 zconfig => $self->zconfig,
119 selector => $selector,
120 filters => [ $filter ]
121 })
122 );
123}
d80786d0 124
125sub select {
1c4455ae 126 my $self = shift->_self_or_new;
127 my ($selector) = @_;
eeeb0921 128 return HTML::Zoom::TransformBuilder->new({
129 zconfig => $self->zconfig,
130 selector => $selector,
131 proto => $self
132 });
d80786d0 133}
134
135# There's a bug waiting to happen here: if you do something like
136#
137# $zoom->select('.foo')
1c4455ae 138# ->remove_attribute(class => 'foo')
d80786d0 139# ->then
140# ->well_anything_really
141#
142# the second action won't execute because it doesn't match anymore.
143# Ideally instead we'd merge the match subs but that's more complex to
144# implement so I'm deferring it for the moment.
145
146sub then {
147 my $self = shift;
2f0c6a86 148 die "Can't call ->then without a previous transform"
149 unless $self->{transforms};
150 $self->select($self->{transforms}->[-1]->selector);
d80786d0 151}
152
c877066a 153sub AUTOLOAD {
b9c27a5a 154 my ($self, $selector, @args) = @_;
c877066a 155 my $meth = our $AUTOLOAD;
156 $meth =~ s/.*:://;
b9c27a5a 157 return $self = $self->select($selector)->$meth(@args);
c877066a 158}
c2d0669e 159
d80786d0 1601;
161
162=head1 NAME
163
164HTML::Zoom - selector based streaming template engine
165
166=head1 SYNOPSIS
167
168 use HTML::Zoom;
169
170 my $template = <<HTML;
171 <html>
172 <head>
173 <title>Hello people</title>
174 </head>
175 <body>
176 <h1 id="greeting">Placeholder</h1>
177 <div id="list">
178 <span>
179 <p>Name: <span class="name">Bob</span></p>
180 <p>Age: <span class="age">23</span></p>
181 </span>
182 <hr class="between" />
183 </div>
184 </body>
185 </html>
186 HTML
187
188 my $output = HTML::Zoom
189 ->from_html($template)
190 ->select('title, #greeting')->replace_content('Hello world & dog!')
191 ->select('#list')->repeat_content(
192 [
193 sub {
194 $_->select('.name')->replace_content('Matt')
195 ->select('.age')->replace_content('26')
196 },
197 sub {
198 $_->select('.name')->replace_content('Mark')
199 ->select('.age')->replace_content('0x29')
200 },
201 sub {
202 $_->select('.name')->replace_content('Epitaph')
203 ->select('.age')->replace_content('<redacted>')
204 },
205 ],
206 { repeat_between => '.between' }
207 )
208 ->to_html;
209
210will produce:
211
212=begin testinfo
213
214 my $expect = <<HTML;
215
216=end testinfo
217
218 <html>
219 <head>
220 <title>Hello world &amp; dog!</title>
221 </head>
222 <body>
223 <h1 id="greeting">Hello world &amp; dog!</h1>
224 <div id="list">
225 <span>
226 <p>Name: <span class="name">Matt</span></p>
227 <p>Age: <span class="age">26</span></p>
228 </span>
229 <hr class="between" />
230 <span>
231 <p>Name: <span class="name">Mark</span></p>
232 <p>Age: <span class="age">0x29</span></p>
233 </span>
234 <hr class="between" />
235 <span>
236 <p>Name: <span class="name">Epitaph</span></p>
237 <p>Age: <span class="age">&lt;redacted&gt;</span></p>
238 </span>
239
240 </div>
241 </body>
242 </html>
243
244=begin testinfo
245
246 HTML
247 is($output, $expect, 'Synopsis code works ok');
248
249=end testinfo
250
1c4455ae 251=head1 DANGER WILL ROBINSON
252
253This is a 0.9 release. That means that I'm fairly happy the API isn't going
254to change in surprising and upsetting ways before 1.0 and a real compatibility
255freeze. But it also means that if it turns out there's a mistake the size of
256a politician's ego in the API design that I haven't spotted yet there may be
257a bit of breakage between here and 1.0. Hopefully not though. Appendages
258crossed and all that.
259
260Worse still, the rest of the distribution isn't documented yet. I'm sorry.
261I suck. But lots of people have been asking me to ship this, docs or no, so
262having got this class itself at least somewhat documented I figured now was
263a good time to cut a first real release.
264
265=head1 DESCRIPTION
266
267HTML::Zoom is a lazy, stream oriented, streaming capable, mostly functional,
268CSS selector based semantic templating engine for HTML and HTML-like
269document formats.
270
271Which is, on the whole, a bit of a mouthful. So let me step back a moment
272and explain why you care enough to understand what I mean:
273
274=head2 JQUERY ENVY
275
276HTML::Zoom is the cure for JQuery envy. When your javascript guy pushes a
277piece of data into a document by doing:
278
279 $('.username').replaceAll(username);
280
281In HTML::Zoom one can write
282
283 $zoom->select('.username')->replace_content($username);
284
285which is, I hope, almost as clear, hampered only by the fact that Zoom can't
286assume a global document and therefore has nothing quite so simple as the
287$() function to get the initial selection.
288
289L<HTML::Zoom::SelectorParser> implements a subset of the JQuery selector
290specification, and will continue to track that rather than the W3C standards
291for the forseeable future on grounds of pragmatism. Also on grounds of their
292spec is written in EN_US rather than EN_W3C, and I read the former much better.
293
294I am happy to admit that it's very, very much a subset at the moment - see the
295L<HTML::Zoom::SelectorParser> POD for what's currently there, and expect more
296and more to be supported over time as we need it and patch it in.
297
298=head2 CLEAN TEMPLATES
299
300HTML::Zoom is the cure for messy templates. How many times have you looked at
301templates like this:
302
303 <form action="/somewhere">
304 [% FOREACH field IN fields %]
305 <label for="[% field.id %]">[% field.label %]</label>
306 <input name="[% field.name %]" type="[% field.type %]" value="[% field.value %]" />
307 [% END %]
308 </form>
309
310and despaired of the fact that neither the HTML structure nor the logic are
311remotely easy to read? Fortunately, with HTML::Zoom we can separate the two
312cleanly:
313
314 <form class="myform" action="/somewhere">
315 <label />
316 <input />
317 </form>
318
319 $zoom->select('.myform')->repeat_content([
320 map { my $field = $_; sub {
321
322 $_->select('label')
2daa653a 323 ->add_to_attribute( for => $field->{id} )
1c4455ae 324 ->then
325 ->replace_content( $field->{label} )
326
327 ->select('input')
2daa653a 328 ->add_to_attribute( name => $field->{name} )
1c4455ae 329 ->then
2daa653a 330 ->add_to_attribute( type => $field->{type} )
1c4455ae 331 ->then
2daa653a 332 ->add_to_attribute( value => $field->{value} )
1c4455ae 333
334 } } @fields
335 ]);
336
337This is, admittedly, very much not shorter. However, it makes it extremely
338clear what's happening and therefore less hassle to maintain. Especially
339because it allows the designer to fiddle with the HTML without cutting
340himself on sharp ELSE clauses, and the developer to add available data to
341the template without getting angle bracket cuts on sensitive parts.
342
343Better still, HTML::Zoom knows that it's inserting content into HTML and
344can escape it for you - the example template should really have been:
345
346 <form action="/somewhere">
347 [% FOREACH field IN fields %]
348 <label for="[% field.id | html %]">[% field.label | html %]</label>
349 <input name="[% field.name | html %]" type="[% field.type | html %]" value="[% field.value | html %]" />
350 [% END %]
351 </form>
352
353and frankly I'll take slightly more code any day over *that* crawling horror.
354
355(addendum: I pick on L<Template Toolkit|Template> here specifically because
356it's the template system I hate the least - for text templating, I don't
357honestly think I'll ever like anything except the next version of Template
358Toolkit better - but HTML isn't text. Zoom knows that. Do you?)
359
360=head2 PUTTING THE FUN INTO FUNCTIONAL
361
362The principle of HTML::Zoom is to provide a reusable, functional container
363object that lets you build up a set of transforms to be applied; every method
364call you make on a zoom object returns a new object, so it's safe to do so
365on one somebody else gave you without worrying about altering state (with
366the notable exception of ->next for stream objects, which I'll come to later).
367
368So:
369
370 my $z2 = $z1->select('.name')->replace_content($name);
371
372 my $z3 = $z2->select('.title')->replace_content('Ms.');
373
374each time produces a new Zoom object. If you want to package up a set of
375transforms to re-use, HTML::Zoom provides an 'apply' method:
376
377 my $add_name = sub { $_->select('.name')->replace_content($name) };
378
379 my $same_as_z2 = $z1->apply($add_name);
380
381=head2 LAZINESS IS A VIRTUE
382
383HTML::Zoom does its best to defer doing anything until it's absolutely
384required. The only point at which it descends into state is when you force
385it to create a stream, directly by:
386
c9e76777 387 my $stream = $zoom->to_stream;
1c4455ae 388
389 while (my $evt = $stream->next) {
390 # handle zoom event here
391 }
392
393or indirectly via:
394
395 my $final_html = $zoom->to_html;
396
397 my $fh = $zoom->to_fh;
398
399 while (my $chunk = $fh->getline) {
400 ...
401 }
402
403Better still, the $fh returned doesn't create its stream until the first
404call to getline, which means that until you call that and force it to be
405stateful you can get back to the original stateless Zoom object via:
406
407 my $zoom = $fh->to_zoom;
408
409which is exceedingly handy for filtering L<Plack> PSGI responses, among other
410things.
411
412Because HTML::Zoom doesn't try and evaluate everything up front, you can
413generally put things together in whatever order is most appropriate. This
414means that:
415
416 my $start = HTML::Zoom->from_html($html);
417
418 my $zoom = $start->select('div')->replace_content('THIS IS A DIV!');
419
420and:
421
422 my $start = HTML::Zoom->select('div')->replace_content('THIS IS A DIV!');
423
424 my $zoom = $start->from_html($html);
425
426will produce equivalent final $zoom objects, thus proving that there can be
427more than one way to do it without one of them being a
428L<bait and switch|Switch>.
429
430=head2 STOCKTON TO DARLINGTON UNDER STREAM POWER
431
432HTML::Zoom's execution always happens in terms of streams under the hood
433- that is, the basic pattern for doing anything is -
434
435 my $stream = get_stream_from_somewhere
436
437 while (my ($evt) = $stream->next) {
438 # do something with the event
439 }
440
441More importantly, all selectors and filters are also built as stream
442operations, so a selector and filter pair is effectively:
443
444 sub next {
445 my ($self) = @_;
446 my $next_evt = $self->parent_stream->next;
447 if ($self->selector_matches($next_evt)) {
448 return $self->apply_filter_to($next_evt);
449 } else {
450 return $next_evt;
451 }
452 }
453
454Internally, things are marginally more complicated than that, but not enough
455that you as a user should normally need to care.
456
457In fact, an HTML::Zoom object is mostly just a container for the relevant
458information from which to build the final stream that does the real work. A
459stream built from a Zoom object is a stream of events from parsing the
460initial HTML, wrapped in a filter stream per selector/filter pair provided
461as described above.
462
463The upshot of this is that the application of filters works just as well on
464streams as on the original Zoom object - in fact, when you run a
465L</repeat_content> operation your subroutines are applied to the stream for
466that element of the repeat, rather than constructing a new zoom per repeat
467element as well.
468
469More concretely:
470
471 $_->select('div')->replace_content('I AM A DIV!');
472
473works on both HTML::Zoom objects themselves and HTML::Zoom stream objects and
474shares sufficient of the implementation that you can generally forget the
475difference - barring the fact that a stream already has state attached so
476things like to_fh are no longer available.
477
478=head2 POP! GOES THE WEASEL
479
480... and by Weasel, I mean layout.
481
482HTML::Zoom's filehandle object supports an additional event key, 'flush',
483that is transparent to the rest of the system but indicates to the filehandle
484object to end a getline operation at that point and return the HTML so far.
485
486This means that in an environment where streaming output is available, such
487as a number of the L<Plack> PSGI handlers, you can add the flush key to an
488event in order to ensure that the HTML generated so far is flushed through
489to the browser right now. This can be especially useful if you know you're
490about to call a web service or a potentially slow database query or similar
491to ensure that at least the header/layout of your page renders now, improving
492perceived user responsiveness while your application waits around for the
493data it needs.
494
495This is currently exposed by the 'flush_before' option to the collect filter,
496which incidentally also underlies the replace and repeat filters, so to
497indicate we want this behaviour to happen before a query is executed we can
498write something like:
499
500 $zoom->select('.item')->repeat(sub {
501 if (my $row = $db_thing->next) {
502 return sub { $_->select('.item-name')->replace_content($row->name) }
503 } else {
504 return
505 }
506 }, { flush_before => 1 });
507
508which should have the desired effect given a sufficiently lazy $db_thing (for
509example a L<DBIx::Class::ResultSet> object).
510
511=head2 A FISTFUL OF OBJECTS
512
513At the core of an HTML::Zoom system lurks an L<HTML::Zoom::ZConfig> object,
514whose purpose is to hang on to the various bits and pieces that things need
515so that there's a common way of accessing shared functionality.
516
517Were I a computer scientist I would probably call this an "Inversion of
518Control" object - which you'd be welcome to google to learn more about, or
519you can just imagine a computer scientist being suspended upside down over
520a pit. Either way works for me, I'm a pure maths grad.
521
522The ZConfig object hangs on to one each of the following for you:
523
524=over 4
525
526=item * An HTML parser, normally L<HTML::Zoom::Parser::BuiltIn>
527
528=item * An HTML producer (emitter), normally L<HTML::Zoom::Producer::BuiltIn>
529
530=item * An object to build event filters, normally L<HTML::Zoom::FilterBuilder>
531
532=item * An object to parse CSS selectors, normally L<HTML::Zoom::SelectorParser>
533
534=item * An object to build streams, normally L<HTML::Zoom::StreamUtils>
535
536=back
537
538In theory you could replace any of these with anything you like, but in
539practice you're probably best restricting yourself to subclasses, or at
540least things that manage to look like the original if you squint a bit.
541
542If you do something more clever than that, or find yourself overriding things
543in your ZConfig a lot, please please tell us about it via one of the means
544mentioned under L</SUPPORT>.
545
546=head2 SEMANTIC DIDACTIC
547
548Some will argue that overloading CSS selectors to do data stuff is a terrible
549idea, and possibly even a step towards the "Concrete Javascript" pattern
550(which I abhor) or Smalltalk's Morphic (which I ignore, except for the part
551where it keeps reminding me of the late, great Tony Hart's plasticine friend).
552
553To which I say, "eh", "meh", and possibly also "feh". If it really upsets
554you, either use extra classes for this (and remove them afterwards) or
555use special fake elements or, well, honestly, just use something different.
556L<Template::Semantic> provides a similar idea to zoom except using XPath
557and XML::LibXML transforms rather than a lightweight streaming approach -
558maybe you'd like that better. Or maybe you really did want
559L<Template Toolkit|Template> after all. It is still damn good at what it does,
560after all.
561
562So far, however, I've found that for new sites the designers I'm working with
563generally want to produce nice semantic HTML with classes that represent the
564nature of the data rather than the structure of the layout, so sharing them
565as a common interface works really well for us.
566
567In the absence of any evidence that overloading CSS selectors has killed
568children or unexpectedly set fire to grandmothers - and given microformats
569have been around for a while there's been plenty of opportunity for
570octagenarian combustion - I'd suggest you give it a try and see if you like it.
571
572=head2 GET THEE TO A SUMMARY!
573
574Erm. Well.
575
576HTML::Zoom is a lazy, stream oriented, streaming capable, mostly functional,
577CSS selector based semantic templating engine for HTML and HTML-like
578document formats.
579
580But I said that already. Although hopefully by now you have some idea what I
581meant when I said it. If you didn't have any idea the first time. I mean, I'm
582not trying to call you stupid or anything. Just saying that maybe it wasn't
583totally obvious without the explanation. Or something.
584
585Er.
586
587Maybe we should just move on to the method docs.
588
589=head1 METHODS
590
591=head2 new
592
593 my $zoom = HTML::Zoom->new;
594
595 my $zoom = HTML::Zoom->new({ zconfig => $zconfig });
596
597Create a new empty Zoom object. You can optionally pass an
598L<HTML::Zoom::ZConfig> instance if you're trying to override one or more of
599the default components.
600
601This method isn't often used directly since several other methods can also
602act as constructors, notable L</select> and L</from_html>
603
604=head2 zconfig
605
606 my $zconfig = $zoom->zconfig;
607
608Retrieve the L<HTML::Zoom::ZConfig> instance used by this Zoom object. You
609shouldn't usually need to call this yourself.
610
611=head2 from_html
612
613 my $zoom = HTML::Zoom->from_html($html);
614
615 my $z2 = $z1->from_html($html);
616
617Parses the HTML using the current zconfig's parser object and returns a new
618zoom instance with that as the source HTML to be transformed.
619
620=head2 from_file
621
622 my $zoom = HTML::Zoom->from_file($file);
623
624 my $z2 = $z1->from_file($file);
625
626Convenience method - slurps the contents of $file and calls from_html with it.
627
628=head2 to_stream
629
630 my $stream = $zoom->to_stream;
631
632 while (my ($evt) = $stream->next) {
633 ...
634
635Creates a stream, starting with a stream of the events from the HTML supplied
636via L</from_html> and then wrapping it in turn with each selector+filter pair
637that have been applied to the zoom object.
638
639=head2 to_fh
640
641 my $fh = $zoom->to_fh;
642
643 call_something_expecting_a_filehandle($fh);
644
645Returns an L<HTML::Zoom::ReadFH> instance that will create a stream the first
646time its getline method is called and then return all HTML up to the next
647event with 'flush' set.
648
649You can pass this filehandle to compliant PSGI handlers (and probably most
650web frameworks).
651
652=head2 run
653
654 $zoom->run;
655
656Runs the zoom object's transforms without doing anything with the results.
657
658Normally used to get side effects of a zoom run - for example when using
659L<HTML::Zoom::FilterBuilder/collect> to slurp events for scraping or layout.
660
661=head2 apply
662
663 my $z2 = $z1->apply(sub {
664 $_->select('div')->replace_content('I AM A DIV!') })
665 });
666
667Sets $_ to the zoom object and then runs the provided code. Basically syntax
668sugar, the following is entirely equivalent:
669
670 my $sub = sub {
671 shift->select('div')->replace_content('I AM A DIV!') })
672 };
673
674 my $z2 = $sub->($z1);
675
676=head2 to_html
677
678 my $html = $zoom->to_html;
679
680Runs the zoom processing and returns the resulting HTML.
681
682=head2 memoize
683
684 my $z2 = $z1->memoize;
685
686Creates a new zoom whose source HTML is the results of the original zoom's
687processing. Effectively syntax sugar for:
688
689 my $z2 = HTML::Zoom->from_html($z1->to_html);
690
691but preserves your L<HTML::Zoom::ZConfig> object.
692
693=head2 with_filter
694
695 my $zoom = HTML::Zoom->with_filter(
696 'div', $filter_builder->replace_content('I AM A DIV!')
697 );
698
699 my $z2 = $z1->with_filter(
700 'div', $filter_builder->replace_content('I AM A DIV!')
701 );
702
703Lower level interface than L</select> to adding filters to your zoom object.
704
705In normal usage, you probably don't need to call this yourself.
706
707=head2 select
708
709 my $zoom = HTML::Zoom->select('div')->replace_content('I AM A DIV!');
710
711 my $z2 = $z1->select('div')->replace_content('I AM A DIV!');
712
97192b02 713Returns an intermediary object of the class L<HTML::Zoom::TransformBuilder>
1c4455ae 714on which methods of your L<HTML::Zoom::FilterBuilder> object can be called.
715
716In normal usage you should generally always put the pair of method calls
717together; the intermediary object isn't designed or expected to stick around.
718
719=head2 then
720
2daa653a 721 my $z2 = $z1->select('div')->add_to_attribute(class => 'spoon')
1c4455ae 722 ->then
723 ->replace_content('I AM A DIV!');
724
725Re-runs the previous select to allow you to chain actions together on the
726same selector.
727
b9c27a5a 728=head1 AUTOLOAD METHODS
729
730L<HTML::Zoom> AUTOLOADS methods against L</select> so that you can reduce a
731certain amount of boilerplate typing. This allows you to replace:
732
733 $z->select('div')->replace_content("Hello World");
734
735With:
736
737 $z->replace_content(div => "Hello World");
738
739Besides saving a few keys per invocations, you may feel this looks neater
740in your code and increases understanding.
741
f107bef7 742=head1 AUTHOR
45b4cea1 743
f107bef7 744mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
45b4cea1 745
f107bef7 746=head1 CONTRIBUTORS
45b4cea1 747
f107bef7 748Oliver Charles
749
750Jakub Nareski
751
752Simon Elliot
753
754Joe Highton
755
756John Napiorkowski
757
758=head1 COPYRIGHT
759
760Copyright (c) 2010-2011 the HTML::Zoom L</AUTHOR> and L</CONTRIBUTORS>
761as listed above.
45b4cea1 762
763=head1 LICENSE
764
765This library is free software, you can redistribute it and/or modify
766it under the same terms as Perl itself.
767
d80786d0 768=cut
45b4cea1 769