#!/bin/sh
#
# add_parens_for_own_funcs.sh
#
# This script is designed to fix inconsistencies in the use of
# parentheses after function names in the manual pages.
# It changes manual pages to add these parentheses.
# The problem is how to determine what is a "function name".
# The approach this script takes is the following:
#
#   For each manual page named in the command line that contains
#           more than one line (i.e., skip man-page link files)
#       Create a set of names taken from the .SH section of the
#               page and from grepping all pages for names that
#               have .so links to this page
#       For each name obtained above
#           If we can find something that looks like a prototype on
#                   the page, then
#               Try to substitute instances of that name on the page.
#                   (instances are considered to be words formatted
#		    using ^.[BI] or \f[BI]...\f[PR] -- this script
#		    ignores unformatted instances of function names.)
#           fi
#       done
#   done
#
# The rationale of the above is that the most likely function names
# that appear on a page are those that the manual page is describing.
# It doesn't fix everything, but it catches many instances.
# The rest will have to be done manually.
#
# This script is rather verbose because it provides a computer-assisted
# solution, rather than one that is fully automated.  When running it,
# pipe the output through
#
#            ...  2>&1 | less
#
# and take a good look at the output.  In particular, you can scan
# the output for *possible* problems by looking for the pattern: /^%%%/
# The script's output should be enough to help you determine if the
# problem is real or not.
#
# Suggested usage (in this case to fix pages in Section 2):
#
#     cd man2
#     sh add_parens_for_own_funcs.sh *.2 2>&1 | tee changes.log | less
#
# Use the "-n" option for a dry run, in order to see what would be
# done, without actually doing it.
#
# (And, yes, there are many ways that this script could probably be
# made to work faster...)
#
######################################################################
#
# (C) Copyright 2005 & 2013, Michael Kerrisk
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details
# (http://www.gnu.org/licenses/gpl-2.0.html).
#
#
#

file_base="tmp.$(basename $0)"

work_dst_file="$file_base.dst"
work_src_file="$file_base.src"

matches_for_all_names="$file_base.all_match"
matches_for_this_name="$file_base.this_match"

all_files="$work_dst_file $work_src_file $matches_for_all_names \
	   $matches_for_this_name"

rm -f $all_files

# Command-line option processing

really_do_it=1
while getopts "n" optname; do
    case "$optname" in
    n)	really_do_it=0;
    	;;
    *)  echo "Unknown option: $OPTARG"
        exit 1
	;;
    esac
done

shift $(( $OPTIND - 1 ))

# Only process files with > 1 line -- single-line files are link files

