jman (Source)

#!/bin/bash
##---------------------------------------------------------------------------##
#
#   Script: jman
#   Author: Brian <genius@groupbcl.ca> :)
#   Date:   June 2001 (as 'cman', renamed to 'jman' in July 2004)
#
#   jman produces a simple text-only version of a given man page, stripping
#   out backspaces and BS-underscore combinations, generates a table of
#   contents, and displays the page based on the value of JMAN_VIEWER.
#
#   Parameters:
#    -l spaces
#         Number of leading spaces to use for indexing. Default 7.
#    -w output_width
#         Output width. Default 72.
#    -m 
#         Generate output based only on the man page and ignore any info page
#    -f file
#         Generate output from file instead of searching the man pages
#
#   Hint: If you use "jman mail", it works best with "-l 5". Likewise,
#   "jman -l 8 perlfunc" will generate a useful index of all the perl
#   functions.
#
##---------------------------------------------------------------------------##
#
#   JMAN_VIEWER environment variable:
#   * If unset, defaults to the value of $EDITOR, $PAGER, or 'less', in that
#     order
#   * If set to 'vi' or 'vim' (directly or via $EDITOR), uses 'view' so that
#     the page file is read-only
#   * If set to 'save_to_file', saves the formatted page as a file in the
#     current directory (added February 2012)
#
##---------------------------------------------------------------------------##
# BUUS: This script is part of Brian's Useful Utilities Set

##---------------------------------------------------------------------------##
#   help: display help and exit
##---------------------------------------------------------------------------##
function help {
    echo "usage: jman [-l spaces] [-w output_width] [-m] [-f man_file]"
    echo 
    echo "Parameters:"
    echo " -l spaces"
    echo "      Number of leading spaces to use for indexing. Default 7."
    echo " -w output_width"
    echo "      Output width. Default 72."
    echo " -m "
    echo "      Generate output based only on the man page and ignore any info page"
    echo " -f file"
    echo "      Generate output from file instead of searching the man pages"
    echo
    echo "JMAN_VIEWER environment variable:"
    echo "* If unset, defaults to the value of \$EDITOR, \$PAGER, or 'less', in that"
    echo "  order"
    echo "* If set to 'vi' or 'vim' (directly or via \$EDITOR), uses 'view' so that"
    echo "  the page file is read-only"
    echo "* If set to 'save_to_file', saves the formatted page as a file in the"
    echo "  current directory"
    echo
    exit 0
}

##---------------------------------------------------------------------------##
#   GenInfoPage - generate useable output from 'info'
#   Returns with code 109 (ASCII "m", meaning "Try 'man' instead") if topic
#   is not found or info indicates it processed a manpage
##---------------------------------------------------------------------------##
function GenInfoPage {
    echo " * Generating info page"
    INFO_FN="$(locate "$MAN_COMMAND.info" | grep -v -- '-[0-9]')"
    if [ ! "$INFO_FN" ]; then RC=109; return; fi
    info --subnodes $INFO_FN 2>/dev/null | awk -v man_command=$MAN_COMMAND '
        BEGIN {
            text_idx=0  # For indexing text array
            toc_idx=0   # For indexing toc_text and toc_line arrays
        }

        #--- Bail immediately if this is a manpage ---
        /^File: \*manpages\*/ { exit 109 }  # 109 = ASCII "m" ("try man command instead")

        #--- Lines beginning with "File: " and "info: Writing node" are skipped
        /^(File: |info: Writing node)/{ next }

        #--- Push the current line on to the text array ---
        { text[text_idx++] = $0 }

        #--- Lines consisting of ***, ===, --- mark level 1, 2, and 3 headers ---
        /^(\*|=|-)+$/{
            toc_line[toc_idx] = text_idx-2
            if ($0 ~ /^\*/) { foofix = ""; level_1_count++ }
            if ($0 ~ /^=/) { foofix = "  " }
            if ($0 ~ /^-/) { foofix = "    " }
            toc_text[toc_idx] = foofix text[text_idx-2]
            toc_idx++
        }

        END {
            if (toc_idx > 0) {
                print toupper(man_command) " Documentation"
                print substr("**********************************************", 1, length(man_command)+14)
                print ""
                print "Table of Contents"
                print "================="
                for (i=0; i<toc_idx; i++) {
                    if (toc_text[i] !~ /^ / ) { print "" }
                    printf("    %5i. %s\n", toc_line[i]+toc_idx+level_1_count+6, toc_text[i])
                }
                for (i=0; i<text_idx; i++) { print text[i] }
            } else {
                exit 109    # No info page by this name, try "man"
            }
        }
    ' > $MAN_FN
    RC=$?
}

