#!/usr/bin/env bash
# set -vx
## __________________________________________________________________
## Universal Help Bash Scripting  
## interhacktives Scripting Tutorial    
## 
## ___________________________________________________________________
## copyright by karl.thomas.schmidt@googlemail.com 2012-2020
## copyright by giff@frankenlinux.de 2019-2020 yz 
## http://www.interhacktive.de     
##___________________________________________________________________
##


man_result="dummy"
help_result="dummy"
cmd=
long_options=()
short_options=()
man_or_help_cmd=

formatdescriptors_only="False"
green=$(tput setaf 2)
fett=$(tput bold)
underline=$(tput smul)
reset=$(tput sgr0)

# FUNCTION usage
# 
# Zeigt die Kurzhilfe an
# Wird auch angezeigt, wenn der User nicht die vorgeschriebenen Argumente setzt.
#

usage(){
    printf "\n  ${fett}mano${reset} (== MAN Options) ist die schnelle Suche nach Optionen eines Kommandos.\n"
    printf "  Sucht eine oder mehrere Optionen eines Kommandos in der Manpage und\n"
    printf "  gibt den jeweiligen Hilfetext aus.\n"
    printf "  Derzeit funktioniert das Script (noch) nicht bei Befehlen wie \"zypper\" oder \"find\".\n"
    printf "  \n"
    printf "\n${fett}  $(basename $0) ${underline}<command> <option>${reset}..."
    printf "\n${fett}  $(basename $0) ${underline}<command> %%${reset}\n"
    printf "${fett}  $(basename $0) --help${reset}\n"
    printf "\n\n"
    printf "      %-20s Gibt die Kurzhilfe aus und beendet das Script $(basename $0)\n" '--help'
    printf "                           Es müssen immer mindestens zwei Argumente angegeben werden. \n\n"  
    printf "      %-20s Gib ein gängiges Kommando ein. \n" ${underline}'<command>'${reset}
    printf "      %-20s Kurze Option, auch kombiniert mit mehreren. (-a -l -al) \n" ${underline}'<option>'${reset}
    printf "                 Auch lange Optionen sind erlaubt. (--almost-all) \n"  
    printf "      %-20s nützlich für Befehle wie \"date\" um eine Liste aller Formatdeskriptoren anzuzeigen.\n" ${underline}'%'${reset} 
    printf "                 Probiere z.B. \"mano date %%\" \n\n"
}


# FUNCTION get_short_option
#
# Ruft "man $1 $2" auf und und gibt mittels sed die Option $2 aus.
# Parameter 1: Ruft den Namen des zu prüfenden Befehls auf
# Parameter 2: Die Option(en), die gesucht und ausgedruckt werden.
#
get_short_option(){
    ${man_or_help_cmd} $cmd | sed -nr '/^([[:blank:]]+-.,)*[[:blank:]]+'"$option"'/,/^$/{/^$/d;p}'
}


get_long_option(){
    ${man_or_help_cmd} $cmd | sed -nr '/[[:blank:]]+'$option'/,/^$/{/^$/d;p}'
}


man_or_help_function(){
    help $cmd >/dev/null  2> /dev/null
    help_result=$?
    man $cmd >/dev/null 2> /dev/null
    man_result=$?
    if [[ "$help_result" == 0 && $man_result > 0 ]]; then
            printf "           %-20s ist ein internes Kommando.\n" ${fett}${green}"${cmd}"${reset}    
    elif [[ "$help_result" > 0 && $man_result == 0 ]]; then
            printf "           %-20s ist ein externes Kommando.\n" ${fett}${green}"${cmd}"${reset}       
    elif [[ "$help_result" == 0 && $man_result == 0 ]]; then
            printf "           %-20s ist sowohl ein externes, als auch ein internes Kommando.\n" ${fett}${green}"${cmd}"${reset}    
            printf "                 Es werden die Optionen des internen Kommandos ausgegeben.\n"
    fi
}

###### SCRIPT BEGINNT HIER ######

    # Wenn nicht mindestens 2 Argumente angegeben werden:
