File Coverage

blib/lib/Texinfo/Menus.pm
Criterion Covered Total %
statement 10 159 6.2
branch 0 102 0.0
condition 0 26 0.0
subroutine 4 11 36.3
pod 0 7 0.0
total 14 305 4.5


line stmt bran cond sub pod time code
1             #---------------------------------------------------------------------
2             package Texinfo::Menus;
3             #
4             # Copyright 1994-2007 Christopher J. Madsen
5             #
6             # Author: Christopher J. Madsen
7             #
8             # This program is free software; you can redistribute it and/or modify
9             # it under the same terms as Perl itself.
10             #
11             # This program is distributed in the hope that it will be useful,
12             # but WITHOUT ANY WARRANTY; without even the implied warranty of
13             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the
14             # GNU General Public License or the Artistic License for more details.
15             #
16             # ABSTRACT: Update node links and menus in Texinfo documents
17             #---------------------------------------------------------------------
18              
19 1     1   64194 use 5.008;
  1         4  
  1         47  
20              
21 1     1   1048 use IO::File;
  1         14591  
  1         146  
22 1     1   10 use strict;
  1         3  
  1         4729  
23             our (
24             $descColumn,$layers,$level,$masterMenu,$menuMark,$node,$printKids,$section,
25             $No_Comments,$No_Detail,$Verbose,
26             @parents,
27             %children,%desc,%level,%next,%prev,%section,%title,%up,
28             );
29              
30             require Exporter;
31              
32             our @ISA = qw(Exporter);
33             our @EXPORT = qw(update_menus);
34              
35             our $VERSION = '1.03';
36              
37             our %layersForEncoding = (qw(
38             UTF-8 :utf8
39             US-ASCII) => ''
40             );
41              
42             #=====================================================================
43             # Subroutines:
44             #---------------------------------------------------------------------
45             # Print an error message on STDERR and exit:
46             #
47             # Input:
48             # filename: The file containing the error
49             # line: The line number of the error (-1 means use $INPUT_LINE_NUMBER)
50             # message: The error message to display
51              
52             sub abort
53             {
54 0     0 0   my ($filename,$line,$message) = @_;
55              
56 0 0         $line = $. if $line eq '-1'; # $INPUT_LINE_NUMBER
57              
58 0           die "$filename:$line: $message\n";
59             } # end abort
60              
61             #---------------------------------------------------------------------
62             sub update_menus
63             {
64 0     0 0   my $master = shift @_;
65 0           my %parms = @_;
66              
67 0   0       $descColumn = $parms{description_column} || 32; # The column for menu descriptions
68 0 0         $No_Comments = (exists $parms{comments} ? !$parms{comments} : 0);
69 0 0         $No_Detail = (exists $parms{detailed} ? !$parms{detailed} : 0);
70 0           $Verbose = $parms{verbose}; # Defaults to off
71              
72 0           $masterMenu = 0;
73 0           $menuMark = '*';
74 0           $layers = '';
75              
76 0           undef $node; # We are not in any node yet
77 0           undef $level; undef %next;
  0            
78 0           undef $section; undef %prev;
  0            
79 0           undef @parents; undef %section;
  0            
80 0           undef %children; undef %title;
  0            
81 0           undef %desc; undef %up;
  0            
82              
83 0           readStructure($master);
84              
85 0           $next{"Top"} = $children{"Top"}->[0];
86              
87 0           writeMenus($master);
88             } # end file
89              
90             #---------------------------------------------------------------------
91             # Generate the master menu:
92             #
93             # Input:
94             # node: The node we are in (usually "Top")
95             #
96             # Input Variables:
97             # %children
98             # %section
99             # $No_Detail
100              
101             sub printMasterMenu
102             {
103 0     0 0   my $node = shift;
104              
105 0           local $masterMenu = 1;
106 0           print "\@menu\n";
107 0           printMenu(@{$children{$node}});
  0            
108 0 0         unless ($No_Detail) {
109 0           print "\n --- The Detailed Node Listing ---\n";
110 0 0         local $printKids = ($No_Comments ? 0 : 1);
111 0           foreach my $child (@{$children{$node}}) {
  0            
112 0 0         if (exists $children{$child}) {
113 0   0       print "\n", ($section{$child} || $child), "\n\n";
114 0           printMenu(@{$children{$child}});
  0            
115             }
116             } # end foreach
117             } # end unless $No_Detail
118 0           print "\@end menu\n";
119             } # end printMasterMenu
120              
121             #---------------------------------------------------------------------
122             # Generate a menu:
123             #
124             # Input Variables:
125             # $descColumn: The column number for descriptions (0 is first column)
126             # $masterMenu: True prevents insertion of "@menu" and "@end menu".
127             # $menuMark: The mark that indicates a menu item (usually "*")
128             # $printKids: True inserts comments for child nodes
129              
130             sub printMenu
131             {
132 0 0   0 0   print "\@menu\n" unless $masterMenu;
133 0           foreach $node (@_) { ## no critic (RequireLexicalLoopIterators)
134 0 0         printf("%-${descColumn}s%s\n",
135             ($title{$node}
136             ? "$menuMark ${title{$node}}: ${node}." # Node with title
137             : "$menuMark ${node}::"), # Node with no title
138             $desc{$node});
139 0 0 0       printMenuComment(@{$children{$node}})
  0            
140             if $printKids and exists $children{$node};
141             } # end foreach $node
142 0 0         print "\@end menu\n\n" unless $masterMenu;
143             } # end printMenu
144              
145             #---------------------------------------------------------------------
146             # Generate comments for a submenu:
147             # Input Variables:
148             # $masterMenu: Must be True
149             # $menuMark: The mark that indicates a menu item (usually "*")
150             # $printKids: True inserts comments for child nodes
151              
152             sub printMenuComment
153             {
154 0     0 0   local $menuMark = $menuMark;
155 0 0         if ($menuMark =~ /^\@c/) { $menuMark .= ' ' }
  0            
156 0           else { $menuMark = '@c *' };
157 0           &printMenu;
158             } # end printMenuComment
159              
160             #---------------------------------------------------------------------
161             # Scan file for node structure and descriptions:
162             #
163             # Input:
164             # $filename: The file to scan
165             #
166             # Variables Created:
167             # %children:
168             # The children of a node, indexed by node name
169             # Each entry is an array of node names (eg, @{$children{"Top"}}
170             # is an array of all the children of the Top node, in the order
171             # they occurred).
172             # %desc: Node descriptions, indexed by node name
173             # %next: The name of the next node, indexed by node name
174             # %prev: The name of the previous node, indexed by node name
175             # %section: Section and subsection titles, indexed by node name
176             # %title: Node titles (menu-entry names), indexed by node name
177             # %up: The name of the "parent" node, indexed by node name
178             #
179             # Variables Used:
180             # $node: The node we are currently in
181             # $level: The level this node is at (0=Top, 1=Chapter, ...)
182             # @parents: A list of all the parent nodes of this node, including
183             # the node itself ("Top", "Chapter Node", ... "This Node")
184              
185             sub readStructure
186             {
187 0     0 0   my $filename = $_[0];
188              
189 0           my $handle = IO::File->new;
190              
191 0 0         openFile:
192             open($handle, "<$layers", $filename) or abort($filename,0,"Unable to open");
193              
194             line:
195 0           while (<$handle>) {
196 0 0         if (/^\@node +([^,\n]+)/) {
    0          
    0          
    0          
    0          
197 0           my $newNode = $1;
198 0 0         abort($filename, -1, "Duplicate node name `$newNode'")
199             if defined $prev{$newNode};
200 0 0         if ($newNode eq 'Top') {
201 0           $node = 'Top';
202 0           @parents = ($node); # The Top node has no parents
203 0           $prev{$node} = '(dir)';
204 0           $up{$node} = '(dir)';
205 0           $level = 0;
206 0           next line;
207             }
208 0           $section = <$handle>;
209 0           $section = <$handle> while $section =~ /^\@c(omment)? /;
210 0 0         abort($filename, -1,
211             'Chapter structuring command required after `@node\'')
212             unless ($section =~ /^\@([a-z]+) +(.+)$/);
213 0 0         abort($filename,-1,"\`\@$1' is not a chapter structuring command")
214             unless exists $level{$1};
215 0           my $newLevel = $level{$1};
216 0 0         abort($filename,-1,"Skipped level")
217             if ($newLevel - $level) > 1;
218 0           $section = $2;
219 0           $section{$newNode} = $section;
220 0 0         if (not $desc{$newNode}) {
221 0 0         $desc{$newNode} = ($newNode ne $section ? $section : "");
222             }
223 0           $next{$newNode} = "";
224 0 0         if ($newLevel < $level) {
225 0           $next{$node} = "";
226 0           my $prevNode = $parents[$newLevel];
227 0           $next{$prevNode} = $newNode;
228 0           $prev{$newNode} = $prevNode;
229             }
230             else {
231 0 0         $next{$node} = $newNode unless $newLevel > $level;
232 0           $prev{$newNode} = $node;
233             }
234 0           $parents[$newLevel] = $newNode;
235 0           $node = $newNode;
236 0           $level = $newLevel;
237 0           my $parent = $parents[$level-1];
238 0           $up{$node} = $parent;
239 0           push @{$children{$parent}}, $node;
  0            
240             } # end if @node
241             elsif (/^\@menu/ .. /^\@end menu/) {
242 0 0         next line unless /^(\@c )?\* /;
243              
244 0           my($node, $title, $desc);
245              
246 0 0         if (/\* +([^:]+):: *(.*)$/) {
    0          
247 0           ($node, $title, $desc) = ($1, "", $2);
248             }
249             elsif (/\* +([^:]+): *([^,.\t\n]+)[,.\t\n] *(.*)$/) {
250 0           ($node, $title, $desc) = ($2, $1, $3);
251             }
252             else {
253 0           abort($filename,-1,"Bad menu entry");
254             }
255 0           $title{$node} = $title;
256 0 0 0       if ($desc and $desc{$node}) {
257 0 0 0       print STDERR <
258             $filename:$.: Warning: Multiple descriptions for node \`$node'
259             \`$desc{$node}' overrides
260             \`$desc'
261             EOT
262 0           undef $desc; # Don't overwrite the first description
263             }
264 0 0         $desc{$node} = $desc if $desc;
265             } # end elsif in @menu
266             elsif (/^\@c(omment)? DESC: *(.*?) *$/) {
267             # A DESC comment in the node overrides any previous description:
268 0 0 0       if ($Verbose and $desc{$node} and $desc{$node} ne $2
      0        
      0        
269             and $desc{$node} ne $section) {
270 0           print STDERR <
271             $filename:$.: Warning: Multiple descriptions for node \`$node'
272             \`$2' overrides
273             \`$desc{$node}'
274             EOT
275             # '
276             } # end if node description is not section name or blank
277 0           $desc{$node} = $2;
278             } # end elsif DESC comment in node
279             elsif (/^ *\@include +(\S+)\s/) {
280 0           readStructure($1);
281             }
282             elsif (/^ *\@documentencoding +(\S+)\s/) {
283 0           my $wantLayers = $layersForEncoding{$1};
284 0 0         $wantLayers = ":encoding($1)" unless defined $wantLayers;
285              
286 0 0         if ($layers) {
    0          
287 0 0         abort($filename, -1, "Cannot switch from $layers to $wantLayers")
288             if $layers ne $wantLayers;
289             } elsif ($wantLayers) {
290 0 0         abort($filename, -1,
291             '@documentencoding must come before structuring commands')
292             if defined $node;
293 0           $layers = $wantLayers;
294 0           close $handle;
295 0           goto openFile;
296             }
297             }
298             } # end while
299              
300 0           close $handle;
301             } # end readStructure
302              
303             #---------------------------------------------------------------------
304             # Insert menus and node links:
305             #
306             # Input:
307             # $filename: The file to write
308             #
309             # Variables Used:
310             # %children:
311             # The children of a node, indexed by node name
312             # Each entry is an array of node names (eg, @{$children{"Top"}}
313             # is an array of all the children of the Top node, in the order
314             # they occurred).
315             # %desc: Node descriptions, indexed by node name
316             # %next: The name of the next node, indexed by node name
317             # %prev: The name of the previous node, indexed by node name
318             # %title: Node titles (menu-entry names), indexed by node name
319             # %up: The name of the "parent" node, indexed by node name
320              
321             sub writeMenus
322             {
323 0     0 0   my $filename = $_[0];
324              
325 0           my ($menu,$node);
326 0           my $deleteBlanks = 0;
327              
328 0 0         rename $filename,"$filename#~" or die "Unable to rename $filename";
329              
330 0           my $inHandle = IO::File->new;
331 0           my $outHandle = IO::File->new;
332              
333 0 0         open($inHandle,"<$layers","$filename#~") or die "Unable to open $filename#~";
334 0 0         open($outHandle,">$layers",$filename) or die "Unable to open $filename";
335              
336 0           my $oldHandle = select $outHandle;
337              
338 0           while (<$inHandle>) {
339 0 0         if (/^ *\@include +(\S+)\s/) {
    0          
    0          
340 0           local $_; # Preserve the current line
341 0           writeMenus($1);
342             } # end if @include
343             elsif (/^\@menu/) {
344 0 0         if (ref($menu)) {
345 0 0         if ($node eq 'Top') { printMasterMenu($node) }
  0            
346 0           else { printMenu(@$menu) }
347             }
348 0           undef $menu;
349             } # end elsif @menu
350             elsif (/^\@node +([^,\n]+)/) {
351 0           my $newNode = $1;
352 0 0         if (ref($menu)) {
353 0 0         if ($node eq 'Top') { printMasterMenu($node) }
  0            
354 0           else { printMenu(@$menu) }
355             }
356 0           undef $menu;
357 0           $node = $newNode;
358 0           $_ = "\@node $node, $next{$node}, $prev{$node}, $up{$node}\n";
359 0 0         $menu = $children{$node} if exists $children{$node};
360             } # end elsif @node
361             } # end while <$inHandle>
362             continue {
363 0 0         if (/^\@menu/ .. /^\@end menu/) {
364 0           $deleteBlanks = 1;
365             } else {
366 0 0 0       print($_), $deleteBlanks = 0 unless ($deleteBlanks and /^ *$/);
367             }
368             } # end while <$inHandle> (continue block)
369              
370 0           select $oldHandle;
371 0           close $inHandle;
372 0           close $outHandle;
373 0           unlink "$filename#~";
374             } # end writeMenus
375              
376             #=====================================================================
377             # Initialize variables:
378             #---------------------------------------------------------------------
379             BEGIN
380             {
381 1     1   316 %level = (
382             "chapter" => 1,
383             "section" => 2,
384             "subsection" => 3,
385             "subsubsection" => 4,
386              
387             "unnumbered" => 1,
388             "unnumberedsec" => 2,
389             "unnumberedsubsec" => 3,
390             "unnumberedsubsubsec" => 4,
391              
392             "appendix" => 1,
393             "appendixsec" => 2,
394             "appendixsubsec" => 3,
395             "appendixsubsubsec" => 4,
396              
397             "chapheading" => 1,
398             "heading" => 2,
399             "subheading" => 3,
400             "subsubheading" => 4,
401             );
402             } # end BEGIN
403              
404             #=====================================================================
405             # Package Return Value:
406             #=====================================================================
407             1;
408              
409             __END__