##---------------------------------------------------------------------------##
#   ProcManFile - processes a generated man page file
##---------------------------------------------------------------------------##
function ProcManFile {
    echo " * Processing file"

    #--- Get the manpage section from the output ---
    SECTION="$(head -n 20 $TEMP1_FN | awk '{
        if (match($0, /\(([[:alnum:]]+)\)[[:space:]]*$/, a)) { print a[1]; exit }
    }' $TEMP1_FN)"
    echo $SECTION
    [ -z "$SECTION" ] && SECTION="x"
    MAN_FN="${MAN_FN/.t/.${SECTION}.t}"

    #----- Do some formatting on the file -----
    # sed:
    #   Remove character-backspace pairs
    #   Change octal 255 characters to dashes
    # grep:
    #   Remove error messages generated by nroff
    # awk:
    #   Remove initial blank line
    #   Remove " |" at the end of lines (tcl man pages)
    #   Strip out multiple zero-length lines
    #
    sed "s/.${BS}//g" $TEMP1_FN | 
     grep -v -e "<standard input>:[0-9]*:" -e "^Reformatting " |
     awk -v SW=0 '
        length > 0 { SW=1 }
        SW { 
            if (substr($0,length,1)=="|") {
                i=length; x=0
                while (i>1) { c=substr($0,i,1); if (c!=" " && c!="|") {x=i;i=0}; i-- }
                if (x > 0) $0=substr($0,1,x)
            }
            print
        }
        length == 0 { SW = 0 }' >$TEMP2_FN
    mv $TEMP2_FN $TEMP1_FN

    #----- Generate a table of contents -----
    # tcl man pages (section n) have special processing for SubTOC lines; that is,
    # lines that describe actions for the main keyword (eg, keyword is "string",
    # actions are "length", "first", "range", "index", etc)
    echo " * Generating table of contents"
    echo -e "\nCONTENTS" >$TEMP2_FN
    echo "        Line  Section" >>$TEMP2_FN
    SUB_TOC=""
    [ "$SECTION" = n ] && SUB_TOC="-v SubTOC=$MAN_COMMAND -v SubTOC_Sw=0 "
    awk -v X=0 -v NameSw=0 -v KeywordLead=$KEYWORD_LEAD $SUB_TOC '
        BEGIN { KeywordSw = 0 }
        { LineNum++ }
        length > 1 {
            if (X++>2 && KeywordSw) { #(X is used to filter out the first two matching lines)
                LeadChars=substr($0,1,KeywordLead)
                Spaces=substr("                    ",1,KeywordLead)
                Ulines=substr("____________________",1,KeywordLead)
                if (LeadChars != Spaces && LeadChars != Ulines) {
                    if (length <= 60) {
                        printf("__TOC__ %4i  %s\n",NR,$0)
                    } else {
                        KeywordSw=0
                    }
                }   
            }
            if (SubTOC != "") {
                # Throw away all references until DESCRIPTION is encountered
                if ($1=="DESCRIPTION") SubTOC_Sw=1
                # SubTOC lines can span two lines (argh!), so look to see if I have
                # to add to the line or can output it now
                if (SubTOC_Line != "") {
                    if (substr($0,8,1)==" ") {
                        if (length(SubTOC_Line) < 100) print SubTOC_Line
                        SubTOC_Line=""
                    } else {
                        SubTOC_Line=SubTOC_Line " " substr($0,8)
                    }
                } else {
                    # Start a SubTOC line if the topic word is found in column 8
                    if (substr($0,8,length(SubTOC)+1) == SubTOC " ") {
                        SubTOC_Line=sprintf("__TOC__ %4i  %s",NR,substr($0,8))
                        if (! SubTOC_Sw) SubTOC_Line=""
                    }
                }
            }
        }
        /^ *$/{ KeywordSw=1 }
    ' $TEMP1_FN >>$TEMP2_FN

    #----- Update the line numbers to account for the index itself -----
    awk -v Offset=$(echo $(cat $TEMP2_FN|wc -l)) '{
        if ($1 == "__TOC__") $0=sprintf("       %4i %s",$2+Offset,substr($0,14))
        print
    }' $TEMP2_FN >$CONTENTS_FN

    #----- Find the end of the NAME section in the manpage file -----
    HEAD_COUNT="$(head -n 20 $TEMP1_FN | awk '
        /^NAME|^Name/{NameSw=1}
        length == 0 { if (NameSw) print NR-1; NameSw=0 }
    ')"

    #----- Insert the index file into the manpage file -----
    head -n $HEAD_COUNT $TEMP1_FN >$MAN_FN
    cat $CONTENTS_FN >>$MAN_FN
    tail -n +$[HEAD_COUNT+1] $TEMP1_FN >>$MAN_FN

    rm $TEMP1_FN $TEMP2_FN $CONTENTS_FN
}

##---------------------------------------------------------------------------##
#           M A I N   P R O C E S S I N G
##---------------------------------------------------------------------------##
[ "$1" == '-h' -o "$1" == '--help' ] && help

[ -d /tmp ] && TEMP_DN='/tmp' || TEMP_DN='/tmp'
JMAN_DN="$TEMP_DN/jman"
[ -d $JMAN_DN ] || mkdir $JMAN_DN
TEMP1_FN=$TEMP_DN/jman.$$.1
TEMP2_FN=$TEMP_DN/jman.$$.2
CONTENTS_FN=$TEMP_DN/jman.$$.contents
BS=$'\010'
EMDASH=$'\255'
# export MANWIDTH=$[$(stty -a|head -n 1|cut -f7 -d" "|cut -f1 -d";") + 8]
export MANWIDTH=72
KEYWORD_LEAD=7  # Number of blanks before a keyword in the manpage