if [[ $# < 2 ]]; then
    # Zeigt die Kurzhilfe an
	usage
    # Und verlässt das Skript mit einer Fehlermeldung. (1)
	exit 1
fi

    # Prüfung aller angegebenen Argumente
while (( $# > 0  )); do
    # Wir prüfen immer das erste Argument, handeln entsprechend und
    # shiften am Ende das bearbeitete Argument beiseite.
	case $1 in
        # Wir prüfen bei jedem "Case- Durchlauf" immer  $1 .
        
        # Wir starten mit den langen Optionen
		--* ) # Wenn $1 eine lange Option ist (also mit zwei Minuszeichen
            # beginnt), speichern wir diese lange Option ohne die Minuszeichen
            # in ein Array. Mehrere Optionen können angegeben werden.
            # Dem Array "long_options" wird +=( ... ) etwas hinzugefügt.
            # Nämlich das Ergebnis der Expansion von $1. 
            # ${variablenname:von Position:wieviele Zeichen von da ab}
            # probiere folgende Zeile:
            # var=--irgendwas ; echo ${var:2:4}
		    long_options+=( "${1:2}" ) 
            # Es wird also dem Array ein Eintrag hinzugefügt, der alle Zeichen 
            # enthält vom dritten Zeichen bis zum Ende von $1 
            # Jetzt wird diese Zeichenkette "weggeshiftet"
            shift
			;;
        -*) # dasselbe machen wir mit den kurzen Optionen auch
            # Der einzige Unterschied ist, dass wir jetzt nur von Position 1
            # (also Zeichen Nummer 2) ab in das Array schreiben. 
            # dazu zerlegen wir kombinierte Short Options, wie -alv in einzelne 
            # Zeichen. Und fügen dem Array short_options jeweils einen Eintrag hinzu.
            cur_option="${1:1}" # die Temp-Variable cur_option enthält jetzt die 
                                # möglicherweise kombinierte kurze Option ohne das - Zeichen
            i=0                 # wir initalisieren eine Zählvariable
            while (( i< ${#cur_option}  )) ;   do # die Schleife wird für jede einzelne Short-Option aufgerufen
                # Dem Array short_options fügen wir += ( ein Zeichen von Stelle i in der Länge 1 hinzu.)
		        short_options+=( ${cur_option:i:1} ) # verwirft nur das führende " - ".
                ((i++)) # und natürlich müssen wir unseren Zähler erhöhen.
            done
            # Natürlich shiften wir nun auch das weg
			shift
			;;
        %   ) # Hier lassen wir uns nur die Formatdescriptoren anzeigen, wie zum
             # Beispiel (date +"%YY %m %d" )
            formatdescriptors_only="True"
            shift
            ;;
	
        # Das erste Argument beim Aufruf muss ein ausführbares Kommando sein.
        # Also wird der folgende Case als erstes zutreffen und ausgeführt werden.
        # Damit aber die oberen Cases ausgeführt werden, muss dieser Case als Letztes stehen.
        # Denn dieser Case mit dem Wildcard * würde ja auch auf alle vorherigen Cases zutreffen.
        # Case/esac versteht keine RegExes, sondern nur die Pathname-
        # Expansion (das "Globbing") der Bash.
        # Deshalb müssen wir diesen Case etwas umständlicher behandeln.
        # Wir müssen ja ausschliessen, dass dieser Case dann doch für andere Optionen
        # ausgeführt wird. (Auch wenn das logisch gesehen nicht eintreffen sollte.
        # Nur wer garantiert uns, dass der User nicht eine extra schräge Kommandozeile reinwürgt?
       
		*   )
            # Wenn das erste Argument ein oder mehrere Treffer ergibt und das
            # erste Zeichen dieses Arguments ${1:0:1} kein Minuszeichen ist:
            if [[ $1 =~ [[:alnum:]]+ ]]; then 
                # Wir schreiben das erste Argument in eine Variable
				cmd="$1"
                # und shiften das nun abgearbeitete Argument für den nächsten Schritt weg.
				shift 1
			fi
			;;
	esac
done

printf  "  command: ${fett}${green}${cmd}${reset} option: ${fett}${green}"  # hier UNBEDINGT in grün die Optionen einzel ausgeben!!!

for ((i=0;i<${#short_options[@]};i++)); do
    printf " -%s" ${short_options[i]}
done

# dasselbe für lange Optionen
for ((i=0;i<${#long_options[@]};i++)); do
    printf " --%s" ${long_options[i]}
done
printf "${reset}\n"



man_or_help_function $*

if [[ $man_result == 0 ]]; then
    man_or_help_cmd="man"
fi
if [[ $help_result == 0 ]]; then
    man_or_help_cmd="help"
fi


if [[ $formatdescriptors_only == "False"  ]] ; then
    # SCHLEIFE über short optionen
    for (( i=0; i< ${#short_options[@]}; i++ )); do
        option="-${short_options[i]}"
        get_short_option
    done
    for (( i=0; i< ${#long_options[@]}; i++ )); do
        option="--${long_options[i]}"
        get_long_option
    done
else
    man $cmd | sed -nr '/^[[:blank:]]+%/,/^$/{/^$/d;p}'
fi
