summaryrefslogtreecommitdiff
path: root/lib/ManUtils/ManUtils.pm
blob: 251a20b923f4a642d088ff8c1ac639661a07daba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package ManUtils;

use strict;
use warnings;
use AE;
use AnyEvent::Util;
use Encode 'decode_utf8', 'encode_utf8';


our $VERSION = '0.01';

require XSLoader;
XSLoader::load('ManUtils', $VERSION);


sub _groff {
  my($input, $output, $errors, $cv, @cmd) = @_;

  # $MANWIDTH works by using the following groff options: -rLL=100n -rLT=100n
  splice @cmd, 1, 0, qw|-Tutf8 -DUTF-8 -P-c -rLL=80n -rLT=80n|;

  $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"
    .$input;

  my $groff = run_cmd \@cmd,
    '<' => \$input,
    '>' => \my $fmt,
    '2>' => sub { if($_[0]) { chomp(my $e = $_[0]); push @$errors, "groff: $e" } };

  $groff->cb(sub {
    $$output = $fmt ? decode_utf8($fmt) : '';
    $$output =~ s/[\t\s\r\n]+$//;
    $cv->send;
  });
  $cv
}


# Usage: $cv = fmt($input, \$output, \@errors)
# $cv = AnyEvent condition variable, fired when done.
# $input = UTF-8 encoded manual page source
# $output = variable that will hold the output when done
# @errors = list of warnings/errors while running groff
sub fmt {
  my($input, $output, $errors) = @_;
  my $cv = AE::cv;
  $$output = '';
  @$errors = ();

  $input = encode_utf8($input);

  # grog has a tendency to recognize pod2man generated pages as -ms, let's just work around that by enforcing -man
  #return _groff $input, $output, $errors, $cv, 'groff', '-man' if $input =~ /^.\\" Automatically generated by Pod::Man/;

  # Call grog to figure out which preprocessors to use.
  my $grog = run_cmd [qw|grog -Tutf8 -DUTF-8 -|],
    '<' => \$input,
    '>' => \my $cmd,
    '2>' => sub { $_[0] && push @$errors, "grog: $_[0]" };

  $grog->cb(sub {
    chomp($cmd);
    if(!$cmd || $cmd =~ /\n/) {
      push @$errors, !$cmd ? 'grog failed to produce output' : "Excessive grog output: $cmd";
      $cv->send;
      return;
    }

    my $double;
    @$errors = grep {
      chomp;
      s/^grog: grog: /grog: /;
      !$double && /there are several macro packages: (.+)$/ ? ($double = $1) && 0 : 1;
    } @$errors;

    my @cmd = split / /, $cmd;
    if($double) {
      my %double = map +($_,1), split / /, $double;
      # Use the first macro package in ASCIIbetical order. (This is somewhat
      # arbitrary, need to find a better conflict resolution method).
      my $macros = (sort keys %double)[0];
      # Replace macro arguments with our selected one.
      @cmd = grep !$double{$_}, @cmd;
      @cmd = (@cmd[0..$#cmd-1], $macros, $cmd[$#cmd]);
      push @$errors, "grog detected several macro packages: $double. Using $macros. (@cmd)";
    }
    @cmd = map $_ eq '-ms' ? '-man' : $_, @cmd; # -ms is almost(?) always wrong.

    _groff $input, $output, $errors, $cv, @cmd;
  });

  $cv;
}


# Blocking version of fmt(). Returns the formatted man page, errors are
# forwarded to warn().
sub fmt_block {
  my $c = shift;
  my $cv = fmt $c, \my $out, \my @err;
  $cv->recv;
  #warn "$_\n" for @err;
  $out;
}

1;