#!/opt/bin/perl use strict; #-------------------------------------------------------------- # perlman: man page viewer in Perl #-------------------------------------------------------------- use Tk; print STDERR "Scouting man directories\n"; scout_man_dirs(); print STDERR "Starting UI ..."; create_ui(); print STDERR "Done \n"; MainLoop(); exit(0); #------------------------------------------------------------------- my $menu_headings; # "Headings" MenuButton my $ignore_case; # 1 if check-button on in Search menu my $match_type; # '-regexp' or '-exact'. my $text; # Main text widget my $show; # "Show" entry widget my $search; # "Search" entry widget my %sections; # Maps section ('1', '3' ,'3n' etc.) # to list of topics in that section sub show_man { my $entry = $show->get(); # get entry from $show my ($man, $section) = ($entry =~ /^(\w+)(\(.*\))?/); if ($section && (!is_valid_section($section))) { undef $section ; } my $cmd_line = get_command_line($man, $section); # used by open # Erase everything to do with current page (contents, menus, marks) $text->delete('1.0', 'end'); # erase current page $text->insert('end', "Formatting \"$man\" .. please wait", 'section'); $text->update(); # Flush changes to text widget $menu_headings->menu()->delete(0,'end'); # Delete current headings my $mark; foreach $mark ($text->markNames) { # remove all marks $text->markUnset($mark); } # UI is clean now. Open the file if (!open (F, $cmd_line)) { # Use the text widget for error messages $text->insert('end', "\nError in running man or rman"); $text->update(); return; } # Erase the "Formatting $man ..." message $text->delete('1.0', 'end'); my $lines_added = 0; my $line; while ($line = ) { $lines_added = 1; # If first character is a capital letter, it's likely a section if ($line =~ /^[A-Z]/) { # Likely a section heading ($mark = $line) =~ s/\s.*$//g; # $mark has section title my $index = $text->index('end');# note current end location # Give 'section' tag to the section title $text->insert('end', "$mark\n\n", 'section'); # Create a menu entry. Have callback invoke text widget's # 'see' method to go to the index noted above $menu_headings->command( '-label' => $mark, '-command' => [sub {$text->see($_[0])},$index]) } else { $text->insert('end', $line); # Ordinary text. Just insert. } } if ( ! $lines_added ) { $text->insert('end', "Sorry. No information found on $man"); } close(F); } sub get_command_line { my ($man, $section) = @_; # Given topic and section, construct # Unix command-line if ($section) { $section =~ s/[()]//g; # remove parens return "man -s $section $man 2> /dev/null | rman |"; } else { return "man $man 2> /dev/null | rman |"; } } sub create_ui { my $top = MainWindow->new(); # MENU STUFF # Menu bar my $menu_bar = $top->Frame()->pack('-side' => 'top', '-fill' => 'x'); # File menu my $menu_file = $menu_bar->Menubutton('-text' => 'File', '-relief' => 'raised', '-borderwidth' => 2, )->pack('-side' => 'left', '-padx' => 2, ); $menu_file->separator(); $menu_file->command('-label' => 'Quit', '-command' => sub {exit(0)}); #Sections Menu $menu_headings = $menu_bar->Menubutton('-text' => 'Headings', '-relief' => 'raised', '-borderwidth' => 2, )->pack('-side' => 'left', '-padx' => 2, ); $menu_headings->separator(); #Search menu my $search_mb = $menu_bar->Menubutton('-text' => 'Search', '-relief' => 'raised', '-borderwidth' => 2, )->pack('-side' => 'left', '-padx' => 2 ); $match_type = "-regexp"; $ignore_case = 1; $search_mb->separator(); # Regexp match $search_mb->radiobutton('-label' => 'Regexp match', '-value' => '-regexp', '-variable' => \$match_type); # Exact match $search_mb->radiobutton('-label' => 'Exact match', '-value' => '-exact', '-variable' => \$match_type); $search_mb->separator(); # Ignore case $search_mb->checkbutton('-label' => 'Ignore case?', '-variable' => \$ignore_case); #Sections Menu my $menu_sections = $menu_bar->Menubutton('-text' => 'Sections', '-relief' => 'raised', '-borderwidth' => 2, )->pack('-side' => 'left', '-padx' => 2, ); # Populate sections menu with keys of % sections my $section_name; foreach $section_name (sort keys %sections) { $menu_sections->command ( '-label' => "($section_name)", '-command' => [\&show_section_contents, $section_name]); } # TEXT STUFF $text = $top->Text ('-width' => 80, '-height' => 40)->pack(); $text->tagConfigure('section', '-font' => '-adobe-helvetica-bold-r-normal--14-140-75-75-p-82-iso8859-1'); $text->bind('', \&pick_word); $top->Label('-text' => 'Show:')->pack('-side' => 'left'); $show = $top->Entry ('-width' => 20, )->pack('-side' => 'left'); $show->bind('', \&show_man); $top->Label('-text' => 'Search:' )->pack('-side' => 'left', '-padx' => 10); $search = $top->Entry ('-width' => 20, )->pack('-side' => 'left'); $search->bind('', \&search); } sub is_valid_section { my $section= $_[0]; return 0 unless $section =~ /\((.*?)\)/; my $section = $1; my $s; foreach $s (keys %sections) { if (lc($s) eq lc($section)) { return 1; } } 0; } sub pick_word { my $start_index = $text->index('insert wordstart'); my $end_index = $text->index('insert lineend'); my $line = $text->get($start_index, $end_index); my ($page, $section) = ($line =~ /^(\w+)(\(.*?\))?/); return unless $page; $show->delete('0', 'end'); if ($section && is_valid_section($section)) { $show->insert('end', "$page${section}"); } else { $show->insert('end', $page); } show_man(); } sub show_section_contents { my $current_section = $_[0]; $text->delete('1.0', 'end'); $menu_headings->menu()->delete(0,'end'); my ($i, $len); return unless exists $sections{$current_section}; my $spaces = " " x 40; my $words_in_line = 0; # New line when this goes to three my $man; foreach $man (@{$sections{$current_section}}) { $text->insert('end', $man . substr($spaces,0, 24 - length($man))); if (++$words_in_line == 3) { $text->insert('end', "\n"); $words_in_line = 0; } } } sub search { my $search_pattern = $search->get(); $text->tagDelete('search'); $text->tagConfigure('search', '-background' => 'yellow', '-foreground' => 'red'); my $current = '1.0'; my $length = '0'; while (1) { if ($ignore_case) { $current = $text->search('-count' => \$length, $match_type, '-nocase', '--', $search_pattern, $current, 'end'); } else { $current = $text->search('-count' => \$length, $match_type, '--', $search_pattern, $current, 'end'); } last if (!$current); $text->tagAdd('search', $current, "$current + $length char"); $current = $text->index("$current + $length char"); } } use Cwd; sub scout_man_dirs { my (@man_dirs,$man_dir, $section); if ($ENV{MANPATH}) { @man_dirs = split (/:/, $ENV{MANPATH}); } else { push (@man_dirs, "/usr/man"); } # Convert all relative man paths to fully qualified ones, by # prepending with $cwd my $cwd = cwd(); foreach $man_dir (@man_dirs) { next if ($man_dir =~ m|^/|); $man_dir = "$cwd/$man_dir"; # Modifies entry in man_dirs } foreach $man_dir (@man_dirs) { chdir $man_dir || next; # Now, in /usr/man, say. Get all the directories my @section_dirs = grep {-d $_} ; my $section_dir; # @section_dirs has man1, man2, man3s etc. foreach $section_dir (@section_dirs) { chdir $section_dir || next; ($section = $section_dir) =~ s/^man//; push (@{$sections{$section}}, <*.$section>); chdir ".."; } chdir ".."; } # All sections in all man pages have been slurped in. Remove duplicates foreach $section (keys %sections) { my @new_list; my %seen; @new_list = sort (grep (!$seen{$_}++, @{$sections{$section}})); # Change all entries like cc.1 to cc(1) foreach (@new_list) { $_ =~ s/[.](.*)/($section)/; } $sections{$section} = \@new_list; } }