if [ -z "$1" ]
then
    echo "usage: jman [-l keyword_lead] [-w output_width] [section] manpage"
    exit 1
fi


#--- Parse the command line parameters -----
ERROR_SW=false
INFO_SW=true
for PARM in $*
do
    if [ "$(expr substr "$PARM" 1 1)" = "-" ]
    then
        case "$(expr substr "$PARM" 2 1)" in
         f) NEXT_PARM='FILENAME';;
         l) NEXT_PARM="KEYWORD_LEAD";;
         m) INFO_SW=false;;
         w) NEXT_PARM="MANWIDTH";;
         *) echo "jman: $PARM: unknown switch" >&2; ERROR_SW=true;;
        esac
    elif [ "$NEXT_PARM" ]
    then
        [ $NEXT_PARM = "FILENAME" ] && FILENAME="$PARM"
        [ $NEXT_PARM = "KEYWORD_LEAD" ] && KEYWORD_LEAD=$PARM
        [ $NEXT_PARM = "MANWIDTH" ] && MANWIDTH=$PARM
        NEXT_PARM=""
    elif [ -z "$WORD_1" ]
    then
        WORD_1=$PARM
    elif [ -z "$WORD_2" ]
    then
        WORD_2=$PARM
    else
        echo "jman: $PARM: bad parameter" >&2
        ERROR_SW=true
    fi
done

# If we have only WORD_1, it's the command the user wants the man page for
[ -z "$WORD_2" ] && MAN_COMMAND=$WORD_1

# If we have WORD_2 and WORD_2, it's section and man page
if [ -n "$WORD_1" -a -n "$WORD_2" ]
then
    MAN_SECTION=$WORD_1
    MAN_COMMAND=$WORD_2
    INFO_SW=false
fi

if [ "$FILENAME" ]
then
    MAN_SECTION='-l'
    MAN_COMMAND="$FILENAME"
fi

#echo "WORD_1=$WORD_1"
#echo "WORD_2=$WORD_2"
#echo "MAN_SECTION=$MAN_SECTION"
#echo "MAN_COMMAND=$MAN_COMMAND"
#echo "MANWIDTH=$MANWIDTH"
#echo "KEYWORD_LEAD=$KEYWORD_LEAD"
#pause

#--- Figure out if the user has his own doc, man, or tmp/temp dirs ---
for DIR in temp tmp doc man
do
    [ -d "$HOME/$DIR" ] && HPATH="$HOME/$DIR"
done

# (The man section is figured out in ProcManFile and is added there)
MAN_FN="${JMAN_DN}/${MAN_COMMAND}.text"

#--- Check to see if we have an 'info' page on this topic ---
if $INFO_SW
    then GenInfoPage            # Returns '109' if we have a man page
    else RC=109
fi

#--- Generate the man page ---
if [ $RC = 109 ]
then
    echo " * Generating man page file"
    rm -f $MAN_FN       # Left over from GenInfoPage
    if man $MAN_SECTION $MAN_COMMAND &>$TEMP1_FN
    then
        ProcManFile
    else
        cat $TEMP1_FN
        rm -f $TEMP1_FN
    fi
fi

#--- View the resulting file, if we have one ---
if [ -f $MAN_FN ]
then
    if [ -z "$JMAN_VIEWER" ]
    then
        JMAN_VIEWER=$EDITOR
        [ -z "$JMAN_VIEWER" ] && JMAN_VIEWER=$PAGER
        [ -z "$JMAN_VIEWER" ] && JMAN_VIEWER='less'
    fi

    if [ "$JMAN_VIEWER" = vim -o "$JMAN_VIEWER" = vi ]
    then
        JMAN_VIEWER="view -c 'syntax off'"
    fi

    # Hack for cygwin: change \ to / in JMAN_VIEWER
    if [ "$OSTYPE" = cygwin ]
    then
        JMAN_VIEWER=${JMAN_VIEWER//\\/\/}
        MAN_FN="C:/cygwin$MAN_FN"
        "$JMAN_VIEWER" $MAN_FN
    elif [ "$JMAN_VIEWER" = 'save_to_file' ]
    then
        SAVE_FN="$(basename $MAN_FN)"; SAVE_FN="${SAVE_FN/__/}"
        echo " * Saving formatted man page to $SAVE_FN"
        mv "$MAN_FN" "$SAVE_FN"
    else
        $SHELL -c "$JMAN_VIEWER $MAN_FN"
    fi
fi

#----- Ask about keeping the output file (default is to delete it) -----
#if [ -f $MAN_FN ]
#then
#   echo -en "Do you want to keep \"$MAN_FN\" (y/n)? n\010"; read YES_NO
#   if [ "$YES_NO" != "y" ]
#   then
#       rm $MAN_FN
#       echo "Deleted"
#   else
#       echo "Kept"
#   fi
#fi
echo "Formatted man page is in $MAN_FN"

# vim: tabstop=4