#!/usr/bin/ksh93

########################################################################
#                                                                      #
#               This software is part of the ast package               #
#                 Copyright (c) 2007-2012 Roland Mainz                 #
#                      and is licensed under the                       #
#                 Eclipse Public License, Version 1.0                  #
#                    by AT&T Intellectual Property                     #
#                                                                      #
#                A copy of the License is available at                 #
#          http://www.eclipse.org/org/documents/epl-v10.html           #
#         (with md5 checksum b35adb5213ca9657e911e9befb180842)         #
#                                                                      #
#                                                                      #
#                 Roland Mainz <roland.mainz@nrubsig.org>              #
#                                                                      #
########################################################################

#
# Copyright (c) 2007, 2013, Roland Mainz. All rights reserved.
#

# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
export PATH='/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin'

# Make sure all math stuff runs in the "C" locale to avoid problems
# with alternative # radix point representations (e.g. ',' instead of
# '.' in de_DE.*-locales). This needs to be set _before_ any
# floating-point constants are defined in this script).
if [[ "${LC_ALL-}" != '' ]] ; then
	export \
		LC_MONETARY="${LC_ALL}" \
		LC_MESSAGES="${LC_ALL}" \
		LC_COLLATE="${LC_ALL}" \
		LC_CTYPE="${LC_ALL}"
		unset LC_ALL
fi
export LC_NUMERIC='C'


function debug_print
{
	# don't use "--" here to allow "-f" for formatting
#	print -u2 "$@"
	return 0
}

# create a compound variable list of _all_ manual page directories,
# calculated from MANPATH and locale which contain...
# "manpath_element" - the MANPATH element this entry belongs to
# "dir"             - physical directory of "manpath_element"
# "sect"            - section (if "manpath_element" is something like /usr/share/man,1b)
# ... and put the result in the array named by argv[1]
function enumerate_mandirs
{
	nameref md=$1
	typeset manpath_element dir sect manlang
    
	if [[ "${LC_MESSAGES-}" != '' ]] ; then
		manlang="${LC_MESSAGES}"
	else
		manlang="${LANG-}"
	fi

	print -r -- "${MANPATH//:/$'\n'}" | while read manpath_element ; do
		# strip section from manpath elements like "/usr/share/man,1b"
		dir="${manpath_element/~(E)(.*),(.*)/\1}"
		sect="${manpath_element/~(E)(.*),(.*)/\2}"
		[[ "${sect}" == "${dir}" ]] && sect=''
		
		if [[ "${manlang}" != '' && -d "${dir}/${manlang}" ]] ; then
			md+=(
				manpath_element="${manpath_element}"
				dir="${dir}/${manlang}"
				sect="${sect}"
			)
		fi
		if [[ -d "${dir}" ]] ; then
			md+=(
				manpath_element="${manpath_element}"
				dir="${dir}"
				sect="${sect}"
			)
		fi
	done
    
	return 0
}

function enumerate_mansects
{
	nameref ms=$1
	nameref mandir_node=$2
	typeset mancf="${mandir_node.dir}/man.cf"
	typeset x s l
	
	if [[ "${mandir_node.sect}" != '' ]] ; then
		x="${mandir_node.sect}"
	elif [[ -v MANSECTS ]] ; then
		x="${MANSECTS//,/$'\n'}"
	elif [[ -f "${mancf}" && -r "${mancf}" ]] ; then
		x="$(egrep -v '^#|^[[:space:]]*$' <"${mancf}" | egrep '^MANSECTS=')"
		x="${x/MANSECTS=}/"
		x="${x//,/$'\n'}"
	else
		x="$(
			cd "${mandir_node.dir}" ;
			set +o noglob ;
			ls -1d ~(El)(sman|man).*/ |
			while read s ; do
				s="${s/~(El)(sman|man)/}" ;
				s="${s/~(Er)\//}" ;
				print -r -- "$s"
			done
		)"
	fi
    
	while read l ; do
		[[ "${l}" != ~(Elr)[[:blank:]]* ]] && ms+=( "${l}" )
#		print -- "sect=$l"
	done <<<"${x}"
    
#	printf "enumerate_mansects: found %d entries.\n" ${#ms[@]}

	return 0
}

