maint: prefer https: to git:
[coreutils.git] / man / help2man
blob581f69ddde15c5a549a0cc54fa12aa98844763f3
1 #!/usr/bin/perl -w
3 # Generate a short man page from --help and --version output.
4 # Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009,
5 # 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2020, 2021 Free Software
6 # Foundation, Inc.
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3, or (at your option)
11 # any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, see <https://www.gnu.org/licenses/>.
21 # Written by Brendan O'Dea <bod@debian.org>
22 # Available from https://ftp.gnu.org/gnu/help2man/
24 use 5.008;
25 use strict;
26 use Getopt::Long;
27 use Text::ParseWords qw(shellwords);
28 use Text::Tabs qw(expand);
29 use POSIX qw(strftime setlocale LC_ALL);
31 my $this_program = 'help2man';
32 my $this_version = '1.48.5';
34 sub _ { $_[0] }
35 sub configure_locale
37 my $locale = shift;
38 die "$this_program: no locale support (Locale::gettext required)\n"
39 unless $locale eq 'C';
42 sub dec { $_[0] }
43 sub enc { $_[0] }
44 sub enc_user { $_[0] }
45 sub kark { die +(sprintf shift, @_), "\n" }
46 sub N_ { $_[0] }
48 sub program_basename;
49 sub get_option_value;
50 sub convert_option;
51 sub fix_italic_spacing;
53 my $version_info = enc_user sprintf _(<<'EOT'), $this_program, $this_version;
54 GNU %s %s
56 Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009,
57 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2020, 2021 Free Software
58 Foundation, Inc.
59 This is free software; see the source for copying conditions. There is NO
60 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
62 Written by Brendan O'Dea <bod@debian.org>
63 EOT
65 my $help_info = enc_user sprintf _(<<'EOT'), $this_program, $this_program;
66 `%s' generates a man page out of `--help' and `--version' output.
68 Usage: %s [OPTION]... EXECUTABLE
70 -n, --name=STRING description for the NAME paragraph
71 -s, --section=SECTION section number for manual page (1, 6, 8)
72 -m, --manual=TEXT name of manual (User Commands, ...)
73 -S, --source=TEXT source of program (FSF, Debian, ...)
74 -L, --locale=STRING select locale (default "C")
75 -i, --include=FILE include material from `FILE'
76 -I, --opt-include=FILE include material from `FILE' if it exists
77 -o, --output=FILE send output to `FILE'
78 -p, --info-page=TEXT name of Texinfo manual
79 -N, --no-info suppress pointer to Texinfo manual
80 -l, --libtool exclude the `lt-' from the program name
81 -b, --bold-refs apply bold style to references
82 --help print this help, then exit
83 --version print version number, then exit
85 EXECUTABLE should accept `--help' and `--version' options and produce output on
86 stdout although alternatives may be specified using:
88 -h, --help-option=STRING help option string
89 -v, --version-option=STRING version option string
90 --version-string=STRING version string
91 --no-discard-stderr include stderr when parsing option output
93 Report bugs to <bug-help2man@gnu.org>.
94 EOT
96 my $section = 1;
97 my $manual = '';
98 my $source = '';
99 my $help_option = '--help';
100 my $version_option = '--version';
101 my $discard_stderr = 1;
102 my ($opt_name, @opt_include, $opt_output, $opt_info, $opt_no_info, $opt_libtool,
103 $opt_bold_refs, $version_text);
105 my %opt_def = (
106 'n|name=s' => \$opt_name,
107 's|section=s' => \$section,
108 'm|manual=s' => \$manual,
109 'S|source=s' => \$source,
110 'L|locale=s' => sub { configure_locale pop },
111 'i|include=s' => sub { push @opt_include, [ pop, 1 ] },
112 'I|opt-include=s' => sub { push @opt_include, [ pop, 0 ] },
113 'o|output=s' => \$opt_output,
114 'p|info-page=s' => \$opt_info,
115 'N|no-info' => \$opt_no_info,
116 'l|libtool' => \$opt_libtool,
117 'b|bold-refs' => \$opt_bold_refs,
118 'help' => sub { print $help_info; exit },
119 'version' => sub { print $version_info; exit },
120 'h|help-option=s' => \$help_option,
121 'v|version-option=s' => \$version_option,
122 'version-string=s' => \$version_text,
123 'discard-stderr!' => \$discard_stderr,
126 # Parse options.
127 Getopt::Long::config('bundling');
128 die $help_info unless GetOptions %opt_def and @ARGV == 1;
130 my %include = ();
131 my %replace = ();
132 my %append = ();
133 my %append_match = ();
134 my @sections = (); # retain order of include file or in-line *section*s
136 # Process include file (if given). Format is:
138 # Optional initial text, ignored. May include lines starting with `-'
139 # which are processed as options.
141 # [section]
142 # Verbatim text to be included in the named section. By default at
143 # the start, but in the case of `name' and `synopsis' the content
144 # will replace the autogenerated contents.
146 # [<section]
147 # Verbatim text to be inserted at the start of the named section.
149 # [=section]
150 # Verbatim text to replace the named section.
152 # [>section]
153 # Verbatim text to be appended to the end of the named section.
155 # /pattern/
156 # Verbatim text for inclusion below a paragraph matching `pattern'.
159 while (@opt_include)
161 my ($inc, $required) = @{shift @opt_include};
163 next unless -f $inc or $required;
164 kark N_("%s: can't open `%s' (%s)"), $this_program, $inc, $!
165 unless open INC, $inc;
167 my $key;
168 my $hash;
170 while (<INC>)
172 # Convert input to internal Perl format, so that multibyte
173 # sequences are treated as single characters.
174 $_ = dec $_;
176 # [section]
177 if (/^\[([^]]+)\]\s*$/)
179 $key = uc $1;
180 $key =~ s/^\s+//;
181 $key =~ s/\s+$//;
182 $hash = \%include;
183 # Handle explicit [<section], [=section] and [>section]
184 if ($key =~ s/^([<>=])\s*//)
186 if ($1 eq '>') { $hash = \%append; }
187 elsif ($1 eq '=') { $hash = \%replace; }
189 # NAME/SYNOPSIS replace by default
190 elsif ($key eq _('NAME') or $key eq _('SYNOPSIS'))
192 $hash = \%replace;
194 else
196 $hash = \%include;
199 push @sections, $key;
200 next;
203 # /pattern/
204 if (m!^/(.*)/([ims]*)\s*$!)
206 my $pat = $2 ? "(?$2)$1" : $1;
208 # Check pattern.
209 eval { $key = qr($pat) };
210 if ($@)
212 $@ =~ s/ at .*? line \d.*//;
213 die "$inc:$.:$@";
216 $hash = \%append_match;
217 next;
220 # Check for options before the first section--anything else is
221 # silently ignored, allowing the first for comments and
222 # revision info.
223 unless ($key)
225 # handle options
226 if (/^-/)
228 local @ARGV = shellwords $_;
229 GetOptions %opt_def;
232 next;
235 $hash->{$key} .= $_;
238 close INC;
240 kark N_("%s: no valid information found in `%s'"), $this_program, $inc
241 unless $key;
244 # Compress trailing blank lines.
245 for my $hash (\(%include, %replace, %append, %append_match))
247 for (keys %$hash) { $hash->{$_} =~ s/\n+$/\n/ }
250 # Grab help and version info from executable.
251 my $help_text = get_option_value $ARGV[0], $help_option;
252 $version_text ||= get_option_value $ARGV[0], $version_option;
254 # By default the generated manual pages will include the current date. This may
255 # however be overridden by setting the environment variable $SOURCE_DATE_EPOCH
256 # to an integer value of the seconds since the UNIX epoch. This is primarily
257 # intended to support reproducible builds (wiki.debian.org/ReproducibleBuilds)
258 # and will additionally ensure that the output date string is UTC.
259 my $epoch_secs = time;
260 if (exists $ENV{SOURCE_DATE_EPOCH} and $ENV{SOURCE_DATE_EPOCH} =~ /^(\d+)$/)
262 $epoch_secs = $1;
263 $ENV{TZ} = 'UTC0';
266 # Translators: the following message is a strftime(3) format string, which in
267 # the English version expands to the month as a word and the full year. It
268 # is used on the footer of the generated manual pages. If in doubt, you may
269 # just use %x as the value (which should be the full locale-specific date).
270 my $date = enc strftime _("%B %Y"), localtime $epoch_secs;
271 my $program = program_basename $ARGV[0];
272 my $package = $program;
273 my $version;
275 if ($opt_output)
277 unlink $opt_output or kark N_("%s: can't unlink %s (%s)"),
278 $this_program, $opt_output, $! if -e $opt_output;
280 open STDOUT, ">$opt_output"
281 or kark N_("%s: can't create %s (%s)"), $this_program, $opt_output, $!;
284 # The first line of the --version information is assumed to be in one
285 # of the following formats:
287 # <version>
288 # <program> <version>
289 # {GNU,Free} <program> <version>
290 # <program> ({GNU,Free,} <package>) <version>
291 # <program> - {GNU,Free,} <package> <version>
292 # <program> - {GNU,Free,} <package> - <version>
294 # and separated from any copyright/author details by a blank line.
296 ($_, $version_text) = ((split /\n+/, $version_text, 2), '');
298 if (/^(\S+) +\(((?:(?:GNU|Free) +)?[^)]+)\) +(\S.*)$/ or
299 /^(\S+) +- +((?:(?:GNU|Free) +)?\S.*) +- +(\S.*)$/ or
300 /^(\S+) +- +((?:(?:GNU|Free) +)?\S+) +(\S.*)$/)
302 $program = program_basename $1;
303 $package = $2;
304 $version = $3;
306 elsif (/^((?:GNU|Free) +)?(\S+) +(\S.*)$/)
308 $program = program_basename $2;
309 $package = $1 ? "$1$program" : $program;
310 $version = $3;
312 else
314 $version = $_;
317 # No info for `info' itself.
318 $opt_no_info = 1 if $program eq 'info';
320 if ($opt_name)
322 # --name overrides --include contents.
323 $replace{_('NAME')} = "$program \\- $opt_name\n";
326 # Translators: "NAME", "SYNOPSIS" and other one or two word strings in all
327 # upper case are manual page section headings. The man(1) manual page in your
328 # language, if available should provide the conventional translations.
329 for ($replace{_('NAME')} || ($include{_('NAME')} ||= ''))
331 if ($_) # Use first name given as $program
333 $program = $1 if /^([^\s,]+)(?:,?\s*[^\s,\\-]+)*\s+\\?-/;
335 else # Set a default (useless) NAME paragraph.
337 $_ = sprintf _("%s \\- manual page for %s %s") . "\n", $program,
338 $program, $version;
342 # Man pages traditionally have the page title in caps.
343 my $PROGRAM = uc $program;
345 # Set default page head/footers
346 $source ||= "$package $version";
347 unless ($manual)
349 for ($section)
351 if (/^(1[Mm]|8)/) { $manual = enc _('System Administration Utilities') }
352 elsif (/^6/) { $manual = enc _('Games') }
353 else { $manual = enc _('User Commands') }
357 # Extract usage clause(s) [if any] for SYNOPSIS.
358 # Translators: "Usage" and "or" here are patterns (regular expressions) which
359 # are used to match the usage synopsis in program output. An example from cp
360 # (GNU coreutils) which contains both strings:
361 # Usage: cp [OPTION]... [-T] SOURCE DEST
362 # or: cp [OPTION]... SOURCE... DIRECTORY
363 # or: cp [OPTION]... -t DIRECTORY SOURCE...
364 my $PAT_USAGE = _('Usage');
365 my $PAT_USAGE_CONT = _('or');
366 if ($help_text =~ s/^($PAT_USAGE):( +(\S+))(.*)((?:\n(?: {6}\1| *($PAT_USAGE_CONT): +\S).*)*)//om)
368 my @syn = $3 . $4;
370 if ($_ = $5)
372 s/^\n//;
373 for (split /\n/) { s/^ *(($PAT_USAGE_CONT): +)?//o; push @syn, $_ }
376 my $synopsis = '';
377 for (@syn)
379 $synopsis .= ".br\n" if $synopsis;
380 s!^\S*/!!;
381 s/^lt-// if $opt_libtool;
382 s/^(\S+) *//;
383 $synopsis .= ".B $1\n";
384 s/\s+$//;
385 s/(([][]|\.\.+)+)/\\fR$1\\fI/g;
386 s/^/\\fI/ unless s/^\\fR//;
387 $_ .= '\fR';
388 s/(\\fI)( *)/$2$1/g;
389 s/\\fI\\fR//g;
390 s/^\\fR//;
391 s/\\fI$//;
392 s/^\./\\&./;
394 $_ = fix_italic_spacing $_;
395 $synopsis .= "$_\n";
398 $include{_('SYNOPSIS')} .= $synopsis;
401 # Process text, initial section is DESCRIPTION.
402 my $sect = _('DESCRIPTION');
403 $_ = "$help_text\n\n$version_text";
405 # Normalise paragraph breaks.
406 s/^\n+//;
407 s/\n*$/\n/;
408 s/\n\n+/\n\n/g;
410 # Join hyphenated lines.
411 s/([A-Za-z])-\n *([A-Za-z])/$1$2/g;
413 # Temporarily exchange leading dots, apostrophes and backslashes for
414 # tokens.
415 s/^\./\x80/mg;
416 s/^'/\x81/mg;
417 s/\\/\x82/g;
419 # Translators: patterns are used to match common program output. In the source
420 # these strings are all of the form of "my $PAT_something = _('...');" and are
421 # regular expressions. If there is more than one commonly used string, you
422 # may separate alternatives with "|". Spaces in these expressions are written
423 # as " +" to indicate that more than one space may be matched. The string
424 # "(?:[\\w-]+ +)?" in the bug reporting pattern is used to indicate an
425 # optional word, so that either "Report bugs" or "Report _program_ bugs" will
426 # be matched.
427 my $PAT_BUGS = _('Report +(?:[\w-]+ +)?bugs|' .
428 'Email +bug +reports +to|' .
429 '.* +online +help:');
430 my $PAT_AUTHOR = _('Written +by');
431 my $PAT_OPTIONS = _('Options');
432 my $PAT_ENVIRONMENT = _('Environment');
433 my $PAT_FILES = _('Files');
434 my $PAT_EXAMPLES = _('Examples');
435 my $PAT_FREE_SOFTWARE = _('This +is +free +software');
436 my $PAT_SEE_ALSO = _('Full +documentation');
438 # Start a new paragraph (if required) for these.
439 s/([^\n])\n($PAT_BUGS|$PAT_AUTHOR|$PAT_SEE_ALSO) /$1\n\n$2 /og;
441 # Convert iso-8859-1 copyright symbol or (c) to nroff
442 # character.
443 s/^Copyright +(?:\xa9|\([Cc]\))/Copyright \\(co/mg;
445 while (length)
447 # Convert some standard paragraph names.
448 if (s/^($PAT_OPTIONS): *\n+//o)
450 $sect = _('OPTIONS');
451 next;
453 if (s/^($PAT_ENVIRONMENT): *\n+//o)
455 $sect = _('ENVIRONMENT');
456 next;
458 if (s/^($PAT_FILES): *\n+//o)
460 $sect = _('FILES');
461 next;
463 elsif (s/^($PAT_EXAMPLES): *\n+//o)
465 $sect = _('EXAMPLES');
466 next;
469 # Custom section indicated by a line containing "*Section Name*".
470 if (s/^\*(\w(.*\w)?)\* *\n+//)
472 $sect = uc $1;
473 $sect =~ tr/*/ /; # also accept *Section*Name*
474 push @sections, $sect;
475 next;
478 # Copyright section.
479 if (/^Copyright /)
481 $sect = _('COPYRIGHT');
484 # Bug reporting section.
485 elsif (/^($PAT_BUGS) /o)
487 $sect = _('REPORTING BUGS');
490 # Author section.
491 elsif (/^($PAT_AUTHOR)/o)
493 $sect = _('AUTHOR');
496 elsif (/^($PAT_SEE_ALSO)/o)
498 $sect = _('SEE ALSO');
499 $opt_no_info = 1;
502 # Examples, indicated by an indented leading $, % or > are
503 # rendered in a constant width font.
504 if (/^( +)([\$\%>] )\S/)
506 my $indent = $1;
507 my $prefix = $2;
508 my $break = '.IP';
509 while (s/^$indent\Q$prefix\E(\S.*)\n*//)
511 $include{$sect} .= "$break\n\\f(CW$prefix$1\\fR\n";
512 $break = '.br';
515 next;
518 my $matched = '';
520 # Sub-sections have a trailing colon and the second line indented.
521 if (s/^(\S.*:) *\n / /)
523 $matched .= $& if %append_match;
524 $include{$sect} .= qq(.SS "$1"\n);
527 my $indent = 0;
528 my $content = '';
530 # Option with description.
531 if (s/^( {1,10}([+-]\S.*?))(?:( +(?!-))|\n( {20,}))(\S.*)\n//)
533 $matched .= $& if %append_match;
534 $indent = length ($4 || "$1$3");
535 $content = ".TP\n\x84$2\n\x84$5\n";
536 unless ($4)
538 # Indent may be different on second line.
539 $indent = length $& if /^ {20,}/;
543 # Option without description.
544 elsif (s/^ {1,10}([+-]\S.*)\n//)
546 $matched .= $& if %append_match;
547 $content = ".HP\n\x84$1\n";
548 $indent = 80; # not continued
551 # Indented paragraph with tag.
552 elsif (s/^( +(\S.*?) +)(\S.*)\n//)
554 $matched .= $& if %append_match;
555 $indent = length $1;
556 $content = ".TP\n\x84$2\n\x84$3\n";
559 # Indented paragraph.
560 elsif (s/^( +)(\S.*)\n//)
562 $matched .= $& if %append_match;
563 $indent = length $1;
564 $content = ".IP\n\x84$2\n";
567 # Left justified paragraph.
568 else
570 s/(.*)\n//;
571 $matched .= $& if %append_match;
572 $content = ".PP\n" if $include{$sect};
573 $content .= "$1\n";
576 # Append continuations.
577 while ($indent ? s/^ {$indent}(\S.*)\n// : s/^(\S.*)\n//)
579 $matched .= $& if %append_match;
580 $content .= "\x84$1\n";
583 # Move to next paragraph.
584 s/^\n+//;
586 for ($content)
588 # Leading dot and apostrophe protection.
589 s/\x84\./\x80/g;
590 s/\x84'/\x81/g;
591 s/\x84//g;
593 # Examples should be verbatim.
594 unless ($sect eq _('EXAMPLES'))
596 # Convert options.
597 s/(^|[ (])(-[][\w=-]+)/$1 . convert_option $2/mge;
599 # Italicise filenames: /a/b, $VAR/c/d, ~/e/f
601 (^|[ (]) # space/punctuation before
603 (?:\$\w+|~)? # leading variable, or tilde
604 (?:/\w(?:[\w.-]*\w)?)+ # path components
606 ($|[ ,;.)]) # space/punctuation after
607 !$1\\fI$2\\fP$3!xmg;
609 $_ = fix_italic_spacing $_;
612 # Escape remaining hyphens.
613 s/-/\x83/g;
615 if ($sect eq _('COPYRIGHT'))
617 # Insert line breaks before additional copyright messages
618 # and the disclaimer.
619 s/\n(Copyright |$PAT_FREE_SOFTWARE)/\n.br\n$1/og;
621 elsif ($sect eq _('REPORTING BUGS'))
623 # Handle multi-line bug reporting sections of the form:
625 # Report <program> bugs to <addr>
626 # GNU <package> home page: <url>
627 # ...
628 s/\n([[:upper:]])/\n.br\n$1/g;
630 elsif ($sect eq _('SEE ALSO'))
632 # Handle external references of the form:
634 # GNU <package> online resources: <addr>
635 # Full documentation at: <addr>
636 # or available locally via: info ...
638 s/\'/\\(aq/g; # shell quotes for info command
639 s/\n(.)/\n.br\n$1/g; # separate lines for each item
643 # Check if matched paragraph contains /pat/.
644 if (%append_match)
646 for my $pat (keys %append_match)
648 if ($matched =~ $pat)
650 $content .= ".PP\n" unless $append_match{$pat} =~ /^\./;
651 $content .= $append_match{$pat};
656 $include{$sect} .= $content;
659 # Refer to the real documentation.
660 unless ($opt_no_info)
662 my $info_page = $opt_info || $program;
664 $sect = _('SEE ALSO');
665 $include{$sect} .= ".PP\n" if $include{$sect};
666 $include{$sect} .= sprintf _(<<'EOT'), $program, $program, $info_page;
667 The full documentation for
668 .B %s
669 is maintained as a Texinfo manual. If the
670 .B info
672 .B %s
673 programs are properly installed at your site, the command
675 .B info %s
677 should give you access to the complete manual.
681 # Append additional text.
682 while (my ($sect, $text) = each %append)
684 $include{$sect} .= $append{$sect};
687 # Replace sections.
688 while (my ($sect, $text) = each %replace)
690 $include{$sect} = $replace{$sect};
693 # Output header.
694 print <<EOT;
695 .\\" DO NOT MODIFY THIS FILE! It was generated by $this_program $this_version.
696 .TH $PROGRAM "$section" "$date" "$source" "$manual"
699 # Section ordering.
700 my @pre = (_('NAME'), _('SYNOPSIS'), _('DESCRIPTION'), _('OPTIONS'),
701 _('EXAMPLES'));
702 my @post = (_('ENVIRONMENT'), _('FILES'), _('AUTHOR'),
703 _('REPORTING BUGS'), _('COPYRIGHT'), _('SEE ALSO'));
704 my %filter = map { $_ => 1 } @pre, @post;
706 # Output content.
707 my %done;
708 for my $sect (@pre, (grep !$filter{$_}, @sections), @post)
710 next if $done{$sect}++; # ignore duplicates
711 next unless $include{$sect};
712 if ($include{$sect})
714 my $quote = $sect =~ /\W/ ? '"' : '';
715 print enc ".SH $quote$sect$quote\n";
717 for ($include{$sect})
719 # Add bold style around referenced pages.
720 if ($opt_bold_refs)
722 # This will ignore entries already marked up (with \)
723 s/(^|\s|,)([\[\w\x83]+)\(([1-9][[:lower:]]?)\)/$1\\fB$2\\fP($3)/g;
726 # Replace leading dot, apostrophe, backslash and hyphen
727 # tokens.
728 s/\x80/\\&./g;
729 s/\x81/\\&'/g;
730 s/\x82/\\e/g;
731 s/\x83/\\-/g;
733 # Convert some latin1 chars to troff equivalents
734 s/\xa0/\\ /g; # non-breaking space
736 print enc $_;
741 close STDOUT or kark N_("%s: error writing to %s (%s)"), $this_program,
742 $opt_output || 'stdout', $!;
744 exit;
746 # Get program basename, and strip libtool "lt-" prefix if required.
747 sub program_basename
749 local $_ = shift;
750 s!.*/!!;
751 s/^lt-// if $opt_libtool;
755 # Call program with given option and return results.
756 sub get_option_value
758 my ($prog, $opt) = @_;
759 my $stderr = $discard_stderr ? '/dev/null' : '&1';
760 my $value = join '',
761 map { s/ +$//; expand $_ }
762 map { dec $_ }
763 `$prog $opt 2>$stderr`;
765 unless ($value)
767 my $err = N_("%s: can't get `%s' info from %s%s");
768 my $extra = $discard_stderr
769 ? "\n" . N_("Try `--no-discard-stderr' if option outputs to stderr")
770 : '';
772 kark $err, $this_program, $opt, $prog, $extra;
775 $value;
778 # Convert option dashes to \- to stop nroff from hyphenating 'em, and
779 # embolden. Option arguments get italicised.
780 sub convert_option
782 local $_ = '\fB' . shift;
784 s/-/\x83/g;
785 unless (s/\[=(.*)\]$/\\fR[=\\fI$1\\fR]/)
787 s/=(.)/\\fR=\\fI$1/;
788 s/ (.)/ \\fI$1/;
789 $_ .= '\fR';
795 # Insert spacing escape characters \, and \/ before and after italic text. See
796 # https://www.gnu.org/software/groff/manual/html_node/Ligatures-and-Kerning.html
797 sub fix_italic_spacing
799 local $_ = shift;
800 s!\\fI(.*?)\\f([BRP])!\\fI\\,$1\\/\\f$2!g;
801 return $_;