diff options
Diffstat (limited to 'src/man2hlp/man2hlp.in')
-rw-r--r-- | src/man2hlp/man2hlp.in | 987 |
1 files changed, 987 insertions, 0 deletions
diff --git a/src/man2hlp/man2hlp.in b/src/man2hlp/man2hlp.in new file mode 100644 index 0000000..08765d8 --- /dev/null +++ b/src/man2hlp/man2hlp.in @@ -0,0 +1,987 @@ +#! @PERL_FOR_BUILD@ -w +# +# Man page to help file converter +# Copyright (C) 1994, 1995, 1998, 2000, 2001, 2002, 2003, 2004, 2005, +# 2007, 2010, 2011 +# The Free Software Foundation, Inc. +# +# Originally written by: +# Andrew V. Samoilov, 2002 +# Pavel Roskin, 2002 +# Andrew Borodin <aborodin@vmail.ru>, 2010 +# +# Completely rewritten in Perl by: +# Alexandr Prenko, 2010 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# \file man2hlp +# \brief Source: man page to help file converter + +use strict; +use warnings; + +# Perl have no static variables, so this hash emulates them +my %static = ( + "string_len anchor_flag" => 0, + "string_len lc_link_flag" => 0, + "handle_link old" => undef +); + +# Imported constants +my $CHAR_LINK_START = chr(01); # Ctrl-A +my $CHAR_LINK_POINTER = chr(02); # Ctrl-B +my $CHAR_LINK_END = chr(03); # Ctrl-C +my $CHAR_NODE_END = chr(04); # Ctrl-D +my $CHAR_ALTERNATE = chr(05); # Ctrl-E +my $CHAR_NORMAL = chr(06); # Ctrl-F +my $CHAR_VERSION = chr(07); # Ctrl-G +my $CHAR_FONT_BOLD = chr(010); # Ctrl-H +my $CHAR_FONT_NORMAL = chr(013); # Ctrl-K +my $CHAR_FONT_ITALIC = chr(024); # Ctrl-T +# end of import + +my $col = 0; # Current output column +my $out_row = 1; # Current output row +my $in_row = 0; # Current input row +my $no_split_flag = 0; # Flag: Don't split section on next ".SH" +my $skip_flag = 0; # Flag: Skip this section. + # 0 = don't skip, + # 1 = skipping title, + # 2 = title skipped, skipping text +my $link_flag = 0; # Flag: Next line is a link +my $verbatim_flag = 0; # Flag: Copy input to output verbatim +my $node = 0; # Flag: This line is an original ".SH" + +my $c_out; # Output filename +my $f_out; # Output file + +my $c_in; # Current input filename + +my $indentation; # Indentation level, n spaces +my $tp_flag; # Flag: .TP paragraph + # 1 = this line is .TP label, + # 2 = first line of label description. +my $topics = undef; + +# Emulate C strtok() +my $strtok; + +sub strtok($$) { + my ($str, $chars) = @_; + + if (! defined $chars || $chars eq "") + { + my $result = $strtok; + $strtok = undef; + return $result; + } + + $str = $strtok unless defined $str; + return undef unless defined $str; + + my $result; + $str =~ s/^[$chars]+//; + ($result, $strtok) = split /[$chars]+/, $str, 2; + ($result, $strtok) = split /[$chars]+/, $strtok, 2 if defined $result && $result eq ""; + $strtok = undef if ! defined $strtok || $strtok eq ""; + return $result; +} + +sub struct_node() { + return { + "node" => undef, # Section name + "lname" => undef, # Translated .SH, undef if not translated + "next" => undef, + "heading_level" => undef + } +} + +my $nodes = struct_node(); +my $cnode; # Current node + +# Report error in input +sub print_error($) +{ + my ($message) = @_; + warn sprintf "man2hlp: %s in file \"%s\" on line %d\n", $message, $c_in, $in_row; +} + +# Do open, exit if it fails +sub fopen_check ($$) +{ + my ($mode, $filename) = @_; + my $f; + + unless (open $f, $mode, $filename) + { + warn sprintf("man2hlp: Cannot open file \"%s\" ($!)\n", $filename); + exit 3; + } + return $f; +} + +# Do close, exit if it fails +sub fclose_check($) +{ + my ($f) = @_; + unless (close $f) + { + warn "man2hlp: Cannot close file ($!)\n"; + exit 3; + } +} + +# Change output line +sub newline() +{ + $out_row++; + $col = 0; + print $f_out "\n"; +} + +# Calculate the length of string +sub string_len +{ + my ($buffer) = @_; + my $anchor_flag = \$static{"string_len anchor_flag"}; # Flag: Inside hypertext anchor name ho4u_v_Ariom + my $lc_link_flag = \$static{"string_len lc_link_flag"}; # Flag: Inside hypertext link target name + my $backslash_flag = 0; # Flag: Backslash quoting + my $len = 0; # Result: the length of the string + + + foreach my $c (split //, $buffer) + { + if ($c eq $CHAR_LINK_POINTER) + { + $$lc_link_flag = 1; # Link target name starts + } + elsif ($c eq $CHAR_LINK_END) + { + $$lc_link_flag = 0; # Link target name ends + } + elsif ($c eq $CHAR_NODE_END) + { + # Node anchor name starts + $$anchor_flag = 1; + # Ugly hack to prevent loss of one space + $len++; + } + # Don't add control characters to the length + next if ord($c) >= 0 && ord($c) < 32; + # Attempt to handle backslash quoting + if ($c eq '\\' && !$backslash_flag) + { + $backslash_flag = 1; + next; + } + $backslash_flag = 0; + # Increase length if not inside anchor name or link target name + $len++ if !$$anchor_flag && !$$lc_link_flag; + if ($$anchor_flag && $c eq ']') + { + # Node anchor name ends + $$anchor_flag = 0; + } + } + return $len; +} + +# Output the string +sub print_string($) +{ + my ($buffer) = @_; + my $len; # The length of current word + my $backslash_flag = 0; + my $font_change_flag = 0; + my $quotes_flag = 0; + + # Skipping lines? + return if $skip_flag; + # Copying verbatim? + if ($verbatim_flag) + { + # Attempt to handle backslash quoting + foreach (split //, $buffer) + { + if ($_ eq '\\' && !$backslash_flag) + { + $backslash_flag = 1; + next; + } + $backslash_flag = 0; + print $f_out $_; + } + } + else + { + # Split into words + $buffer = strtok($buffer, " \t\n"); + # Repeat for each word + while (defined $buffer) + { + # Skip empty strings + if ($buffer ne '') + { + $len = string_len($buffer); + # Words are separated by spaces + if ($col > 0) + { + print $f_out ' '; + $col++; + } + elsif ($indentation) + { + print $f_out ' ' while $col++ < $indentation; + } + # Attempt to handle backslash quoting + foreach (split //, $buffer) + { + # handle quotes: \(lq, \(rq, \(dq + if ($quotes_flag != 0) + { + if (($_ eq 'l' || $_ eq 'r' || $_ eq 'd') && $quotes_flag == 1) + { + # continue quotes handling + $quotes_flag = 2; + next; + } + elsif ($_ eq 'q' && $quotes_flag == 2) + { + # finish quotes handling + $quotes_flag = 0; + print $f_out '"'; + next; + } + else + { + print $f_out '(' . $_; + print_error "Syntax error: unsupported \\(" . $_ . " command"; + } + } + # handle \fR, \fB, \fI and \fP commands + if ($font_change_flag) + { + if ($_ eq 'B') + { + print $f_out $CHAR_FONT_BOLD; + } + elsif ($_ eq 'I') + { + print $f_out $CHAR_FONT_ITALIC; + } + elsif ($_ eq 'R' || $_ eq 'P') + { + print $f_out $CHAR_FONT_NORMAL; + } + else + { + print $f_out 'f' . $_; + print_error "Syntax error: unsupported \\f" . $_ . " command"; + } + + $font_change_flag = 0; + next; + } + if ($_ eq '(' && $backslash_flag) + { + $quotes_flag = 1; + $backslash_flag = 0; + next; + } + if ($_ eq 'f' && $backslash_flag) + { + $font_change_flag = 1; + $backslash_flag = 0; + next; + } + if ($_ eq '\\' && !$backslash_flag) + { + $backslash_flag = 1; + next; + } + $backslash_flag = 0; + $font_change_flag = 0; + $quotes_flag = 0; + print $f_out $_; + } + # Increase column + $col += $len; + } + # Get the next word + $buffer = strtok(undef, " \t\n"); + } # while + } +} + +# Like print_string but with printf-like syntax +sub printf_string +{ + print_string sprintf shift, @_; +} + +# Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE +# FIXME: Consider to remove first parameter +sub handle_node($$) +{ + my ($buffer, $is_sh) = @_; + my ($len, $heading_level); + + # If we already skipped a section, don't skip another + $skip_flag = 0 if $skip_flag == 2; + + # Get the command parameters + $buffer = strtok(undef, ""); + if (! defined $buffer) + { + print_error "Syntax error: .SH: no title"; + return; + } + else + { + # Remove quotes + $buffer =~ s/^"// and $buffer =~ s/"$//; + # Calculate heading level + $heading_level = 0; + $heading_level++ while substr($buffer, $heading_level, 1) eq ' '; + # Heading level must be even + if ($heading_level % 2) + { + print_error "Syntax error: .SH: odd heading level"; + } + if ($no_split_flag) + { + # Don't start a new section + newline; + print_string $buffer; + newline; + newline; + $no_split_flag = 0; + } + elsif ($skip_flag) + { + # Skipping title and marking text for skipping + $skip_flag = 2; + } + else + { + $buffer = substr($buffer, $heading_level); + if (! $is_sh || ! $node) + { + # Start a new section, but omit empty section names + if ($buffer ne '') + { + printf $f_out "%s[%s]", $CHAR_NODE_END, $buffer; + newline; + } + + # Add section to the linked list + if (! defined $cnode) + { + $cnode = $nodes; + } + else + { + $cnode->{'next'} = struct_node(); + $cnode = $cnode->{'next'}; + } + $cnode->{'node'} = $buffer; + $cnode->{'lname'} = undef; + $cnode->{'next'} = undef; + $cnode->{'heading_level'} = $heading_level; + } + if ($is_sh) + { + $cnode->{'lname'} = $buffer; + print_string $buffer; + newline; + newline; + } + } # Start new section + } # Has parameters + $node = ! $is_sh; +} + +# Convert character from the macro name to the font marker +sub char_to_font($) +{ + my ($c) = @_; + my %font = ( + 'R' => $CHAR_FONT_NORMAL, + 'B' => $CHAR_FONT_BOLD, + 'I' => $CHAR_FONT_ITALIC + ); + return exists $font{$c} ? $font{$c} : chr(0); +} + +# +# Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB) +# Return 0 if the command wasn't recognized, 1 otherwise +# +sub handle_alt_font($) +{ + my ($buffer) = @_; + my $in_quotes = 0; + my $alt_state = 0; + + return 0 if length($buffer) != 3; + return 0 if substr($buffer, 0, 1) ne '.'; + + my @font = ( + char_to_font substr($buffer, 1, 1), + char_to_font substr($buffer, 2, 1) + ); + + # Exclude names with unknown characters, .BB, .II and .RR + if ($font[0] eq chr(0) || $font[1] eq chr(0) || $font[0] eq $font[1]) + { + return 0; + } + + my $p = strtok(undef, ""); + return 1 unless defined $p; + + $buffer = $font[0]; + + my @p = split //, $p; + while (@p) + { + + if ($p[0] eq '"') + { + $in_quotes = !$in_quotes; + shift @p; + next; + } + + if ($p[0] eq ' ' && !$in_quotes) + { + shift @p; + # Don't change font if we are at the end + if (@p) + { + $alt_state = $alt_state ? 0 : 1; + $buffer .= $font[$alt_state]; + } + + # Skip more spaces + shift @p while @p && $p[0] eq ' '; + + next; + } + + $buffer .= shift @p; + } + + # Turn off attributes if necessary + if ($font[$alt_state] ne $CHAR_FONT_NORMAL) + { + $buffer .= $CHAR_FONT_NORMAL; + } + + print_string $buffer; + + return 1; +} + +# Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP +sub handle_tp_ip($) +{ + my ($is_tp) = @_; + newline if $col > 0; + newline; + if ($is_tp) + { + $tp_flag = 1; + $indentation = 0; + } + else + { + $indentation = 8; + } +} + +# Handle all the roff dot commands. See man groff_man for details +sub handle_command($) +{ + my ($buffer) = @_; + my $len; + + # Get the command name + $buffer = strtok($buffer, " \t"); + + if ($buffer eq ".SH") + { + $indentation = 0; + handle_node $buffer, 1; + } + elsif ($buffer eq ".\\\"NODE") + { + handle_node $buffer, 0; + } + elsif ($buffer eq ".\\\"DONT_SPLIT\"") + { + $no_split_flag = 1; + } + elsif ($buffer eq ".\\\"SKIP_SECTION\"") + { + $skip_flag = 1; + } + elsif ($buffer eq ".\\\"LINK2\"") + { + # Next two input lines form a link + $link_flag = 2; + } + elsif ($buffer eq ".PP" || $buffer eq ".P" || $buffer eq ".LP") + { + $indentation = 0; + # End of paragraph + newline if $col > 0; + newline; + } + elsif ($buffer eq ".nf") + { + # Following input lines are to be handled verbatim + $verbatim_flag = 1; + newline if $col > 0; + } + elsif ($buffer eq ".I" || $buffer eq ".B" || $buffer eq ".SB") + { + # Bold text or italics text + my $backslash_flag = 0; + + # .SB [text] + # Causes the text on the same line or the text on the + # next line to appear in boldface font, one point + # size smaller than the default font. + # + + # FIXME: text is optional, so there is no error + + my $p = strtok(undef, ""); + if (! defined $p) + { + print_error "Syntax error: .I | .B | .SB : no text"; + return; + } + + $buffer = substr($buffer, 1, 1) eq 'I' ? $CHAR_FONT_ITALIC : $CHAR_FONT_BOLD; + + # Attempt to handle backslash quoting + foreach (split //, $p) + { + if ($_ eq '\\' && !$backslash_flag) + { + $backslash_flag = 1; + next; + } + $backslash_flag = 0; + $buffer .= $_; + } + print_string $buffer . $CHAR_FONT_NORMAL; + } + elsif ($buffer eq ".TP") + { + handle_tp_ip 1; + } + elsif ($buffer eq ".IP") + { + handle_tp_ip 0; + } + elsif ($buffer eq ".\\\"TOPICS") + { + if ($out_row > 1) + { + print_error "Syntax error: .\\\"TOPICS must be first command"; + return; + } + $buffer = strtok(undef, ""); + if (! defined $buffer) + { + print_error "Syntax error: .\\\"TOPICS: no text"; + return; + } + # Remove quotes + $buffer =~ s/^"// and $buffer =~ s/"$//; + $topics = $buffer; + } + elsif ($buffer eq ".br") + { + newline if $col; + } + elsif ($buffer =~ /^\.\\"/) + { + # Comment { Hello from K.O. ;-) } + } + elsif ($buffer eq ".TH") + { + # Title header + } + elsif ($buffer eq ".SM") + { + # Causes the text on the same line or the text on the + # next line to appear in a font that is one point + # size smaller than the default font. + $buffer = strtok(undef, ""); + print_string $buffer if defined $buffer; + } + elsif (handle_alt_font($buffer) == 1) + { + return; + } + elsif ($buffer eq ".RE") + { + newline; + } + else + { + # Other commands are ignored + print_error sprintf "Warning: unsupported command %s", $buffer; + return; + } +} + +sub struct_links() +{ + return { + 'linkname' => undef, # Section name + 'line' => undef, # Input line in ... + 'filename' => undef, + 'next' => undef + } +} + +my $links = struct_links(); +my $current_link; + + +sub handle_link($) +{ + my ($buffer) = @_; + my $old = \$static{"handle_link old"}; + my $len; + my $amp; + my $amp_arg; + + if ($link_flag == 1) + { + # Old format link, not supported + } + elsif ($link_flag == 2) + { + # First part of new format link + # Bold text or italics text + if (substr($buffer, 0, 2) eq '.I' || substr($buffer, 0, 2) eq '.B') + { + $buffer =~ s/^..[\s\t]*//; + } + $$old = $buffer; + $link_flag = 3; + + } + elsif ($link_flag == 3) + { + # Second part of new format link + $buffer =~ s/^\.//; + $buffer =~ s/^\\//; + $buffer =~ s/^"//; + $buffer =~ s/"$//; + + # "Layout\&)," -- "Layout" should be highlighted, but not ")," + ($$old, $amp_arg) = split /\\&/, $$old, 2; + $amp_arg = "" unless defined $amp_arg; + printf_string "%s%s%s%s%s%s\n", $CHAR_LINK_START, $$old, + $CHAR_LINK_POINTER, $buffer, $CHAR_LINK_END, $amp_arg; + $link_flag = 0; + # Add to the linked list + if (defined $current_link) + { + $current_link->{'next'} = struct_links(); + $current_link = $current_link->{'next'}; + $current_link->{'next'} = undef; + } + else + { + $current_link = $links; + } + $current_link->{'linkname'} = $buffer; + $current_link->{'filename'} = $c_in; + $current_link->{'line'} = $in_row; + } +} + +sub main +{ + my $len; # Length of input line + my $c_man; # Manual filename + my $c_tmpl; # Template filename + my $f_man; # Manual file + my $f_tmpl; # Template file + my $buffer; # Full input line + my $lc_node = undef; + my $outfile_buffer; # Large buffer to keep the output file + my $cont_start; # Start of [Contents] + my $file_end; # Length of the output file + + # Validity check for arguments + if (@ARGV != 3) + { + warn "Usage: man2hlp file.man template_file helpfile\n"; + return 3; + } + + $c_man = $ARGV[0]; + $c_tmpl = $ARGV[1]; + $c_out = $ARGV[2]; + + # First stage - process the manual, write to the output file + + $f_man = fopen_check "<", $c_man; + $f_out = fopen_check ">", $c_out; + $c_in = $c_man; + + # Repeat for each input line + while (<$f_man>) + { + # Remove terminating newline + chomp; + $buffer = $_; + my $input_line; # Input line without initial "\&" + + if (substr($buffer, 0, 2) eq '\\&') + { + $input_line = substr($buffer, 2); + } + else + { + $input_line = $buffer; + } + + $in_row++; + $len = length($input_line); + + if ($verbatim_flag) + { + # Copy the line verbatim + if ($input_line eq ".fi") + { + $verbatim_flag = 0; + } + else + { + print_string $input_line; + newline; + } + } + elsif ($link_flag) + { + # The line is a link + handle_link $input_line; + } + elsif (substr($buffer, 0, 1) eq '.') + { + # The line is a roff command + handle_command $input_line; + } + else + { + #A normal line, just output it + print_string $input_line; + } + # .TP label processed as usual line + if ($tp_flag) + { + if ($tp_flag == 1) + { + $tp_flag = 2; + } + else + { + $tp_flag = 0; + $indentation = 8; + if ($col >= $indentation) + { + newline; + } + else + { + print $f_out " " while ++$col < $indentation; + } + } + } + } + + newline; + fclose_check $f_man; + # First stage ends here, closing the manual + + # Second stage - process the template file + $f_tmpl = fopen_check "<", $c_tmpl; + $c_in = $c_tmpl; + + # Repeat for each input line + # Read a line + while (<$f_tmpl>) + { + $buffer = $_; + if (defined $lc_node) + { + if ($buffer ne "\n") + { + $cnode->{'lname'} = $buffer; + chomp $cnode->{'lname'}; + } + $lc_node = undef; + } + else + { + my $char_node_end = index($buffer, $CHAR_NODE_END); + $lc_node = $char_node_end < 0 ? undef : substr($buffer, $char_node_end); + + if (defined $lc_node && substr($lc_node, 1, 1) eq '[') + { + my $p = index($lc_node, ']'); + if ($p >= 0) { + if (substr($lc_node, 1, 6) eq '[main]') + { + $lc_node = undef; + } + else + { + if (! defined $cnode) + { + $cnode = $nodes; + } + else + { + $cnode->{'next'} = struct_node(); + $cnode = $cnode->{'next'}; + } + $cnode->{'node'} = substr($lc_node, 2, $p-2); + $cnode->{'lname'} = undef; + $cnode->{'next'} = undef; + $cnode->{'heading_level'} = 0; + } + } + else + { + $lc_node = undef; + } + } + else + { + $lc_node = undef; + } + } + print $f_out $buffer; + } + + $cont_start = tell $f_out; + if ($cont_start <= 0) + { + perror $c_out; + return 1; + } + + if ($topics) + { + printf $f_out "\004[Contents]\n%s\n\n", $topics; + } + else + { + print $f_out "\004[Contents]\n"; + } + + for ($current_link = $links; defined $current_link && defined $current_link->{'linkname'};) + { + my $found = 0; + my $next = $current_link->{'next'}; + + if ($current_link->{'linkname'} eq "Contents") + { + $found = 1; + } + else + { + for ($cnode = $nodes; defined $cnode && defined $cnode->{'node'}; $cnode = $cnode->{'next'}) + { + if ($cnode->{'node'} eq $current_link->{'linkname'}) + { + $found = 1; + last; + } + } + } + if (! $found) + { + $buffer = sprintf "Stale link \"%s\"", $current_link->{'linkname'}; + $c_in = $current_link->{'filename'}; + $in_row = $current_link->{'line'}; + print_error $buffer; + } + + $current_link = $next; + } + + for ($cnode = $nodes; defined $cnode && defined $cnode->{'node'};) + { + my $next = $cnode->{'next'}; + $lc_node = $cnode->{'node'}; + + if (defined $lc_node && $lc_node ne '') { + printf $f_out " %*s\001%s\002%s\003", $cnode->{'heading_level'}, + "", $cnode->{'lname'} ? $cnode->{'lname'} : $lc_node, $lc_node; + } + print $f_out "\n"; + $cnode = $next; + } + + $file_end = tell $f_out; + + # Sanity check + if (($file_end <= 0) || ($file_end - $cont_start <= 0)) + { + warn $c_out ."\n"; + return 1; + } + + fclose_check $f_out; + fclose_check $f_tmpl; + # Second stage ends here, closing all files, note the end of output + + # + # Third stage - swap two parts of the output file. + # First, open the output file for reading and load it into the memory. + # + $outfile_buffer = ''; + $f_out = fopen_check '<', $c_out; + $outfile_buffer .= $_ while <$f_out>; + fclose_check $f_out; + # Now the output file is in the memory + + # Again open output file for writing + $f_out = fopen_check '>', $c_out; + + # Write part after the "Contents" node + print $f_out substr($outfile_buffer, $cont_start, $file_end - $cont_start); + + # Write part before the "Contents" node + print $f_out substr($outfile_buffer, 0, $cont_start-1); + print $f_out "\n"; + fclose_check $f_out; + + return 0; +} + +exit main(); |