manned/ManUtils/ManUtils.pm
Yorhel 59a7a16c2a Only resolve .so includes when it's the only thing in a man page
Leaving the rest to be formatted as links to the included man page
instead.

Primary reason for this change is to make it possible to cache formatted
man pages, as they now no longer depend on anything except the raw
source of the page itself.
2025-05-25 14:02:10 +02:00

78 lines
2.5 KiB
Perl

package ManUtils 0.2;
use v5.36;
use IPC::Open3;
use IO::Poll qw/POLLOUT POLLIN/;
use Symbol 'gensym';
use XSLoader;
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
XSLoader::load('ManUtils');
sub fmt($input) {
my @cmd = 'groff';
push @cmd, '-t' if $input =~ /^\.TS/m;
push @cmd, '-e' if $input =~ /^\.EQ/m;
# $MANWIDTH works by using the following groff options: -rLL=100n -rLT=100n
push @cmd, qw/-mandoc -Tutf8 -DUTF-8 -P-c -rLL=80n -rLT=80n -/;
# tix comes with* a custom(?) man.macros package. But it looks okay even without loading that.
# (* It actually doesn't, the tcllib package appears to have that file, but doesn't '.so' it)
# Other .so's are just turned into a link.
$input =~ s/^\.so (.+)$/$1 eq 'man.macros' ? '' : ".in -10\n.sp\n[[[MANNEDINCLUDE$1]]]"/emg;
$input =
# Disable hyphenation, since that screws up man page references. :-(
".hy 0\n.de hy\n..\n"
# Emulate man-db's --nj option
.".na\n.de ad\n..\n"
# Unbreak some quotes, there's plenty of man pages that ("incorrectly") use these for code samples.
.".char ` \\`\n"
.".char ' \\[aq]\n"
.$input;
utf8::encode($input);
my $pid = open3(my $in, my $out, my $err = gensym, @cmd);
# "POLLOUT" does NOT guarantee that a write will not block, we must set
# O_NONBLOCK to tell the kernel that we can handle short writes.
fcntl($in, F_SETFL, fcntl($in, F_GETFL, 0) | O_NONBLOCK);
my $inoff = 0;
my $output = '';
my $poll = IO::Poll->new;
$poll->mask($in => POLLOUT);
$poll->mask($out => POLLIN);
$poll->mask($err => POLLIN);
while (1) {
$poll->poll;
if ($poll->events($in)) {
my $r = syswrite $in, $input, 16*1024, $inoff;
die "Write error: $!\n" if $r <= 0;
$inoff += $r;
if ($inoff == length $input) {
$poll->remove($in);
close $in;
}
}
if ($poll->events($out)) {
my $r = sysread $out, $output, 16*1024, length $output;
die "Read error: $!\n" if $r < 0;
if ($r == 0) {
utf8::decode($output);
waitpid $pid, 0;
die "Groff exited with $?\n" if $?;
return $output =~ s/\s+$//r;
}
}
if ($poll->events($err)) {
my $r = sysread $err, my $buf, 16*1024;
$poll->remove($err) if $r == 0;
#warn "GROFF: $buf\n" if $r > 0;
}
}
}
1;