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$

Methods
M
N
P
Constants
ANGLE = 1
 
OPTION = 2
 
PAREN = 3
 
RE_PUNCT = /^[!"'),\.\/:;>\?\]`]$/
 
Class Public methods
mdoc2man(i, o)
# File tool/mdoc2man.rb, line 466
def self.mdoc2man(i, o)
  new.mdoc2man(i, o)
end
new()
# File tool/mdoc2man.rb, line 52
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
Instance Public methods
mdoc2man(i, o)
# File tool/mdoc2man.rb, line 67
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
parse_macro(line)
# File tool/mdoc2man.rb, line 85
def parse_macro(line)
  words = line.split
  retval = ''

  quote = []
  dl = false

  while word = words.shift
    case word
    when RE_PUNCT
      next retval << word if word == ':'
      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
    when 'Bro'
      retval << '{'
      @nospace = 1 if @nospace == 0
      next
    when 'Brc'
      retval.sub!(/ *\z/, '}')
      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