# wrapper around more/less
function browse_manpage
{
	typeset tmpdirname
	typeset doc_filename="$1"
	typeset doc_title="$2"
    
	# squish characters in filename which are not allowed in a filesystem
	# (currently '/')
	doc_title="${doc_title//\//}"

	# check if we have "less" installed, if not fall back to /usr/xpg4/bin/more
	if which 'less' >'/dev/null' 2>&1 ; then
		# use "cat" here to avoid that "less" may try funny things
		cat <"${doc_filename}" | less -I -M $"--prompt=MManual\ page\ ""${doc_title}"$"\ ?ltline\ %lt?L/%L.:"
	else    
		tmpdirname="$(mktemp -t -d "shman_${PPID}_$$_XXXXXX")"

		mkdir -p "${tmpdirname}" || { print -u2 -f $"Couldn't create tmp. dir %s\n" "${tmpdirname}" ; return 1 ; }

		(
			cd "${tmpdirname}"

			# note: we need to support /dev/stdin
			cat <"${doc_filename}" >"./${doc_title}"

			/usr/xpg4/bin/more "${doc_title}"

			rm -f "${doc_title}"
		)

		rmdir "${tmpdirname}"
	fi
    
	return 0
}

# /usr/bin/man <keyword>
function show_manpage
{
	compound -a mandirs
	integer i
	integer j
	nameref manname="$1"

	enumerate_mandirs mandirs
#	debug_print -- "${mandirs[@]}"

	integer num_mandirs=${#mandirs[@]}

	for (( i=0 ; i < num_mandirs ; i++ )) ; do
		typeset mandir="${mandirs[i].dir}"
	
		typeset -a mansects
		enumerate_mansects mansects "mandirs[$i]"

		integer num_mansects="${#mansects[@]}"
#		debug_print -- "mansects=${mansects[@]}"

		for (( j=0 ; j < num_mansects ; j++ )) ; do
			typeset mansect="${mansects[j]}"
			
			[[ -v manname.sect && "${manname.sect}" != "${mansect}" ]] && continue

			# try 1: SGML manpage
			typeset match="${mandir}/sman${mansect}/${manname.name}.${mansect}"
			if [[ -r "${match}" ]] ; then
				typeset note nlink
		
				# follow SGML links if needed (needs rework, including protection against link loops)
				while true ; do
					debug_print -f "match: %s\n" "${match}"
		    
					tmp="$(cd "${mandir}" ; LC_MESSAGES=C /usr/lib/sgml/sgml2roff "${match}")"
					read note nlink <<<"${tmp}"
		
					if [[ "${note}" == ".so" ]] ; then
						match="${nlink}"
					else
						break
					fi
				done
		
				tbl <<<"${tmp}" | eqn | nroff -u0 -Tlp -man - | col -x | browse_manpage /dev/stdin "${manname.name}(${mansect})"
				return 0
			fi

			# try 2: troff manpage
			match="${mandir}/man${mansect}/${manname.name}.${mansect}"
			if [[ -r "${match}" ]] ; then
				debug_print -f "match: %s\n" "${match}"
				tbl <"${match}" | eqn | nroff -u0 -Tlp -man - | col -x | browse_manpage /dev/stdin "${manname.name}(${mansect})"
				return 0
			fi
		done
		unset mansects num_mansects
	done
    
	printf $"No manual entry for %s.\n" "${manname.name}"
	return 0
}

# /usr/bin/man -l <keyword>
function list_manpages
{
	nameref manname=$1
	compound -a mandirs
	integer i
	integer j

	enumerate_mandirs mandirs
	#debug_print -- "${mandirs[@]}"

	integer num_mandirs=${#mandirs[@]}

	for (( i=0 ; i < num_mandirs ; i++ )) ; do
		typeset mandir="${mandirs[i].dir}"

		typeset -a mansects
		enumerate_mansects mansects "mandirs[$i]"

		integer num_mansects="${#mansects[@]}"
#		debug_print -- "mansects=${mansects[@]}"

		for (( j=0 ; j < num_mansects ; j++ )) ; do
			mansect="${mansects[j]}"

			[[ -v manname.sect && "${manname.sect}" != "${mansect}" ]] && continue

			# try 1: SGML manpage
			match="${mandir}/sman${mansect}/${manname.name}.${mansect}"
			if [[ -r "${match}" ]] ; then
				printf '%s (%s)\t-M %s\n' "${manname.name}" "${mansect}" "${mandir}"
				continue
			fi

			# try 2: troff manpage
			match="${mandir}/man${mansect}/${manname.name}.${mansect}"
			if [[ -r "${match}" ]] ; then
				printf '%s (%s)\t-M %s\n' "${manname.name}" "${mansect}" "${mandir}"
				continue
			fi
		done
		unset mansects num_mansects
	done

	return 0
}

# /usr/bin/appropos
function list_keywords
{
	nameref manname=$1
	typeset -a mandirs
	typeset name namesec title
	integer i

	enumerate_mandirs mandirs
	#debug_print -- "${mandirs[@]}"

	integer num_mandirs=${#mandirs[@]}

	for (( i=0 ; i < num_mandirs ; i++ )) ; do
		typeset mandir="${mandirs[i].dir}"
		typeset windexfile="${mandir}/windex"
	
		if [[ ! -r "${windexfile}" ]] ; then
			print -u2 -f $"%s: Can't open %s.\n" "${progname}" "${windexfile}"
			continue
		fi
	
	        # fixme: What should we do about manname.sect ?
		while IFS=$'\t' read name namesec title ; do
			if [[ "${name}${namesec}${title}" == ~(Fi)${manname.name} ]] ; then
				printf "%s\t%s\t%s\n" "${name}" "${namesec}" "${title}"
			fi
		done <"${windexfile}"
	done
    
	return 0
}

function usage
{
	OPTIND=0
	getopts -a "${progname}" "${man_usage}" OPT '-?'
	exit 2
}

# parse man name, return name and section pattern
function parse_manname
{
	typeset str="$2"
	nameref manname=$1
	typeset name
	typeset sect
	
	name="${str/~(E)(.*)\.(.*)/\1}"
	sect="${str/~(E)(.*)\.(.*)/\2}"

	typeset manname.name="${name}"
	if [[ "${sect}" != "${name}" && "${sect}" != '' ]] ; then
		typeset manname.sect="${sect}"
	fi
	
	return 0
}

function main
{
	bool do_list=false
	bool do_keyword=false
	compound manname
	
	while getopts -a "${progname}" "${man_usage}" OPT ; do 
		case "${OPT}" in
			'M')	MANPATH="${OPTARG}"	;;
			'l')	do_list=true		;;
			'k')	do_keyword=true		;;
			's')	MANSECTS="${OPTARG}" 	;;
			*)	usage			;;
		esac
	done
	shift $(( OPTIND-1 ))
	
	# cd /usr/man; LC_MESSAGES=C /usr/lib/sgml/sgml2roff /usr/man/sman1as/asadmin-list-timers.1as  | tbl | eqn | nroff -u0 -Tlp -man -  | col -x > /tmp/mpLQaqac
	
	# prechecks
	(( $# > 0 )) || usage
	
	# process arguments
	while (( $# > 0 )) ; do
		parse_manname manname "$1"
		shift
	
		debug_print -f "# searching for %s ...\n" "${manname.name}"
	
		if (( do_keyword )) ; then
			list_keywords manname
		elif (( do_list )) ; then
			list_manpages manname
		else
			show_manpage manname
		fi
	done
	
	# todo: better exit codes
	return 0
}

# program start
builtin basename
builtin cat
builtin date
builtin mkdir
builtin rmdir

set -o noglob
set -o nounset

typeset progname="$(basename "${0}")"

typeset -r man_usage=$'+
[-?\n@(#)\$Id: shman (Roland Mainz) 2013-05-12 \$\n]
[-author?Roland Mainz <roland.mainz@nrubsig.org>]
[+NAME?man - find and display reference manual pages]
[+DESCRIPTION?The man command displays information from the reference
	manuals. It displays complete manual pages that you select
	by name, or one-line summaries selected  either  by  keyword
	(-k), or by the name of an associated file (-f). If no
	manual page is located, man prints an error message.]
[+?write me.]
[k:keyword?Prints out one-line summaries from the windex database (table of contents) that
	contain any of the given  keywords. The windex database is created using
	catman(1M).]
[l:list?Lists all manual  pages  found  matching name within the search path.]
[M:mpath?Specifies an alternate search  path  for manual  pages. path is a colon-separated
	list of directories that contain  manual page directory subtrees. For example, if
	path  is  /usr/share/man:/usr/local/man, man  searches  for  name in the standard
	location, and then /usr/local/man.  When used  with  the -k or -f options, the -M
	option must appear first. Each directory in  the  path is assumed to contain subdirectories of the form man* or sman*  ,
	one  for each section. This option overrides the MANPATH environment variable.]:[path]
[s:section?Specifies sections of the manual for man to search. The directories searched for
	name are limited to those specified by section. section can be a numerical
	digit, perhaps followed by one or more letters to match the desired section of
	the manual, for example,  "3libucb". Also, section can be a word, for example,
	local, new, old, public. section can also be a letter.
	To specify multiple sections, separate each section with
	a comma. This option overrides the MANPATH environment variable and the man.cf
	file.
	See Search Path below for an explanation of how man conducts its search.]:[section]
	
name

[+SEE ALSO?\bksh93\b(1), \bman\b(1)]
'

main "$@"
exit $?

# EOF.
