1 2 3# 4# This is a template file, intended to be used as a startup frame when writing new formatters. 5# Search for TODO remarks and follow the instructions therein. 6# 7# Optional methods are marked. 8# 9 10 11# = HISTORY SECTION ===================================================================== 12 13# --------------------------------------------------------------------------------------- 14# version | date | author | changes 15# --------------------------------------------------------------------------------------- 16# 0.01 |<data> | <author> | new. 17# --------------------------------------------------------------------------------------- 18 19# = POD SECTION ========================================================================= 20 21=head1 NAME 22 23B<PerlPoint::Generator::LANGUAGE::Formatter> - generates Formatter formatted LANGUAGE files 24 25=head1 VERSION 26 27This manual describes version B<0.01>. 28 29=head1 SYNOPSIS 30 31 32 33=head1 DESCRIPTION 34 35 36=head1 METHODS 37 38=cut 39 40 41 42 43# check perl version 44require 5.00503; 45 46# = PACKAGE SECTION (internal helper package) ========================================== 47 48# declare package 49package PerlPoint::Generator::LANGUAGE::Formatter; 50 51# declare package version 52$VERSION=0.01; 53$AUTHOR='<author> (<mail>), <(c)-year>'; 54 55 56 57# = PRAGMA SECTION ======================================================================= 58 59# set pragmata 60use strict; 61 62# inherit from common generator class 63use base qw(PerlPoint::Generator::LANGUAGE); 64 65# declare object data fields 66use fields qw( 67 # TODO: insert the fields you need 68 ); 69 70 71# = LIBRARY SECTION ====================================================================== 72 73# load modules 74use Carp; 75use PerlPoint::Constants; 76use PerlPoint::Converters; 77use PerlPoint::Generator::LANGUAGE; # older perls need this, newer perls do it with "use base" 78 79# = CODE SECTION ========================================================================= 80 81 82=pod 83 84=head2 new() 85 86 87B<Parameters:> 88 89=over 4 90 91=item class 92 93The class name. 94 95=back 96 97B<Returns:> the new object. 98 99B<Example:> 100 101 102=cut 103sub new 104 { 105 # get parameter 106 my ($class, %params)=@_; 107 108 # check parameters 109 confess "[BUG] Missing class name.\n" unless $class; 110 confess "[BUG] Missing options hash.\n" unless exists $params{options}; 111 112 # build object 113 my $me; 114 $me=fields::new($class); 115 116 # store options of interest (really necessary?) 117 $me->{options}{$_}=$params{options}{$_} 118 for grep(exists $params{options}{$_}, qw( 119 ), 120 ); 121 122 # supply new object 123 $me; 124 } 125 126 127# provide option declarations 128sub declareOptions 129 { 130 # get and check parameters 131 (my __PACKAGE__ $me)=@_; 132 confess "[BUG] Missing object parameter.\n" unless $me; 133 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 134 135 # start with the base options 136 $me->readOptions($me->SUPER::declareOptions); 137 138 # now add your own 139 ( 140 [ 141 # TODO: add options used by this formatter, in Getopt::Long syntax. 142 # Remember to supply help for your options in help(), see below. 143 "example=s", # an example option; 144 ], 145 146 # and base options that we ignore 147 [ 148 # TODO: list options of base classes that become *invalid* by using this formatter. 149 qw( 150 baseexample 151 ), 152 ], 153 ); 154 } 155 156 157# provide help portions 158sub help 159 { 160 # to get a flexible tool, help texts are supplied in portions 161 { 162 # supply the options part 163 OPTIONS => { 164 # TODO: supply help for all options that were added by this module 165 # (see declareOptions() above). Use the option name as keys 166 # and the help texts as values of your entries. You may embed 167 # POD markup, but please dont use headlines and lists. 168 example => qq(an example option), 169 }, 170 171 # supply synopsis part 172 # TODO: add a *formatter specific* part of the SYNOPSIS that is displayed when 173 # a user specifies -help. Your part will be mixed with others, hierarchically. 174 # Make sure to check the result. 175 SYNOPSIS => <<EOS, 176 177According to your formatter choice, the LANGUAGE produced will be formatted by I<Formatter>. 178 179EOS 180 } 181 } 182 183 184# provide source filter declarations 185# 186# TODO: adapt this method to reply a list of patterns for languages that can be embedded into 187# PerlPoint sources when using this formatter. Please note that both base classes can 188# provide a list already, which you might want to extend or restrict. 189# 190# OPTIONAL METHOD: if you don't want to modify the inherited list, just remove this method. 191# 192sub sourceFilters 193 { 194 # get and check parameters 195 (my __PACKAGE__ $me)=@_; 196 confess "[BUG] Missing object parameter.\n" unless $me; 197 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 198 199 # get the common parent class list, replace a few items and provide the result 200 ( 201 grep($_!~/^example$/i, $me->SUPER::sourceFilters), # parent class list, but we do not support pure EXAMPLE 202 "myown", # embedded MYOWN; 203 ); 204 } 205 206# check usage 207sub checkUsage 208 { 209 # get and check parameters 210 (my __PACKAGE__ $me)=@_; 211 confess "[BUG] Missing object parameter.\n" unless $me; 212 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 213 214 # don't forget the base class (checks translations as well) 215 $me->SUPER::checkUsage; 216 217 # adapt tag translations (allows to use several standard XML converters) 218 $me->{options}{tagtrans}=[ 219 qw( 220 TABLE_COL:td 221 TABLE_HL:th 222 TABLE_ROW:tr 223 anchor:a 224 dlist:dl 225 dpointitem:dt 226 dpointtext:dd 227 example:pre 228 olist:ol 229 opoint:li 230 text:p 231 ulist:ul 232 upoint:li 233 ) 234 ]; 235 236 # check your own options 237 die "[Fatal] Please specify the name of the result file by -xmlfile.\n" unless exists $me->{options}{xmlfile}; 238 not -e $me->{options}{xmlfile} or -w _ or die "[Fatal] XML file $me->{options}{xmlfile} cannot be written.\n"; 239 } 240 241 242# sub bootstrap 243# { 244# # get and check parameters 245# (my __PACKAGE__ $me)=@_; 246# confess "[BUG] Missing object parameter.\n" unless $me; 247# confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 248 249# # don't forget the base class 250# $me->SUPER::bootstrap; 251# } 252 253 254 255# initializations done when a backend is available 256sub initBackend 257 { 258 # get and check parameters 259 (my __PACKAGE__ $me)=@_; 260 confess "[BUG] Missing object parameter.\n" unless $me; 261 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 262 263 # don't forget the base class 264 $me->SUPER::initBackend; 265 266 # open result file 267 my $handle=$me->{targethandle}=new IO::File(">$me->{options}{xmlfile}"); 268 269 # start the page 270 # print $handle $me->{xml}->xmldecl({version => 1.0,}); 271 print $handle $me->{xml}->xmldtd( 272 [ 273 # document type (needs to match the root element) 274 'html', 275 276 # external id 277 qq(PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"), 278 279 # DTD 280 qq("http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd"), 281 ] 282 ), "\n\n"; 283 } 284 285# headline formatter 286sub formatHeadline 287 { 288 # get and check parameters 289 ((my __PACKAGE__ $me), my ($page, $item))=@_; 290 confess "[BUG] Missing object parameter.\n" unless $me; 291 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 292 confess "[BUG] Missing page data parameter.\n" unless $page; 293 confess "[BUG] Missing item parameter.\n" unless defined $item; 294 295 # build the tag name and a path 296 my $tag="h$item->{cfg}{data}{level}"; 297 my $path=$page->path(type=>'fpath', mode=>'full', delimiter=>'|'); 298 299 # build the headline, store it with anchors 300 ( 301 $me->{xml}->a({name=>$item->{cfg}{data}{full}}), 302 $path ne $item->{cfg}{data}{full} ? $me->{xml}->a({name=>$path}) : (), 303 $me->{xml}->$tag(@{$item->{parts}}), 304 ) 305 } 306 307 308# tag formatter 309sub formatTag 310 { 311 # get and check parameters 312 ((my __PACKAGE__ $me), my ($page, $item))=@_; 313 confess "[BUG] Missing object parameter.\n" unless $me; 314 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 315 confess "[BUG] Missing page data parameter.\n" unless $page; 316 confess "[BUG] Missing item parameter.\n" unless defined $item; 317 318 # declarations 319 my ($directive, $xmltag, @results)=(''); 320 321 # handle the various tags 322 if ($item->{cfg}{data}{name} eq 'INDEX') 323 { 324 # scopies 325 my (%index, %anchors); 326 327 # index: get data structure 328 my $anchors=$item->{cfg}{data}{options}{__anchors}; 329 330 # start with an anchor and a navigation bar ... 331 push(@results, $me->{xml}->a({name=>(my $bar)=$me->{anchorfab}->generic})); 332 push(@results, map {$anchors{$_}=$me->{anchorfab}->generic; $me->{xml}->a({href=>"#$anchors{$_}"}, $_)} sort keys %$anchors); 333 334 # now, traverse all groups and build their index 335 foreach my $group (sort keys %$anchors) 336 { 337 # make the character a "headline", linking back to the navigation bar 338 push(@results, $me->{xml}->p($me->{xml}->strong($me->{xml}->a({href=>"#$bar"}, $group)))); 339 340 # now add all the index entries 341 push( 342 @results, 343 $me->{xml}->div( 344 {class => 'indexgroup'}, 345 map 346 { 347 ( 348 # first, the entry string 349 $me->{xml}->div( 350 {class => 'indexitem'}, 351 $_->[0], 352 ), 353 354 # then, the list of occurences 355 $me->{xml}->div( 356 {class => 'indexreflist'}, 357 map 358 { 359 # an occurence reference 360 $me->{xml}->a({href=>"#$_->[0]"}, $_->[1]), 361 } grep(ref($_), @{$_->[1]}) # TODO: check the structure! Why (doubled) scalars? 362 ), 363 ) 364 } @{$anchors->{$group}}, 365 ), 366 ); 367 } 368 } 369 elsif ($item->{cfg}{data}{name} eq 'L') 370 { 371 # link: build it 372 @results=$me->{xml}->a( 373 { 374 href => $item->{cfg}{data}{options}{url}, 375 }, 376 @{$item->{parts}}, 377 ); 378 } 379 elsif ($item->{cfg}{data}{name} eq 'LOCALTOC') 380 { 381 # local toc: subchapters defined? 382 if (exists $item->{cfg}{data}{options}{__rawtoc__} and @{$item->{cfg}{data}{options}{__rawtoc__}}) 383 { 384 # get type flag, store it more readable 385 my $plain=($item->{cfg}{data}{options}{type} eq 'plain'); 386 387 # make a temporary headline path array copy 388 my @localHeadlinePath=@{$page->path(type=>'fpath', mode=>'array')}; 389 390 # prepare a subroutine to build links, if necessary 391 my $link; 392 unless ($plain) 393 { 394 $link=sub 395 { 396 # take parameters 397 my ($level, $title)=@_; 398 399 # update headline path (so that it describes the complete 400 # path of the future chapter then) 401 $localHeadlinePath[$level-1]=$title; 402 403 # supply the path of the upcoming chapter 404 @results=$me->{xml}->a( 405 { 406 href => join('', '#', 407 join('|', 408 map {defined($_) ? $_ : ''} @localHeadlinePath[0..$level-1], 409 ), 410 ), 411 }, 412 $title, 413 ); 414 } 415 } 416 417 # use a more readable toc variable 418 my $toc=$item->{cfg}{data}{options}{__rawtoc__}; 419 420 # make it a list of the requested format 421 if ($item->{cfg}{data}{options}{format}=~/^(bullets|enumerated|numbers)$/) 422 { 423 # start a stack of intermediate level results 424 my @buffered; 425 426 # setup method name to build a (partial) list 427 my $listMethodName=$item->{cfg}{data}{options}{format}=~/^(bullets|numbers)$/ ? 'ul' : 'ol'; 428 429 # make a temporary headline number array copy (used for numbered lists) 430 my @localHeadlineNumbers=@{$page->path(type=>'npath', mode=>'array')}; 431 432 # calculate initial level depth for indentation (real headline levels should 433 # not be reflected in a TOC listing them - a TOC *list* should start at level 1) 434 my $startLevel=@localHeadlineNumbers; 435 436 # traverse TOC entries 437 foreach (@$toc) 438 { 439 # get level and title 440 my ($level, $title)=@$_; 441 442 # update headline numbering 443 $localHeadlineNumbers[$level-1]++; 444 445 # previous level closed? 446 if ($level<@buffered) 447 { 448 # complete closed levels and integrate them as lists 449 push(@{$buffered[$_-1]}, $me->{xml}->$listMethodName(@{$buffered[$_]})), 450 for reverse $level..@buffered-1; 451 452 # delete all buffer levels which were integrated, 453 # delete headline numbers of the levels that were closed 454 $#buffered=$#localHeadlineNumbers=$level-1; 455 } 456 457 # buffer item on current level 458 push(@{$buffered[$level-1]}, $me->{xml}->li( 459 # write numbers if we are building a numbered lists 460 $item->{cfg}{data}{options}{format} eq 'numbers' ? join('', join('.', @localHeadlineNumbers[0..$level-1]), '. ') : (), 461 $plain ? $title : $link->(@$_) 462 ) 463 ); 464 } 465 466 # close open lists (down to the initial level depth) 467 push(@{$buffered[$_-1]}, $me->{xml}->$listMethodName(@{$buffered[$_]})), 468 for reverse $startLevel+1 .. @buffered-1; 469 470 # finally, build the list (on startup level), including nested lists eventually 471 @results=$me->{xml}->$listMethodName(@{$buffered[$startLevel]}); 472 } 473 else 474 {die "[BUG] Unhandled case $item->{cfg}{data}{options}{format}."} 475 } 476 else 477 { 478 # oops - there are no subchapters 479 @results=(); 480 } 481 } 482 elsif ($item->{cfg}{data}{name} eq 'REF') 483 { 484 # scopies 485 my ($label); 486 487 # catch target 488 my $target=$item->{cfg}{data}{options}{name}; 489 490 # get the upcoming chapters data 491 my @chapters=$me->getChapterByPath($target); 492 493 # Anything found? Otherwise search for an anchor of the target name and get its page data 494 unless ($chapters[0][0]) 495 { 496 # get value and page 497 my $data=$me->getAnchorData($target); 498 499 # anything found? 500 if (defined $data) 501 { 502 # get chapter data 503 @chapters=$me->getChapterByPagenr($data->[1]); 504 505 # set up local link 506 $label=$target; 507 } 508 } 509 510 # build text to display: is there a body? 511 if ($item->{cfg}{data}{options}{__body__}) 512 { 513 # yes: the tag body is our text 514 @results=@{$item->{parts}}; 515 } 516 else 517 { 518 # no body: what we display depends on option "valueformat" 519 if ($item->{cfg}{data}{options}{valueformat} eq 'pure') 520 { 521 # display the value of the requested object 522 @results=$item->{cfg}{data}{options}{__value__}; 523 } 524 elsif ($item->{cfg}{data}{options}{valueformat} eq 'pagetitle') 525 { 526 # display the objects page title, to be found in target data 527 @results=$chapters[0][2]; 528 } 529 elsif ($item->{cfg}{data}{options}{valueformat} eq 'pagenr') 530 { 531 # display the objects page number (for more generic or configurable numbers, 532 # this could be done by a function) 533 @results=join('.', @{$chapters[0][7]}, ''); 534 } 535 else 536 {die "[BUG] Unhandled case $item->{cfg}{data}{options}{valueformat}."} 537 } 538 539 # if we do not build a link, we are already done now, otherwise, we have to 540 # add link syntax 541 if ($item->{cfg}{data}{options}{type} eq 'linked') 542 { 543 # build target chapter file name, using the absolute page number 544 my $link=$me->_buildFilename($chapters[0][0]); 545 546 # add lokal link, if necessary 547 $link=join('#', $link, $label) if $label; 548 549 # now build the link 550 @results=$me->{xml}->a({href => $link}, @results); 551 } 552 } 553 elsif ($item->{cfg}{data}{name} eq 'SEQ') 554 { 555 # sequence: pure text for now, possibly anchored 556 # (what's best to keep the sequence type?) 557 if (exists $item->{cfg}{data}{options}{name}) 558 { 559 @results=$me->{xml}->A( 560 { 561 name => $item->{cfg}{data}{options}{name}, 562 }, 563 $item->{cfg}{data}{options}{__nr__}, 564 ); 565 } 566 else 567 {@results=$item->{cfg}{data}{options}{__nr__};} 568 } 569 elsif ($item->{cfg}{data}{name} eq 'TABLE') 570 { 571 # build the table 572 @results=$me->{xml}->table(@{$item->{parts}}); 573 } 574 elsif ($item->{cfg}{data}{name} eq 'TABLE_COL') 575 { 576 # build the cell 577 @results=$me->{xml}->td( 578 { 579 }, 580 @{$item->{parts}} ? @{$item->{parts}} : ' ' 581 ); 582 } 583 elsif ($item->{cfg}{data}{name} eq 'X') 584 { 585 # index entry: transform it into an anchor 586 @results=$me->{xml}->a( 587 { 588 name => $item->{cfg}{data}{options}{__anchor}, 589 }, 590 @{$item->{parts}}, 591 ); 592 } 593 else 594 { 595 # invoke base method 596 return $me->SUPER::formatTag(@_[1..$#_]); 597 } 598 599 # supply results 600 # warn @results; 601 @results; 602 } 603 604 605# docstream entry formatter 606sub formatDStreamEntrypoint 607 { 608 # get and check parameters 609 ((my __PACKAGE__ $me), my ($page, $item))=@_; 610 confess "[BUG] Missing object parameter.\n" unless $me; 611 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 612 confess "[BUG] Missing page data parameter.\n" unless $page; 613 confess "[BUG] Missing item parameter.\n" unless $item; 614 615 # provide the parts 616 $me->{xml}->div({class=>$item->{cfg}{data}{name}}, @{$item->{parts}}); 617 } 618 619 620 621# docstream frame formatter 622sub formatDStreamFrame 623 { 624 # get and check parameters 625 ((my __PACKAGE__ $me), my ($page, $item))=@_; 626 confess "[BUG] Missing object parameter.\n" unless $me; 627 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 628 confess "[BUG] Missing page data parameter.\n" unless $page; 629 confess "[BUG] Missing item parameter.\n" unless $item; 630 631 # provide the parts 632 $me->{xml}->div({class=>'streamFrame'}, @{$item->{parts}}); 633 } 634 635 636# page formatter 637sub formatPage 638 { 639 # get and check parameters 640 ((my __PACKAGE__ $me), my ($page, $item))=@_; 641 confess "[BUG] Missing object parameter.\n" unless $me; 642 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 643 confess "[BUG] Missing page data parameter.\n" unless $page; 644 confess "[BUG] Missing item parameter.\n" unless $item; 645 646 # store this slide - note that there is no frame in XHTML, 647 # a slide just starts with its headline 648 push(@{$me->{slides}}, @{$item->{parts}}) 649 if @{$item->{parts}} and grep(ref($_)!~/::comment$/, @{$item->{parts}}); 650 651 # supply nothing directly 652 ''; 653 } 654 655# finish 656sub finish 657 { 658 # get and check parameters 659 (my __PACKAGE__ $me)=@_; 660 confess "[BUG] Missing object parameter.\n" unless $me; 661 confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and $me->isa(__PACKAGE__); 662 663 # don't forget the base class 664 $me->SUPER::finish; 665 666 # write document 667 my ($handle)=($me->{targethandle}); 668 print $handle $me->{xml}->html( 669 { 670 #xmlns => qq("http://www.w3c.org/1999/xhtml"), 671 #'xml:lang' => qq("en"), 672 #lang => qq("en"), 673 }, 674 675 # add meta data part 676 $me->{xml}->head( 677 # title (mandatory) ... 678 $me->{xml}->title($me->{options}{doctitle}), 679 680 # stylesheets 681 exists $me->{options}{css} ? map 682 { 683 # extract title and filename 684 my ($file, $title)=split(':', $me->{options}{css}[$_], 2); 685 $title="unnamed $_" if $_ and not $title; 686 687 $me->{xml}->link( 688 { 689 href => $file, 690 $_ ? (title=>$title) : (), 691 rel => $_<2 ? 'stylesheet' : 'alternate stylesheet', 692 type => 'text/css', 693 }, 694 ) 695 } 0..$#{$me->{options}{css}} : (), 696 697 # the "doc..." options are reserved for further data 698 # ... stored in meta tags 699 map 700 { 701 /^doc(.+)$/; 702 my $tag=$me->elementName("_$1"); 703 $me->{xml}->meta({name=>$tag, contents=>$me->{options}{$_}}), 704 } sort grep((/^doc/ and not /^doctitle/), keys %{$me->{options}}), 705 ), 706 707 # embed slides into a special section 708 $me->{xml}->body(@{$me->{slides}}), 709 ), "\n\n"; 710 711 712 # close document 713 close($handle); 714 } 715 716 717# flag successfull loading 7181; 719 720 721# = POD TRAILER SECTION ================================================================= 722 723=pod 724 725=head1 NOTES 726 727 728=head1 SEE ALSO 729 730=over 4 731 732=item Base formatters 733 734This formatter inherits from C<PerlPoint::Generator> and C<PerlPoint::Generator::XML>. 735 736=back 737 738 739=head1 SUPPORT 740 741A PerlPoint mailing list is set up to discuss usage, ideas, 742bugs, suggestions and translator development. To subscribe, 743please send an empty message to perlpoint-subscribe@perl.org. 744 745If you prefer, you can contact me via perl@jochen-stenzel.de 746as well. 747 748=head1 AUTHOR 749 750Copyright (c) Jochen Stenzel (perl@jochen-stenzel.de), 2003-2004. 751All rights reserved. 752 753This module is free software, you can redistribute it and/or modify it 754under the terms of the Artistic License distributed with Perl version 7555.003 or (at your option) any later version. Please refer to the 756Artistic License that came with your Perl distribution for more 757details. 758 759The Artistic License should have been included in your distribution of 760Perl. It resides in the file named "Artistic" at the top-level of the 761Perl source tree (where Perl was downloaded/unpacked - ask your 762system administrator if you dont know where this is). Alternatively, 763the current version of the Artistic License distributed with Perl can 764be viewed on-line on the World-Wide Web (WWW) from the following URL: 765http://www.perl.com/perl/misc/Artistic.html 766 767 768=head1 DISCLAIMER 769 770This software is distributed in the hope that it will be useful, but 771is provided "AS IS" WITHOUT WARRANTY OF ANY KIND, either expressed or 772implied, INCLUDING, without limitation, the implied warranties of 773MERCHANTABILITY and FITNESS FOR A PARTICULAR PURPOSE. 774 775The ENTIRE RISK as to the quality and performance of the software 776IS WITH YOU (the holder of the software). Should the software prove 777defective, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR 778CORRECTION. 779 780IN NO EVENT WILL ANY COPYRIGHT HOLDER OR ANY OTHER PARTY WHO MAY CREATE, 781MODIFY, OR DISTRIBUTE THE SOFTWARE BE LIABLE OR RESPONSIBLE TO YOU OR TO 782ANY OTHER ENTITY FOR ANY KIND OF DAMAGES (no matter how awful - not even 783if they arise from known or unknown flaws in the software). 784 785Please refer to the Artistic License that came with your Perl 786distribution for more details. 787 788