3 # groffer - display groff files
5 # Source file position: <groff-source>/contrib/groffer/subs.pl
6 # Installed position: <prefix>/lib/groff/groffer/subs.pl
8 # Copyright (C) 2006-2018 Free Software Foundation, Inc.
9 # Written by Bernd Warken <groff-bernd.warken-72@web.de>.
11 # Last update: 27 Aug 2015
13 # This file is part of 'groffer', which is part of 'groff'.
15 # 'groff' is free software; you can redistribute it and/or modify it
16 # under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 2 of the License, or
18 # (at your option) any later version.
20 # 'groff' is distributed in the hope that it will be useful, but
21 # WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 # General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see
27 # <http://www.gnu.org/licenses/gpl-2.0.html>.
29 ########################################################################
34 ########################################################################
36 ########################################################################
41 # Decompress or cat the <file>.
43 # Return: the decompressed file as array
47 die "cat_z(): one argument is needed; you used $n;"
51 die "cat_z(): '$file' is not a readable file;" unless -f $file && -r $file;
52 return () if -z $file;
55 if ($main::Has_Compression) {
56 if ($main::Has_bzip) {
57 # test whether bz2 compressed, shell return must be inverted
58 unless ( system("bzip2 -t $file 2>$main::Dev_Null") ) {
59 @res = `bzip2 -c -d $file 2>$main::Dev_Null`;
62 # if not compressed with gz, gzip will act like 'cat'
63 @res = `gzip -c -d -f $file 2>$main::Dev_Null`;
68 open $fh, "<$file" or die "cat_z(): could not open $file";
80 # Remove the temporary directory and restore the system.
84 chdir $main::Start_Dir;
85 if ($main::Debug{'KEEP'} && -d $main::tmpdir) {
86 my $glob = File::Spec->catfile($main::tmpdir, '*');
87 unlink glob($glob); # rm $tmpdir/*
96 # Split the path and return the directory name part
98 # Return: string of directory name
102 die "get_filename(): one argument is needed; you used $n;" unless $n == 1;
103 return '' unless $_[0];
105 my ($dirname, $filename) = &split_path($_[0]);
111 # get_filename(<path>)
113 # Split the path and return the file name part
115 # Return: string of file name
119 die "get_dirname(): one argument is needed; you used $n;" unless $n == 1;
120 return '' unless $_[0];
122 my ($dirname, $filename) = &split_path($_[0]);
130 # Test whether X Windows is running.
133 return 1 if $ENV{'DISPLAY'};
139 # list_has(<list_ref>, <string>)
141 # Determine if <list_ref> has <string> as element.
145 die "list_has(): 2 arguments are needed; you used $n;"
148 my $list_ref = $_[0];
150 die "list_has(): first argument must be an array reference;"
151 unless ref($list_ref) eq 'ARRAY';
153 foreach ( @$list_ref ) {
154 return 1 if $_ eq $string;
161 # path_uniq(<dir>...)
163 # make path having unique existing directories
169 next if exists $h{$_};
179 # print_hash(<hash_or_ref>)
181 # print the elements of a hash or hash reference
187 print "empty hash\n;";
190 if (ref($_[0]) eq 'HASH') {
193 warn 'print_hash(): the argument is not a hash or hash reference;';
198 warn 'print_hash(): the arguments are not a hash;';
208 print "empty hash\n";
211 print "hash (ignore the ^ characters):\n";
212 for my $k (sort keys %$hr) {
228 # print_times(<string>)
230 # print the time, result: user, system, child process user, child system
236 # print STDERR "$s: @t\n";
239 BEGIN { &print_times("start"); }
240 END { &print_times("end"); }
246 # Split the path into directory and file name parts
248 # Return: array with 2 elements consisting of directory and file name
252 die "split_path(): one argument is needed; you used $n;" unless $n == 1;
254 return () unless $arg;
256 my $basename = basename($arg);
257 if ( $basename ne $arg ) { # path with dir
258 # fileparse() is from File::Basename
259 my($filename, $dirname, $suffix) = fileparse($arg);
260 return ($dirname, $basename);
275 if ! system("echo -n '' | soelim -r 2>$main::Dev_Null >$main::Dev_Null");
278 # to_tmp (<filename>)
280 # Print file (decompressed) to the temporary cat file with handling .SO
285 die "to_tmp(): one argument is needed; you used $n;"
289 return 1 unless $arg;
290 die "to_tmp(): $arg is not an existing file;" unless -f $arg;
291 die "to_tmp(): could not read file $arg;" unless -r $arg;
294 $tmp_file_base = File::Spec->catfile($main::tmpdir, ',file')
295 unless $tmp_file_base;
296 $tmp_so_base = File::Spec->catfile($main::tmpdir, ',so')
299 open $main::fh_cat, ">>$main::tmp_cat" or
300 die "to_tmp(): could not open temporary cat file";
302 if ($main::Opt{'WHATIS'}) {
303 &whatis_filename($arg);
308 my $dir = &get_dirname($arg);
310 my ($fh_file, $tmp_file, $fh_tmp, $tmp_tmp);
312 $tmp_file = $tmp_file_base . $nr_file;
313 $tmp_tmp = File::Spec->catfile($main::tmpdir, ',tmp');
315 print STDERR "file: $arg\n" if $main::Debug{'FILENAMES'};
317 if ($main::Filespec_Is_Man) {
318 my ($fh_so, $tmp_so);
320 open $fh_file, ">$tmp_file" or
321 die "to_tmp(): could not open $tmp_file;";
322 foreach ( &cat_z($arg) ) {
327 open $fh_file, "<$tmp_file" or
328 die "to_tmp(): could not open $tmp_file;";
330 foreach (<$fh_file>) {
331 if (/^[\.']\s*so\s/) {
339 if ( @list && $main::Debug{'KEEP'} ) {
340 my $f = $tmp_file . '+man';
345 DO_MAN_SO: foreach (@list) {
346 # start of _do_man_so() in shell version
349 $soname =~ s/\\\s/ /g;
352 my $path = File::Spec->rootdir();
353 if ($soname =~ m#^$path#) { # absolute path name
354 next DO_MAN_SO if -f $soname;
355 foreach ('.gz', '.Z', '.bz2') {
356 my $name = $soname . $_;
362 next DO_MAN_SO unless $sofound;
363 } else { # relative to man path
364 LOOP: foreach my $ext ('', '.gz', '.Z', '.bz2') {
365 foreach my $p ( @{$main::Man{'PATH'}} ) {
366 my $f = File::Spec->catfile($p, "$soname$ext");
368 $sofound = $f if -f $f;
373 next DO_MAN_SO unless $sofound;
376 print STDERR "file from .so: $so\n" if $main::Debug{'FILENAMES'};
380 $tmp_so = $tmp_so_base . $nr_so;
381 unlink $tmp_so if -e $tmp_so;
382 open $fh_so, ">$tmp_so" or
383 die "to_tmp(): could not open $tmp_so;";
384 foreach ( &cat_z($sofound) ) {
391 open $fh_file, "<$tmp_file" or
392 die "to_tmp(): could not open $tmp_file;";
393 open $fh_tmp, ">$tmp_tmp" or
394 die "to_tmp(): could not open $tmp_tmp;";
395 foreach (<$fh_file>) {
396 s#^([\.'])\s*so\s+($so|$esc|$soname)\s*\n$#${1}so $tmp_so\n#s;
402 unlink $tmp_file if -e $tmp_file;
403 rename $tmp_tmp, $tmp_file;
404 # end of _do_man_so() in shell version
407 if ( @list && $main::Debug{'KEEP'} ) {
408 my $f = $tmp_file . '+tmp';
412 unlink $tmp_tmp if -e $tmp_tmp;
413 rename $tmp_file, $tmp_tmp;
414 system("soelim -I$dir $soelim_r $tmp_tmp >$tmp_file");
415 unlink $tmp_tmp if -e $tmp_tmp;
417 } else { # $Filespec_Is_Man is empty
418 open $fh_tmp, ">$tmp_tmp" or
419 die "to_tmp(): could not open $tmp_tmp;";
420 foreach (cat_z $arg) {
425 system("soelim -I$dir $soelim_r $tmp_tmp >$tmp_file");
427 system("soelim $soelim_r $tmp_tmp >$tmp_file");
430 } # if ($Filespec_Is_Man)
433 my $grog = `grog $tmp_file`;
434 die "to_tmp(): grog error on $tmp_file;" if $?;
436 print STDERR "grog output: $grog\n" if $main::Debug{'GROG'};
437 if ($grog =~ /^.*\s-m.*$/) {
439 $grog =~ s/ -m / -m/g;
440 $grog =~ s/ -mm([^ ]) / -m$1/g;
441 foreach my $g (split / /, $grog) {
443 my $ref = \@main::Macro_Packages;
444 if ( &list_has($ref, $g) ) {
445 if (! $main::Macro_Pkg) {
446 $main::Macro_Pkg = $g;
447 } elsif ($main::Macro_Pkg eq $g) {
449 } elsif ($main::Macro_Pkg =~ /^-m/) {
450 warn "to_tmp(): Ignore $arg because it needs $g " .
451 "instead of $main::Macro_Pkg";
452 unlink $tmp_file unless $main::Debug{'KEEP'};
454 } elsif ($main::Macro_Pkg ne $g) {
455 die "to_tmp(): \$Macro_Pkg does not start with -m: " .
457 } # if (! $main::Macro_Pkg)
463 open $fh_file, "<$tmp_file" or
464 die "to_tmp(): could not open $tmp_file for reading;";
465 open $main::fh_cat, ">>$main::tmp_cat" or
466 die "to_tmp(): could not open $main::tmp_cat for appending;";
467 foreach (<$fh_file>) {
468 print $main::fh_cat $_;
473 unless ( $main::Debug{'KEEP'} ) {
475 foreach ( glob("$tmp_so_base*") ) {
484 # to_tmp_line (<text>...)
486 # Print array of lines with <text> to the temporary cat file. \n is added
487 # if a line does not end with \n.
492 open $main::fh_cat, ">>$main::tmp_cat" or
493 die "to_tmp_line(): could not open temporary cat file";
497 print $main::fh_cat "$line\n";
507 # Print usage information for --help.
514 Usage: groffer [option]... [filespec]...
516 Display roff files, standard input, and/or Unix manual pages with an X
517 Window viewer or in several text modes. All input is decompressed
518 on-the-fly with all formats that gzip can handle.
521 "filename" name of a readable file
522 "-" for standard input
523 "man:name(n)" man page "name" in section "n"
524 "man:name.n" man page "name" in section "n"
525 "man:name" man page "name" in first section found
526 "name(n)" man page "name" in section "n"
527 "name.n" man page "name" in section "n"
528 "n name" man page "name" in section "n"
529 "name" man page "name" in first section found
530 where 'section' is a single character out of [1-9on], optionally followed
531 by some more letters that are called the 'extension'.
533 -h --help print this usage message.
534 -T --device=name pass to groff using output device "name".
535 -v --version print version information.
536 -V display the groff execution pipe instead of formatting.
537 -X display with "gxditview" using groff -X.
538 -Z --ditroff --intermediate-output
539 generate groff intermediate output without
540 post-processing and viewing, like groff -Z.
541 All other short options are interpreted as "groff" formatting options.
543 The most important groffer long options are
545 --apropos=name start man's "apropos" program for "name".
547 "apropos" for "name" in man's data sections 4, 5, 7.
549 "apropos" for "name" in development sections 2, 3, 9.
551 "apropos" for "name" in man's program sections 1, 6, 8.
552 --auto choose mode automatically from the default mode list.
553 --default reset all options to the default value.
554 --default-modes=mode1,mode2,...
555 set sequence of automatically tried modes.
556 --dvi display in a viewer for TeX device independent format.
557 --groff process like groff, disable viewing features.
558 --help display this helping output.
559 --html display in a web browser.
560 --man check file parameters first whether they are man pages.
561 --mode=auto|dvi|groff|html|pdf|ps|source|text|tty|utf8|www|x|X|xhtml
563 --no-man disable man-page facility.
564 --no-special disable --all, --apropos*, and --whatis
565 --pager=program preset the paging program for tty mode.
566 --pdf display in a PDF viewer.
567 --ps display in a Postscript viewer.
568 --source output as roff source.
569 --text output in a text device without a pager.
570 --to-stdout output the content of the mode file without display.
571 --tty display with a pager on text terminal even when in X.
572 --utf8 unicode text mode
573 --viewer choose a viewer for the actual device mode
574 --whatis display the file name and description of man pages
575 --www same as --html.
576 --x --X display with "gxditview" using an X* device.
577 --xhtml display html in XML mode using a web browser.
579 The usual X Windows toolkit options transformed into GNU long options:
580 --background=color, --bd=size, --bg=color, --bordercolor=color,
581 --borderwidth=size, --bw=size, --display=Xdisplay, --fg=color,
582 --fn=font, --font=font, --foreground=color, --geometry=geom, --iconic,
583 --resolution=dpi, --rv, --title=text, --xrm=resource
585 Long options of GNU "man":
586 --all, --ascii, --ditroff, --extension=suffix, --locale=language,
587 --local-file=name, --location, --manpath=dir1:dir2:...,
588 --sections=s1:s2:..., --systems=s1,s2,..., --where, ...
590 Development options that are not useful for normal usage:
591 --debug, --debug-filenames, --debug-grog, --debug-keep, --debug-params,
592 --debug-tmpdir, --do-nothing, --print=text
596 # The following mode-viewer options were replaced by a single --viewer
597 # option. The old options are only ignored:
598 # --dvi-viewer=prog choose the viewer program for dvi mode.
599 # --html-viewer=program
600 # choose the web browser for html or xhtml mode.
601 # --pdf-viewer=prog choose the viewer program for pdf mode.
602 # --ps-viewer=prog choose the viewer program for ps mode.
603 # --tty-viewer=prog select a pager for tty mode; same as --pager.
604 # --www-viewer=prog same as --html-viewer
605 # --x-viewer=prog choose viewer program for x mode (X mode).
606 # --X-viewer=prog same as "--xviewer".
614 # Get version information from version.sh and print a text with this.
618 my $program_version = '';
619 my $last_update = '';
620 my $groff_version_preset = '';
622 die "$main::File_version_sh does not exist;"
623 unless -f "$main::File_version_sh";
625 open $fh, "<$main::File_version_sh";
628 if (/^\s*_PROGRAM_VERSION\s*=\s*['"]*([^'"]*)['"]*\s*;?\s*$/) {
629 $program_version = $1;
632 if (/^\s*_GROFF_VERSION_PRESET\s*=\s*['"]*([^'"]*)['"]*\s*;?\s*$/) {
633 # this setting of the groff version is only used before make is run,
634 # otherwise @VERSION@ will set it, see groffer.sh.
635 $groff_version_preset = $1;
641 if ($main::Groff_Version) {
642 $groff_version = $main::Groff_Version;
644 $groff_version = $groff_version_preset;
647 groffer $program_version is part of groff version $groff_version.
648 GNU groff and groffer come with ABSOLUTELY NO WARRANTY.
649 You may redistribute copies of groff and its subprograms
650 under the terms of the GNU General Public License.
656 # where_is_prog(<program>)
658 # Test whether <program> without its arguments exists or is a program
661 # Arguments : 1, <program> can have spaces and arguments.
662 # Return : a hash with 'dir', 'file', 'fullname', 'args' if
663 # argument exists or is a program in $PATH, empty hash else.
666 scalar @_ eq 1 or die "where_is_prog(): only one argument is allowed";
668 return () unless $p1;
673 return () unless $p1;
677 return () unless $noarg;
680 if ($p1 =~ /^.* -.*$/) {
682 $args =~ s#^$noarg ##;
684 $args = '' unless defined $args;
688 # test whether $noarg has directory, so it is not tested with $PATH
689 my ($dir, $name) = &split_path($noarg);
690 $result{'dir'} = $dir;
693 if (-f $noarg && -x $noarg) {
694 $result{'args'} = $args;
695 $result{'file'} = $name;
696 $result{'fullname'} = File::Spec->catfile($dir, $name);
700 if ($dir) { # $noarg has name with directory
701 # now $wip_noarg (with directory) is not an executable file
703 # test name with space
705 my @base = split(/ /, $name);
709 my $base = join(' ', @base);
710 my $file = File::Spec->catpath($dir, $base);
711 if (-f $file && -x $file) {
712 my $baseargs = join(' ', @baseargs);
715 $args = "$baseargs $args";
719 $result{'args'} = $args;
720 $result{'file'} = $base;
721 $result{'fullname'} = $file;
725 unshift(@baseargs, $n);
727 } # end of test name with space
731 # now $noarg doesn't have a directory part
737 # test path with $win_noarg as a whole, evt. with spaces
738 foreach my $d (@main::Path) {
739 my $file = File::Spec->catfile($d, $name);
740 if (-f $file && -x $file) {
741 $result{'args'} = $args;
742 $result{'fullname'} = $file;
743 ($result{'dir'}, $result{'file'}) = &split_path($file);
746 } # foreach (@main::Path)
750 my @base = split(/ /, $name);
754 my $base = join(' ', @base);
755 foreach my $d (@main::Path) {
756 my $file = File::Spec->catfile($d, $base);
757 if (-f $file && -x $file) {
758 my $baseargs = join(' ', @baseargs);
760 $args = "$baseargs $args";
764 $result{'args'} = $args;
765 $result{'fullname'} = $file;
766 ($result{'dir'}, $result{'file'}) = &split_path($file);
769 } # foreach (@main::Path)
771 unshift(@baseargs, $n);
773 } # test $name on space
781 # stop for checking temp files, etc.
791 ########################################################################