#!/bin/sh

usage()
{
cat << EOT
NAME
  txt2man - convert flat ASCII text to man page format

SYNOPSIS
  txt2man [-hpTX] [-t mytitle] [-P pname] [-r rel] [ifile] 

DESCRIPTION
  txt2man converts the input text into nroff/troff standard man(7)
  macros used to format Unix manual pages. Nice pages can be generated
  specially for commands (section 1 or 8) or for C functions reference
  (sections 2, 3), with the ability to recognize and format command and
  function names, flags, types and arguments.

  txt2man is also able to recognize and format sections, paragraphs, lists
  (standard, numbered, description, nested), cross references and literal
  display blocks.

  If input file ifile is omitted, standard input is taken as
  argument. Result is displayed on standard output.

  Here is how text patterns are recognized and processed:

  Sections    These headers are defined by a line in upper case, starting
              column 1.
  Paragraphs  They must be separated by a blank line, and left aligned.
  Tag list    The item definition is separated from the item description 
              by at least 2 blank spaces, even before a new line, if
              definition is too long. Definition will be emphasized
              by default.
  Bullet list  
              Bullet list items are defined by the first word being "-"
	      or "*" or "o".
  Enumerated list  
	      The first word must be a number followed by a dot.
  Literal display blocks  
	      This paragraph type is used to display unmodified text,
	      for example source code. It must be separated by a blank
	      line, and be indented. It is primarily used to format
	      unmodified source code. It will be printed using fixed font
	      whenever possible (troff).
  Cross references  
	      A cross reference (another man page) is defined by a word
	      followed by a number in parenthesis.

  Special sections:
  NAME      The function or command name and short description are set in 
            this section.
  SYNOPSIS  This section receives a special treatment to identify command
            name, flags and arguments, and propagate corresponding
            attributes later in the text. If a C like function is recognized
	    (word immediately followed by an open parenthesis), txt2man will 
	    print function name in bold font, types in normal font, and 
	    variables in italic font. The whole section will be printed using
	    a fixed font family (courier) whenever possible (troff).

  It is a good practice to embed documentation into source code, by using 
  comments or constant text variables. txt2man allows to do that, keeping
  the document source readable, usable even without further formatting
  (i.e. for online help) and easy to write. The result is high quality 
  and standard complying document.

OPTIONS
  -h          The option -h displays help.
  -P pname    Set pname as project name in header. Default to uname -s.
  -p          Probe title, section name and volume.
  -t mytitle  Set mytitle as title of generated man page.
  -r rel      Set rel as project name and release.
  -T          Text result previewing using PAGER, usually more(1).
  -X          X11 result previewing using gxditview(1).

ENVIRONMENT
  PAGER    name of paging command, usually more(1), or less(1). If not set
           falls back to more(1).

EXAMPLE
  Try this command to format this text itself:

      $ txt2man -h | txt2man -T

SEE ALSO
  man(1), mandoc(7), rman(1), groff(1), more(1), gxditview(1), troff(1).

BUGS
  Automatic probe (-p option) works only if input is a regular file (i.e.
  not stdin).

AUTHOR
  mvertes@cimai.com (Marc Vertes)
EOT
}

sys=$(uname -s)
rel=
volume=
section=
title=untitled
doprobe=
post=cat
while getopts :hpTXr:s:t:P: opt
do
	case $opt in
	r) rel=$OPTARG;;
	t) title=$OPTARG;;
	s) section=$OPTARG;;
	v) volume=$OPTARG;;
	P) sys=$OPTARG;;
	p) doprobe=1;;
	T) post="groff -mandoc -Tlatin1 | ${PAGER:-more}";;
	X) post="groff -mandoc -X";;
	*) usage; exit;;
	esac
done
shift $(($OPTIND - 1))

