1#! /usr/local/bin/perl 2 3## mdoc2man.pl -- Convert mdoc tags to man tags 4## 5## Author: Harlan Stenn <stenn@ntp.org> 6## 7## 8## This file is part of AutoOpts, a companion to AutoGen. 9## AutoOpts is free software. 10## AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 11## 12## AutoOpts is available under any one of two licenses. The license 13## in use must be one of these two and the choice is under the control 14## of the user of the license. 15## 16## The GNU Lesser General Public License, version 3 or later 17## See the files "COPYING.lgplv3" and "COPYING.gplv3" 18## 19## The Modified Berkeley Software Distribution License 20## See the file "COPYING.mbsd" 21## 22## These files have the following sha256 sums: 23## 24## 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 25## 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 26## 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 27 28### ToDo 29# Properly implement -columns in the "my %lists" definition... 30# 31# .Xr requires at least 1 arg, the code here expects at least 2 32# 33### 34 35package mdoc2man; 36use strict; 37use warnings; 38use File::Basename; 39use lib dirname(__FILE__); 40use Mdoc qw(hs ns pp mapwords son soff stoggle gen_encloser); 41 42######## 43## Basic 44######## 45 46Mdoc::def_macro( '.Sh', sub { '.SH', hs, @_ }, raw => 1); 47Mdoc::def_macro( '.Ss', sub { '.SS', hs, @_ }, raw => 1); 48Mdoc::def_macro( '.Pp', sub { ".sp \\n(Ppu\n.ne 2\n" } ); 49Mdoc::def_macro( '.Nd', sub { "\\- @_" } ); 50 51# Macros that enclose things 52Mdoc::def_macro( '.Brq', gen_encloser(qw({ })) , greedy => 1 ); 53Mdoc::def_macro( '.Op' , gen_encloser(qw([ ])) , greedy => 1 ); 54Mdoc::def_macro( '.Qq' , gen_encloser(qw(" ")) , greedy => 1 ); 55Mdoc::def_macro( '.Dq' , gen_encloser(qw(\*[Lq] \*[Rq])), greedy => 1 ); 56Mdoc::def_macro( '.Ql' , gen_encloser(qw(\[oq] \[cq])) , greedy => 1 ); 57Mdoc::def_macro( '.Sq' , gen_encloser(qw(\[oq] \[cq])) , greedy => 1 ); 58Mdoc::def_macro( '.Pq' , gen_encloser(qw/( )/) , greedy => 1 ); 59Mdoc::def_macro( '.D1' , sub { ".in +4\n", ns, @_ , ns , "\n.in -4" } , greedy => 1); 60 61Mdoc::def_macro( 'Oo', sub { '[', @_ } ); 62Mdoc::def_macro( 'Oc', sub { ']', @_ } ); 63 64Mdoc::def_macro( 'Po', sub { '(', @_} ); 65Mdoc::def_macro( 'Pc', sub { ')', @_ } ); 66 67Mdoc::def_macro( 'Bro', sub { '{', ns, @_ } ); 68Mdoc::def_macro( 'Brc', sub { '}', @_ } ); 69 70Mdoc::def_macro( '.Oo', gen_encloser(qw([ ])), concat_until => '.Oc' ); 71Mdoc::def_macro( '.Bro', gen_encloser(qw({ })), concat_until => '.Brc' ); 72Mdoc::def_macro( '.Po', gen_encloser(qw/( )/), concat_until => '.Pc' ); 73 74Mdoc::def_macro( '.Ev', sub { @_ } ); 75Mdoc::def_macro( '.An', sub { ".NOP ", @_, "\n.br" }, raw => 1 ); 76Mdoc::def_macro( '.Li', sub { mapwords {"\\f[C]$_\\f[]"} @_ } ); 77Mdoc::def_macro( '.Cm', sub { mapwords {"\\f\\*[B-Font]$_\\f[]"} @_ } ); 78Mdoc::def_macro( '.Ic', sub { mapwords {"\\f\\*[B-Font]$_\\f[]"} @_ } ); 79Mdoc::def_macro( '.Fl', sub { mapwords {"\\f\\*[B-Font]\\-$_\\f[]"} @_ } ); 80Mdoc::def_macro( '.Ar', sub { mapwords {"\\f\\*[I-Font]$_\\f[]"} @_ } ); 81Mdoc::def_macro( '.Em', sub { mapwords {"\\fI$_\\f[]"} @_ } ); 82Mdoc::def_macro( '.Va', sub { mapwords {"\\fI$_\\f[]"} @_ } ); 83Mdoc::def_macro( '.Sx', sub { mapwords {"\\fI$_\\f[]"} @_ } ); 84Mdoc::def_macro( '.Xr', sub { "\\fC".(shift)."\\f[]\\fR(".(shift).")\\f[]", @_ } ); 85Mdoc::def_macro( '.Fn', sub { "\\f\\*[B-Font]".(shift)."\\f[]\\fR()\\f[]" } ); 86Mdoc::def_macro( '.Fn', sub { "\\fB".(shift)."\\f[]\\fR()\\f[]" } ); 87Mdoc::def_macro( '.Fx', sub { "FreeBSD", @_ } ); 88Mdoc::def_macro( '.Ux', sub { "UNIX", @_ } ); 89 90Mdoc::def_macro( '.No', sub { ".NOP", map { ($_, ns) } @_ } ); 91Mdoc::def_macro( '.Pa', sub { mapwords {"\\fI$_\\f[]"} @_; } ); 92{ 93 my $name; 94 Mdoc::def_macro('.Nm', sub { 95 $name = shift if (!$name); 96 "\\f\\*[B-Font]$name\\fP", @_ 97 } ); 98} 99 100######## 101## lists 102######## 103 104my %lists = ( 105 bullet => sub { 106 Mdoc::def_macro('.It', sub { '.IP \fB\(bu\fP 2' }); 107 }, 108 109 column => sub { 110 Mdoc::def_macro('.It', sub { '.IP \fB\(bu\fP 2' }); 111 }, 112 113 tag => sub { 114 my (%opts) = @_; 115 116 my $width = ''; 117 118 if (exists $opts{width}) { 119 $width = ' '.((length $opts{width})+1); 120 } 121 122 if (exists $opts{compact}) { 123 my $dobrns = 0; 124 Mdoc::def_macro('.It', sub { 125 my @ret = (".TP$width\n.NOP", hs); 126 if ($dobrns) { 127 ".br\n.ns\n", ns, @ret, @_; 128 } 129 else { 130 $dobrns = 1; 131 @ret, @_; 132 } 133 }, raw => 1); 134 } 135 else { 136 Mdoc::def_macro('.It', sub { 137 ".TP$width\n.NOP", hs, @_ 138 }, raw => 1); 139 } 140 }, 141); 142 143Mdoc::set_Bl_callback(do { my $nested = 0; sub { 144 my $type = shift; 145 my %opts = Mdoc::parse_opts(@_); 146 if (defined $type && $type =~ /-(\w+)/ && exists $lists{$1}) { 147 148 # Wrap nested lists with .RS and .RE 149 Mdoc::set_El_callback(sub { 150 return '.RE' if $nested-- > 1; 151 return '.PP'; 152 }); 153 154 $lists{$1}->(%opts); 155 156 if ($nested++) { 157 return ".RS"; 158 } 159 else { 160 return (); 161 } 162 } 163 else { 164 die "Invalid list type <$type>"; 165 } 166}}, raw => 1); 167 168# don't bother with arguments for now and do what mdoc2man'.sh' did 169 170Mdoc::def_macro('.Bd', sub { ".br\n.in +4\n.nf" } ); 171Mdoc::def_macro('.Ed', sub { ".in -4\n.fi" } ); 172 173Mdoc::set_Re_callback(sub { 174 my ($reference) = @_; 175 <<"REF"; 176$reference->{authors}, 177\\fI$reference->{title}\\fR, 178$reference->{optional}\n.PP 179REF 180}); 181 182# Define all macros which have the same sub for inline and standalone macro 183for (qw(Xr Em Ar Fl Ic Cm Qq Op Nm Pa Sq Li Va Brq Pq Fx Ux)) { 184 my $m = Mdoc::get_macro(".$_"); 185 Mdoc::def_macro($_, delete $m->{run}, %$m); 186} 187 188sub print_line { 189 print shift; 190 print "\n"; 191} 192 193sub run { 194 print <<'DEFS'; 195.de1 NOP 196. it 1 an-trap 197. if \\n[.$] \,\\$*\/ 198.. 199.ie t \ 200.ds B-Font [CB] 201.ds I-Font [CI] 202.ds R-Font [CR] 203.el \ 204.ds B-Font B 205.ds I-Font I 206.ds R-Font R 207DEFS 208 209 while (my ($macro, @args) = Mdoc::parse_line(\*STDIN, \&print_line)) { 210 my @ret = Mdoc::call_macro($macro, @args); 211 print_line(Mdoc::to_string(@ret)) if @ret; 212 } 213 return 0; 214} 215 216exit run(@ARGV) unless caller; 217 2181; 219__END__ 220