#!/usr/bin/env ruby ### ### mdoc2man - mdoc to man converter ### ### Quick usage: mdoc2man.rb < mdoc_manpage.8 > man_manpage.8 ### ### Ported from Perl by Akinori MUSHA. ### ### Copyright (c) 2001 University of Illinois Board of Trustees ### Copyright (c) 2001 Mark D. Roth ### Copyright (c) 2002, 2003 Akinori MUSHA ### All rights reserved. ### ### Redistribution and use in source and binary forms, with or without ### modification, are permitted provided that the following conditions ### are met: ### 1. Redistributions of source code must retain the above copyright ### notice, this list of conditions and the following disclaimer. ### 2. Redistributions in binary form must reproduce the above copyright ### notice, this list of conditions and the following disclaimer in the ### documentation and/or other materials provided with the distribution. ### 3. All advertising materials mentioning features or use of this software ### must display the following acknowledgement: ### This product includes software developed by the University of ### Illinois at Urbana, and their contributors. ### 4. The University nor the names of their ### contributors may be used to endorse or promote products derived from ### this software without specific prior written permission. ### ### THIS SOFTWARE IS PROVIDED BY THE TRUSTEES AND CONTRIBUTORS ``AS IS'' AND ### ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ### IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ### ARE DISCLAIMED. IN NO EVENT SHALL THE TRUSTEES OR CONTRIBUTORS BE LIABLE ### FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ### DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ### OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ### HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ### LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ### OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ### SUCH DAMAGE. ### ### $Id: mdoc2man.rb,v 1.4 2003/01/20 12:10:40 knu Exp $ ### class Mdoc2Man ANGLE = 1 OPTION = 2 PAREN = 3 RE_PUNCT = /^[!"'),\.\/:;>\?\]`]$/ def initialize @name = @date = @id = nil @refauthors = @reftitle = @refissue = @refdate = @refopt = nil @optlist = 0 ### 1 = bullet, 2 = enum, 3 = tag, 4 = item @oldoptlist = 0 @nospace = 0 ### 0, 1, 2 @enum = 0 @synopsis = true @reference = false @ext = false @extopt = false @literal = false end def mdoc2man(i, o) i.each { |line| if /^\./ !~ line o.print line o.print ".br\n" if @literal next end line.slice!(0, 1) next if /\\"/ =~ line line = parse_macro(line) and o.print line } initialize end def parse_macro(line) words = line.split retval = '' quote = [] dl = false while word = words.shift case word when RE_PUNCT while q = quote.pop case q when OPTION retval << ']' when PAREN retval << ')' when ANGLE retval << '>' end end retval << word next when 'Li', 'Pf' @nospace = 1 next when 'Xo' @ext = true retval << ' ' unless retval.empty? || /[\n ]\z/ =~ retval next when 'Xc' @ext = false retval << "\n" unless @extopt break when 'Bd' @literal = true if words[0] == '-literal' retval << "\n" break when 'Ed' @literal = false break when 'Ns' @nospace = 1 if @nospace == 0 retval.chomp!(' ') next when 'No' retval.chomp!(' ') retval << words.shift next when 'Dq' retval << '``' begin retval << words.shift << ' ' end until words.empty? || RE_PUNCT =~ words[0] retval.chomp!(' ') retval << '\'\'' @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0] next when 'Sq', 'Ql' retval << '`' << words.shift << '\'' @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0] next # when 'Ic' # retval << '\\fB' << words.shift << '\\fP' # next when 'Oo' #retval << "[\\c\n" @extopt = true @nospace = 1 if @nospace == 0 retval << '[' next when 'Oc' @extopt = false retval << ']' next when 'Ao' @nospace = 1 if @nospace == 0 retval << '<' next when 'Ac' retval << '>' next end retval << ' ' if @nospace == 0 && !(retval.empty? || /[\n ]\z/ =~ retval) @nospace = 0 if @nospace == 1 case word when 'Dd' @date = words.join(' ') return nil when 'Dt' if words.size >= 2 && words[1] == '""' && /^(.*)\(([0-9])\)$/ =~ words[0] words[0] = $1 words[1] = $2 end @id = words.join(' ') return nil when 'Os' retval << '.TH ' << @id << ' "' << @date << '" "' << words.join(' ') << '"' break when 'Sh' retval << '.SH' @synopsis = (words[0] == 'SYNOPSIS') next when 'Xr' retval << '\\fB' << words.shift << '\\fP(' << words.shift << ')' << words.shift break when 'Rs' @refauthors = [] @reftitle = '' @refissue = '' @refdate = '' @refopt = '' @reference = true break when 'Re' retval << "\n" # authors while @refauthors.size > 1 retval << @refauthors.shift << ', ' end retval << 'and ' unless retval.empty? retval << @refauthors.shift # title retval << ', \\fI' << @reftitle << '\\fP' # issue retval << ', ' << @refissue unless @refissue.empty? # date retval << ', ' << @refdate unless @refdate.empty? # optional info retval << ', ' << @refopt unless @refopt.empty? retval << ".\n" @reference = false break when 'An' next when 'Dl' retval << ".nf\n" << '\\& ' dl = true next when 'Ux' retval << "UNIX" next end if @reference case word when '%A' @refauthors.unshift(words.join(' ')) break when '%T' @reftitle = words.join(' ') @reftitle.sub!(/^"/, '') @reftitle.sub!(/"$/, '') break when '%N' @refissue = words.join(' ') break when '%D' @refdate = words.join(' ') break when '%O' @refopt = words.join(' ') break end end case word when 'Nm' name = words.empty? ? @name : words.shift @name ||= name retval << ".br\n" if @synopsis retval << "\\fB" << name << "\\fP" @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0] next when 'Nd' retval << '\\-' next when 'Fl' retval << '\\fB\\-' << words.shift << '\\fP' @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0] next when 'Ar' retval << '\\fI' if words.empty? retval << 'file ...\\fP' else retval << words.shift << '\\fP' while words[0] == '|' retval << ' ' << words.shift << ' \\fI' << words.shift << '\\fP' end @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0] next end when 'Cm' retval << '\\fB' << words.shift << '\\fP' while RE_PUNCT =~ words[0] retval << words.shift end next when 'Op' quote << OPTION @nospace = 1 if @nospace == 0 retval << '[' # words.push(words.pop + ']') next when 'Aq' quote << ANGLE @nospace = 1 if @nospace == 0 retval << '<' # words.push(words.pop + '>') next when 'Pp' retval << "\n" next when 'Ss' retval << '.SS' next end if word == 'Pa' && !quote.include?(OPTION) retval << '\\fI' retval << '\\&' if /^\./ =~ words[0] retval << words.shift << '\\fP' while RE_PUNCT =~ words[0] retval << words.shift end # @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0] next end case word when 'Dv' retval << '.BR' next when 'Em', 'Ev' retval << '.IR' next when 'Pq' retval << '(' @nospace = 1 quote << PAREN next when 'Sx', 'Sy' retval << '.B ' << words.join(' ') break when 'Ic' retval << '\\fB' until words.empty? || RE_PUNCT =~ words[0] case words[0] when 'Op' words.shift retval << '[' words.push(words.pop + ']') next when 'Aq' words.shift retval << '<' words.push(words.pop + '>') next when 'Ar' words.shift retval << '\\fI' << words.shift << '\\fP' else retval << words.shift end retval << ' ' if @nospace == 0 end retval.chomp!(' ') retval << '\\fP' retval << words.shift unless words.empty? break when 'Bl' @oldoptlist = @optlist case words[0] when '-bullet' @optlist = 1 when '-enum' @optlist = 2 @enum = 0 when '-tag' @optlist = 3 when '-item' @optlist = 4 end break when 'El' @optlist = @oldoptlist next end if @optlist != 0 && word == 'It' case @optlist when 1 # bullets retval << '.IP \\(bu' when 2 # enum @enum += 1 retval << '.IP ' << @enum << '.' when 3 # tags retval << ".TP\n" case words[0] when 'Pa', 'Ev' words.shift retval << '.B' end when 4 # item retval << ".IP\n" end next end case word when 'Sm' case words[0] when 'off' @nospace = 2 when 'on' # retval << "\n" @nospace = 0 end words.shift next end retval << word end return nil if retval == '.' retval.sub!(/\A\.([^a-zA-Z])/, "\\1") # retval.chomp!(' ') while q = quote.pop case q when OPTION retval << ']' when PAREN retval << ')' when ANGLE retval << '>' end end # retval << ' ' unless @nospace == 0 || retval.empty? || /\n\z/ =~ retval retval << ' ' unless !@ext || @extopt || / $/ =~ retval retval << "\n" unless @ext || @extopt || retval.empty? || /\n\z/ =~ retval retval << ".fi\n" if dl return retval end def self.mdoc2man(i, o) new.mdoc2man(i, o) end end if $0 == __FILE__ Mdoc2Man.mdoc2man(ARGF, STDOUT) end