for page in $(wc "$@" 2> /dev/null | awk '$1 > 1 {print $4}'| \
    grep -v '^total'); do

    echo ">>>>>>>>>>>>>>>>>>>>>>>>>" $page "<<<<<<<<<<<<<<<<<<<<<<<<<"
    echo ">>>>>>>>>>>>>>>>>>>>>>>>>" $page "<<<<<<<<<<<<<<<<<<<<<<<<<" 1>&2

    # Extract names that follow the ".SH NAME" directive -- these will
    # be our guesses about function names to look for

    sh_nlist=$(cat $page | \
        awk 'BEGIN { p = 0 }
             /^\.SH NAME/     { p = NR }
	     /^.SH/ && NR > p { p = 0 }	    # Stop at the next .SH directive
	     p > 0 && NR > p  { print $0 }  # These are the lines between
					    # the two .SH directives
	    ')
    sh_nlist=$(echo $sh_nlist | sed -e 's/ *\\-.*//' -e 's/, */ /g')
    echo "### .SH name list:" $sh_nlist

    # Some pages like msgop.2 don't actually list the function names in
    # the .SH section -- but we can try using link pages to give us
    # another guess at the right function names to look for

    so_nlist=$(grep -l "^\\.so.*/$(echo $page| \
	     sed -e 's/\.[1-8]$//')\\." $* | \
	     sed -e 's/\.[1-8]$//g')

    echo "### .so name list:" $so_nlist

    # Combine the two lists, eliminate duplicates

    nlist=$(echo $sh_nlist $so_nlist | tr ' ' '\012' | sort -u)

    maybechanged=0

    cp $page $work_dst_file
    rm -f $matches_for_all_names; # touch $matches_for_all_names

    for rname in $nlist; do	# try each name from out list for this page

        # A very few names in .SH sections contain regexp characters!

	name=$(echo $rname | sed -e 's/\*/\\*/g' -e 's/\./\\./g' \
		-e 's/\[/\\[/g' -e 's/\+/\\+/g')

        echo "########## trying $rname ##########"

	rm -f $matches_for_this_name

        grep "^.BR* $name *$" $page | \
	    >> $matches_for_this_name
        grep "^.BR $name [^(\"]$" $page | \
	    >> $matches_for_this_name
        grep '\\fB'"$name"'\\f[PR][ .,;:]' $page | \
	    >> $matches_for_this_name
        grep '\\fB'"$name"'\\f[PR]$' $page | \
	    >> $matches_for_this_name

	cat $matches_for_this_name | sed -e 's/^/### MATCH: /'
	cat $matches_for_this_name >> $matches_for_all_names

	# Only process a page if we can see something that looks
	# like a function prototype for this name in the page

        if grep -q "$name *(" $page || \
	    grep -q "$name\\\\f.[\\ ]*(" $page; then

	    # '.B name$'
	    # '.BR name [^("]*$
	    # (The use of [^"] in the above eliminates lines
	    # like: .BR func " and " func
	    # Those lines better be done manually.)
	    cp $work_dst_file $work_src_file
            cat $work_src_file | \
		sed \
		-e "s/^.BR* $name *\$/.BR $name ()/" \
		-e "/^.BR *$name [^(\"]*\$/s/^.BR *$name /.BR $name ()/" \
		> $work_dst_file

	    # '\fBname\fP[ .,;:]'
	    # '\fBname\fP$'
	    cp $work_dst_file $work_src_file
            cat $work_src_file | \
		sed \
		-e 's/\\fB'$name'\\fP /\\fB'$name'\\fP() /g' \
		-e 's/\\fB'$name'\\fP\./\\fB'$name'\\fP()./g' \
		-e 's/\\fB'$name'\\fP,/\\fB'$name'\\fP(),/g' \
		-e 's/\\fB'$name'\\fP;/\\fB'$name'\\fP();/g' \
		-e 's/\\fB'$name'\\fP:/\\fB'$name'\\fP():/g' \
		-e 's/\\fB'$name'\\fP$/\\fB'$name'\\fP()/g' \
		> $work_dst_file

	    # '\fBname\fR[ .,;:]'
	    # '\fBname\fR$'
	    cp $work_dst_file $work_src_file
            cat $work_src_file | \
		sed \
		-e 's/\\fB'$name'\\fR /\\fB'$name'\\fR() /g' \
		-e 's/\\fB'$name'\\fR\./\\fB'$name'\\fR()./g' \
		-e 's/\\fB'$name'\\fR,/\\fB'$name'\\fR(),/g' \
		-e 's/\\fB'$name'\\fR;/\\fB'$name'\\fR();/g' \
		-e 's/\\fB'$name'\\fR:/\\fB'$name'\\fR():/g' \
		-e 's/\\fB'$name'\\fR$/\\fB'$name'\\fR()/g' \
		> $work_dst_file

	    maybechanged=1
        else
            echo "%%%%%%%%%% WARNING: NO PROTOTYPE MATCHES FOR: $name"
        fi
    done

    # If the file was changed, then:
    # show "diff -U" output to user;
    # and count number of changed lines and compare it with what
    # we expected, displaying a warning if it wasn't what was expected

    if test $maybechanged -ne 0 && ! cmp -s $page $work_dst_file; then
        diff -u $page $work_dst_file

        made_matches=$(diff -U 0 $page $work_dst_file | grep '^\+[^+]' | \
		wc -l | awk '{print $1}')

	# The following line makes the changes -- comment it out if you
        # just want to do a dry run to see what changes would be made.

	if test $really_do_it -ne 0; then
            cat $work_dst_file > $page
	fi

    else
        echo "### NOTHING CHANGED"
	made_matches=0
    fi

    min_match=$(cat $matches_for_all_names | \
	    sort -u | wc -l | awk '{print $1}')

    echo "### Expected matches >= $min_match"
    echo "### Made matches $made_matches"

    if test $made_matches -lt $min_match; then
        echo "%%%%%%%%%% WARNING: NOT ENOUGH MATCHES: " \
	    "$made_matches < $min_match"
    fi

done

# clean up

rm -f $all_files
exit 0