if test "$doprobe"
then
	title=${1##*/}; title=${title%.txt}
	grep -q '#include ' $1 && 
		{ section=${section:-3}; 
		  volume=${volume:-"$sys Programmer's Manual"}; } || 
		{ section=${section:-1}; 
		  volume=${volume:-"$sys Reference Manual"}; }
	# get release from path
	rel=$(pwd | sed 's:/.*[^0-9]/::g
	s:/.*::g')
fi

head=".TH $title $section \"$(date +'%B %d, %Y')\" \"$rel\" \"$volume\""

expand $* | 		# All tabs converted to spaces
awk -v head="$head" '	# gawk is needed because use of non standard regexp 
BEGIN {print head}
/^[A-Z_0-9][A-Z_0-9]/ {		# Section header
	if (in_bd == 1) {
		in_bd = 0
		print ".fam T\n.fi"
	}
	if (section == "SYNOPSIS") print ".fam T\n.fi"
	print ".SH " $0
	section = $0
	if (section == "SYNOPSIS") print ".nf\n.fam C"
	ls = 0		# line start index
	pls = 0		# previous line start index
	pnzls = 0	# previous non zero line start index
	ni = 0		# indent level
	ind[0] = 0	# indent offset table
	prevblankline = 0
	next
}
{	# compute line start index, handle start of example display block
	pls = ls
	if (ls != 0) pnzls = ls
	match($0, /[^ ]/)
	ls = RSTART
	if (pls == 0 && pnzls > 0 && ls > pnzls && $1 !~ /^[0-9\-\*\o]\.*$/) {
		# example display block
		if (prevblankline == 1) { print ".PP"; prevblankline = 0 }
		print ".nf\n.fam C"
		in_bd = 1
		eoff = ls
	}
	if (ls > 0 && ind[0] == 0) ind[0] = ls
}
in_bd == 1 {				# In example display block
	if (ls != 0 && ls < eoff) { 	# End of litteral display block
		in_bd = 0
		print ".fam T\n.fi"
	} else { print; next }
}
section == "NAME" { $1 = "\\fB" $1; sub(/ \- /, " \\fP- ") }
section == "SYNOPSIS" {			# Identify arguments of fcts and cmds
	if (index($0, "(") == 0 && index($0, ")") == 0) {	# Line is a command line
		b = $1
		sub(/^\*/, "", b)
		subwords["\\<" b "\\>"] = "\\fB" b "\\fP"
		for (i = 2; i <= NF; i++) {
			a = $i
			gsub(/[\[\]\|]/, "", a)
			if (a ~ /^[^\-]/) subwords["\\<" a "\\>"] = "\\fI" a "\\fP"
		}
	} else {			# Line is a C function definition
		for (i = 1; i <= NF; i++) 
			if ($i ~ /[\,\)]/) {
				a = $i
				sub(/.*\(/, "", a)
				gsub(/\W/, "", a)
				subwords["\\<" a "\\>"] = "\\fI" a "\\fP"
			}
	}
}
{
	for (i = 1; i <= NF; i++) {	# identify func calls and cross refs
		b = $i
		sub(/^\*/, "", b)
		if ((a = index(b, ")(")) > 0) {
			w = substr(b, 3, a - 3)
			subwords["\\<" w "\\>"] = "\\fI" w "\\fP"
		}
		if ((a = index(b, "(")) > 0) {
			w = substr(b, 1, a - 1)
			subwords["\\<" w "\\("] = "\\fB" w "\\fP("
		}
	}
	for (i in subwords) gsub(i, subwords[i])	# word attributes
	gsub(/\B\-+\w+(\-\w+)*/, "\\fB&\\fP")		# options

	if (match($0, /[^ ]  +/) > 0) {			# tag list item
		adjust_indent()
		tag = substr($0, 1, RSTART)
		sub(/^ */, "", tag)
		tail = substr($0, RSTART + RLENGTH)
		print ".TP\n.B"
		print tag
		$0 = tail
		prevblankline = 0
		if (NF == 0) next
	} else if ($1 == "-"||$1 == "o"||$1 == "*") {	# bullet list item
		adjust_indent()
		print ".IP \\(bu 3"
		prevblankline = 0
		$1 = ""
	} else if ($1 ~ /^[0-9]+[\).]$/) {		# enum list item
		adjust_indent()
		print ".IP " $1 " 4"
		prevblankline = 0
		$1 = ""
	} else if (pls == 0) {				# new paragraph
		adjust_indent()
	} else if (NF == 0) { 				# blank line
		prevblankline = 1; next 
	} else prevblankline = 0			# other
	if (prevblankline == 1) { 			# flush vertical space
		print ".PP"; prevblankline = 0 
	}
	sub(/ */,"")	# trim leading blank spaces
	print
}
function adjust_indent()
{
	if (ls > ind[ni]) { ind[++ni] = ls; print ".RS" }
	else if (ls < ind[ni])
		while (ls < ind[ni]) { ni--; print ".RE" }
}
' | eval $post
