Changeset 1638

Show
Ignore:
Timestamp:
02/05/05 14:19:45 (4 years ago)
Author:
whmoseley
Message:

Update POD link generation.

TODO: think about a two-pass parsing to build up targets, then when generating links
can lookup targets. This is to solve the problem of multiple targets when a name
is used in both a =head and a =item line.

-v should now report broken links.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/swish_website/Plugin/My/POD.pm

    r1633 r1638  
    44use Pod::POM::View::HTML; 
    55use base 'Template::Plugin'; 
    6  
    7 my %split_by = map {"head".$_ => 1} 1..4; 
     6use vars qw( $stash );  # for debugging links 
     7 
     8if ( my $ file = shift ) { 
     9    warn "Testing links\n"; 
     10    My::pod::test(); 
     11
     12 
    813 
    914my $view_mode = 'Pod::POM::View::HTML';  # The view module 
     
    2732    my ( $class, $context, $content ) = @_; 
    2833 
    29  
    30     # Grab pod index variable 
    31     my $stash = $context->stash; 
    32     my $page = $stash->get( 'page.id' ); 
     34    $stash = $context->stash; 
     35 
    3336    my $template_name = $stash->get( 'template.name' ); 
    3437 
     38    # Enable output of debugging messages from Pod::POM 
    3539    my $warn = sub { warn "[$template_name]: @_\n" } 
    3640        if $stash->get( 'self.config.verbose' ); 
    3741 
    38     my $parser = Pod::POM->new( warn => $warn );    # Make this a coderef to report name 
     42    my $parser = Pod::POM->new( warn => $warn ); 
     43 
     44 
     45    # Parse the pod into a Pod::POM tree 
    3946    my $pom = $parser->parse_text( $content ); 
    4047 
    41     combine_verbatim_sections_hack( $pom );     # merge sequential <pre> sections into one 
    42  
    43  
    44     my @sections = $pom->head1;                 # get pod into sections 
    45  
    46  
    47     # Structure for returning info to template 
    48  
    49     my %data = ( 
    50         pom         => $pom , 
    51         view        => 'My::Pod::View::HTML', 
    52         sections    => \@sections, 
    53         podparts    => [ slice_by_head(@sections) ], 
    54         toc         => fetch_toc( \@sections ), 
    55     ); 
    56  
    57  
    58     # Get title of document 
     48 
     49    # Merge sequential verbatim (<pre>) sections into one 
     50    combine_verbatim_sections_hack( $pom ); 
     51 
     52 
     53    my @sections = $pom->head1; # get pod into sections 
     54 
     55    # Get title of document and shift off first NAME section 
     56 
     57    my $doc_title; 
    5958    if ( @sections && $sections[0]->title =~ /NAME/ ) { 
    60         $data{title} = ( shift @sections )->content; 
    61         $data{title} =~ s/^\s*|\s*$//sg;  # strip; 
    62     } 
     59        $doc_title = ( shift @sections )->content; 
     60        $doc_title =~ s/^\s*|\s*$//sg;  # strip; 
     61    } 
     62 
    6363 
    6464 
     
    6868    if ( my $cache = $stash->get( 'toc_cache' ) ) { 
    6969        $cache->{ $template_name } = { 
    70             page    => $page
    71             title   => $data{title}
    72             abstract => fetch_abstract( $data{sections} ), 
     70            page    => $stash->get( 'page.id' )
     71            title   => $doc_title
     72            abstract => fetch_abstract( \@sections ), 
    7373        }; 
    7474    }; 
    7575 
    7676 
     77    # Structure for returning info to template 
     78    # the "toc" is the table of contents for the top of the page 
     79    # the "podparts" is the document broken into sequential parts by 
     80    # =head*  This allows the template to wrap each section in a <div> and 
     81    # therefore makes indexing each section with swish-e easy -- allows targeted searches 
     82 
     83    my %data = ( 
     84        pom         => $pom, 
     85        view        => 'My::Pod::View::HTML', 
     86        toc         => fetch_toc( @sections ), 
     87        podparts    => [ slice_by_head(@sections) ], # Must be lasts because modifies tree 
     88        title       => $doc_title, 
     89    ); 
     90 
     91 
     92 
    7793    return \%data; 
    7894} 
     
    8197 
    8298#----------------------------------------------------------------------------- 
     99# This function is modified from Stas' DocSet 
     100# It flattens the recursive structure of the tree into a list of sections by =head 
     101# 
     102# Pass in: 
     103#   @sections = list of head1 sections 
     104# 
     105# Returns: 
     106#   flat list of sections for each headX section 
     107# 
     108# Note: 
     109#   This MODIFIES the original tree!  Should probably clone and build a new tree. 
     110# 
     111# This is useful for printing and wrapping each headX section in, say, <div> 
    83112 
    84113sub slice_by_head { 
    85     my @sections = @_; 
     114    my @sections = @_;  # these are the sections at the current level. 
    86115    my @body = (); 
     116 
    87117    for my $node (@sections) { 
    88118        my @next = (); 
    89119        # assumption, after the first 'headX' section, there can only 
    90120        # be other 'headX' sections 
    91         my $count = scalar $node->content; 
     121 
     122        # Look for first =headX seciton and chop of the content there 
     123        # then process that new =headX section again 
     124 
    92125        my $id = -1; 
    93126        for ($node->content) { 
    94127            $id++; 
    95             next unless exists $split_by{ $_->type }; 
     128            # Keep content until a =head section is found.  Then end current content 
     129            # section at that point. 
     130 
     131            next unless $_->type =~ /^head\d$/; 
     132 
     133            # Modify original content to just include the nodes up to the first =head 
    96134            @next = splice @{$node->content}, $id; 
    97135            last; 
    98136        } 
     137 
     138        # combine all =head nodes (flatten) 
    99139        push @body, $node, slice_by_head(@next); 
    100140    } 
    101141    return @body; 
    102142} 
     143 
     144#--------------------------------------------------------------------------- 
     145# Merge verbatim sequential <pre> sections together 
    103146 
    104147sub combine_verbatim_sections_hack { 
     
    124167 
    125168 
     169 
     170#---------------------------------------------------------------------------- 
     171# Grabs the first paragraph from either DESCRIPTION or OVERVIEW -- whichever comes first 
     172 
     173 
    126174sub fetch_abstract { 
    127175    my ( $sections ) = @_; 
     
    136184 
    137185 
     186#--------------------------------------------------------------------------------- 
     187# Walk tree building up a TOC 
     188 
    138189sub fetch_toc { 
    139     my ( $sections ) = @_; 
     190    my ( @sections ) = @_; 
    140191 
    141192    my @toc = (); 
    142193    my $level = 1; 
    143     for my $node (@$sections) { 
     194    for my $node (@sections) { 
    144195        push @toc, render_toc_level($node, $level); 
    145196    } 
     
    151202sub render_toc_level { 
    152203    my( $node, $level) = @_; 
    153     my $title = $node->title
     204    my $title = $node->title->present($view_mode)
    154205    my $anchor = My::Pod::View::HTML->escape_name( $title ); 
    155206 
    156207    my %toc_entry = ( 
    157         title    => $title->present($view_mode), # run the formatting if any 
     208        title    => $title, 
    158209        link     => "#$anchor", 
    159210    ); 
     
    173224} 
    174225 
     226 
     227 
     228#---------------- Package to override Pod::POM::View::HTML ---------------- 
     229 
    175230package My::Pod::View::HTML; 
    176231use strict; 
    177232use warnings; 
    178233use base 'Pod::POM::View::HTML'; 
    179  
    180 sub view_head1 { 
    181     my ($self, $head1) = @_; 
    182     return "<h1>" . $self->anchor($head1->title) . "</h1>\n\n" . 
    183         $head1->content->present($self); 
    184 
    185  
    186 sub view_head2 { 
    187     my ($self, $head2) = @_; 
    188     return "<h2>" . $self->anchor($head2->title) . "</h2>\n\n" . 
    189         $head2->content->present($self); 
    190 
    191  
    192 sub view_head3 { 
    193     my ($self, $head3) = @_; 
    194     return "<h3>" . $self->anchor($head3->title) . "</h3>\n\n" . 
    195         $head3->content->present($self); 
    196 
    197  
    198 sub view_head4 { 
    199     my ($self, $head4) = @_; 
    200     return "<h4>" . $self->anchor($head4->title) . "</h4>\n\n" . 
    201         $head4->content->present($self); 
    202 
    203  
     234use HTML::Entities; 
     235use Template::Filters; 
     236use File::Basename; 
     237 
     238use vars qw( %targets @links ); 
     239 
     240 
     241#-------------- For checking that all pod links work ---------------- 
     242sub save_link { 
     243    my ($self, $href) = @_; 
     244 
     245 
     246    my $out_file = basename( $My::POD::stash->get( 'this.out_file' ) );  # for debugging links 
     247 
     248 
     249    push @links, { 
     250        href => $href, 
     251        on_page => $out_file, 
     252    }; 
     253
     254 
     255sub save_target { 
     256    my ($self, $target) = @_; 
     257    my $out_file = basename( $My::POD::stash->get( 'this.out_file' ) ); 
     258    $targets{$out_file}{$target}++; 
     259
     260 
     261sub validate_links { 
     262    my $all_files_processed = $My::POD::stash->get( 'self.config.all'); 
     263 
     264    for my $link ( @links ) { 
     265        my ($name, $fragment) = split /#/, $link->{href}, 2; 
     266 
     267        # If processing all files then can check for page links 
     268        if ( $all_files_processed ) { 
     269            if ( $name && !$targets{$name} ) { 
     270                warn "Link to page [$name] from $link->{on_page} could not be resolved\n"; 
     271                next; 
     272            } 
     273        } else { 
     274            next if $name && $name ne $link->{on_page};  # can't check (unless links are cached in the future 
     275        } 
     276 
     277        $name ||= $link->{on_page};  # default to current page; 
     278 
     279        # Check page link 
     280        unless ( $targets{ $name } ) { 
     281            warn "Link on page $link->{on_page} to page $name is unresolved\n"; 
     282            next; 
     283        } 
     284        next unless $fragment; 
     285 
     286        # And check frag if one 
     287        unless ( $targets{ $name}{ $fragment } ) { 
     288            warn "Link [$link->{href}] on page $link->{on_page} could not be resolved\n"; 
     289        } else { 
     290            warn "Link $link->{href} on page $link->{on_page} has multiple targets\n" 
     291                if $targets{$name}{$fragment} > 1; 
     292        } 
     293 
     294 
     295    } 
     296 
     297    @links = (); 
     298    %targets = (); 
     299
     300 
     301 
     302#---------------- Override Pod::POM --------------------------------- 
     303 
     304sub view_head1 { shift->show_head( @_ ) }; 
     305sub view_head2 { shift->show_head( @_ ) }; 
     306sub view_head3 { shift->show_head( @_ ) }; 
     307sub view_head4 { shift->show_head( @_ ) }; 
     308 
     309sub show_head {  # all all child nodes 
     310    my ($self, $head) = @_; 
     311    my $level = substr( $head->type, -1 ); 
     312    return "\n<h$level>" . $self->anchor($head->title) . "</h$level>\n\n" . 
     313        $head->content->present($self); 
     314
     315 
     316 
     317#----------------- <a name="foo"></a>foo --------------------- 
    204318sub anchor { 
    205319    my($self, $title) = @_; 
    206     my $text = $title->present($self); 
    207     $text = $self->escape_name( $text ); 
    208     return qq[<a name="$text"></a>$title]; 
    209 
    210  
    211 # This just adds a class attribute 
    212 sub view_verbatim { 
     320    my $text = $self->escape_name( $title->present($self) ); 
     321    my $t = Template::Filters::html_filter( $title ); 
     322    $self->save_target( $text ); 
     323    return qq[<a name="$text"></a>$t]; 
     324
     325 
     326#----------------- prepare text for name="" -------------------- 
     327# This first unescapes any entities, and then removes all non-word chars 
     328# and finally returns all lower case; 
     329 
     330 
     331sub escape_name { 
    213332    my ($self, $text) = @_; 
    214     return '' unless $text; 
    215     for ($text) { 
    216         s/&/&amp;/g; 
    217         s/</&lt;/g; 
    218         s/>/&gt;/g; 
    219     } 
    220  
    221     return qq{<pre class="pre-section">$text</pre>\n}; 
    222 
    223  
     333 
     334    # Comes in already HTML escaped -- well, kind of, quotes are not escaped. 
     335    my $plain = decode_entities( $text ); 
     336 
     337    # Not much is allowed in the name="" attribute, so just hack away: 
     338    $plain =~ s/\W+/_/g; 
     339    return lc $plain; 
     340
     341 
     342 
     343#----------------- Fixup L<> links in pod ------------------------------- 
    224344 
    225345# Thes first two fixup L<> links. 
    226346# Pod::POM doesn't provide a way to get at the fragment for escaping, 
    227 # so do it the hard way here. 
     347# so let Pod::POM deal with it and then then update 
    228348 
    229349sub view_seq_link { 
     
    231351    my $url = $self->SUPER::view_seq_link( @_ ); 
    232352    return unless $url; 
    233     $url = $1 . $self->escape_name($2) . $3 if $url =~ /^([^#]+#)([^"]+)(.+)$/; 
    234     $url =~ s/#item_/#/; 
     353 
     354    return unless $url =~ /href="(.+)(?=">)/; 
     355    my ( $href, $fragment ) = split /#/, $1, 2; 
     356 
     357    $href = '' unless defined $href; 
     358    $href .= '#'. $self->escape_name( $fragment ) if defined $fragment; 
     359 
     360    $self->save_link( $href );  # for checking that they all go some place. 
     361 
     362    # Now replace the href into the original string 
     363 
     364    $url =~ s/href=".+(?=">)/href="$href/; 
     365 
    235366    return $url; 
    236367} 
    237368 
    238 # This needs work -- Pod::POM doesn't uri escape the href, for one thing. 
    239 # This converts the file name part.  Can't call esacpe_name(), so do uri 
    240 # escape -- but that will not validate with xhtml in some cases.  So this i 
    241 # not really correct. 
     369 
     370# This allows fixing up links to *other* pages.  In our case, they are mostly to 
     371# other pod pages, which are now lower case and end in .html. 
     372 
    242373 
    243374sub view_seq_link_transform_path { 
    244375    my ( $self, $link ) = @_; 
    245     return $self->escape_uri( lc $link ) . '.html'; 
    246 
    247  
    248 # Returns $text as uri-escaped string 
    249 sub escape_uri { 
    250     my ($self, $text) = @_; 
    251     $text =~ s/\n/ /g; 
    252     return Template::Filters::uri_filter( $text ); 
    253 
    254  
    255 sub escape_name { 
    256     my ($self, $text) = @_; 
    257     $text =~ s/\W+/_/g; 
    258     return lc $text; 
    259 
    260  
    261  
    262 # Pod::POM::View::HTML: 
    263 # view_seq_link is broken in many ways 
    264 # L<Foo Bar> ends up as a section due to spaces 
    265 # L</foo> or L<|/foo> don't work, but L<foo|foo> does work 
    266 
    267 
    268 
     376    return lc $link . '.html'; 
     377
     378 
    269379 
    270380# Modified version of item display that removes the item_ prefix and only takes the first 
     
    276386# 
    277387# so we take only the first word.  But that breaks if linking to multi-word item. 
     388# 
    278389 
    279390sub view_item { 
     
    291402            $anchor =~ s/\s+.*$//;  # strip all trailing stuff from first space on 
    292403            $anchor = $self->escape_name( $anchor ); 
     404 
     405            $self->save_target( $anchor ); 
    293406            $title = qq{<a name="$anchor"></a><b>$title</b>}; 
    294407        } 
     
    302415 
    303416 
     417package My::pod; 
     418use strict; 
     419use warnings; 
     420 
     421sub test { 
     422 
     423    # From perldoc perlpod: 
     424    # 
     425    my @l_tests = ( 
     426        {  
     427            in  => ['L<name>'], 
     428            out => '<a href="name.html">name</a>', 
     429        }, 
     430 
     431        { 
     432            in  => [ 'L<name/"section here">', 'L<name/section here>', ], 
     433            out => '<a href="name.html#section_here">section here</a>', 
     434        }, 
     435 
     436        { 
     437            in  => ['L</"section here">', 'L</section here>', 'L<"section here">', ], 
     438            out => '<a href="#section_here">section here</a>', 
     439        }, 
     440 
     441 
     442 
     443        {   in  => ['L<text here|name>'], 
     444            out => '<a href="name.html">text here</a>', 
     445        }, 
     446 
     447        { 
     448            in  => ['L<text|name/"sec here">', 'L<text|name/sec here>'], 
     449            out => '<a href="name.html#sec_here">text</a>', 
     450        }, 
     451 
     452        { 
     453            in  => ['L<text|/"sec">',  'L<text|/sec>', 'L<text|"sec">'], 
     454            out => '<a href="#sec">text</a>', 
     455        }, 
     456 
     457        { 
     458            in  => ['L<http://host.name/some/path.html#fragment>'], 
     459            out => '<a href="http://host.name/some/path.html#fragment">http://host.name/some/path.html#fragment</a>', 
     460        }, 
     461 
     462        { 
     463            in => ['L<Link to $foo-E<gt> with & "quotes" and %funny !!? chars>'], 
     464            out => '<a href="#link_to_foo_with_quotes_and_funny_chars">Link to $foo-&gt; with &amp; "quotes" and %funny !!? chars</a>', 
     465        }, 
     466        { 
     467            in => ['L<Some & text|/Link to $foo-E<gt> with & "quotes" and %funny !!? chars>'], 
     468            out => '<a href="#link_to_foo_with_quotes_and_funny_chars">Some &amp; text</a>', 
     469        }, 
     470        { 
     471            in => ['L<SWISH-RUN|SWISH-RUN>'], 
     472            out => '<a href="swish-run.html">SWISH-RUN</a>', 
     473        } 
     474    ); 
     475 
     476 
     477    for my $test ( @l_tests ) { 
     478        for my $input ( @{$test->{in}} ) { 
     479 
     480            my $pod = Pod::POM->new( warn => 1 )->parse_text( "=head1 Title\n\n$input" ); 
     481            my $out = My::Pod::Test::HTML->print( $pod ); 
     482 
     483            warn "input  [$input]\n", 
     484                 "output [$out]\n", 
     485                 "test   [$test->{out}]\n\n" 
     486                        if $out ne $test->{out} || $ENV{VERBOSE}; 
     487        } 
     488    } 
     489} 
     490 
     491package My::Pod::Test::HTML; 
     492use strict; 
     493use warnings; 
     494use base 'My::Pod::View::HTML'; 
     495 
     496sub view_textblock { 
     497    my ($self, $text) = @_; 
     498    return $text; 
     499} 
     500 
     501sub view_pod { 
     502    my ($self, $pod) = @_; 
     503    return $pod->content->present($self); 
     504} 
     505 
     506sub view_head1 { 
     507    my ($self, $pod) = @_; 
     508    return $pod->content->present($self); 
     509} 
     510 
     511 
    3045121; 
    305513 
  • trunk/swish_website/bin/build

    r1624 r1638  
    407407    # Create sub-cache for this source, if doesn't exist 
    408408    # When processing a pod file the POD plugin will update its own cache entry 
    409     # in the hash. 
    410  
    411     my $this_toc_cache = $toc_cache->{$key} ||= {}; 
     409    # in the hash.  Cache by source directory 
     410 
     411    my $this_toc_cache = $toc_cache->{$src_dir} ||= {}; 
    412412 
    413413 
     
    458458                abslinks    => $abslinks, 
    459459                swish_version => $version, 
     460                out_file    => $out_file, 
    460461            }, 
    461462            mode        => 0644, 
     
    478479 
    479480    shift @$include_path;  # remove source dir. 
     481 
     482    # Check all the L<> links 
     483    My::Pod::View::HTML::validate_links() 
     484        if $update_index and $config->verbose; 
    480485} 
    481486