#!/usr/bin/env bash

Version=2.00
Myname="${0##*/}"

doc=$(cat \
<<'DOC'
= scriptinfo - for a gendoc-documented script, print concise info

= Synopsis
scriptinfo [options] scriptname	

= Options

== General options:
-h,--help	print short help and exit
-H,--Help	print full documentation and exit
-V,--version	print version and exit

== Output options:
-b,--brief	print brief output for usage in scripts
-m,--markdown	use markdown for github-compatible output
-o,--options	list the options and exits

= Description
For a script with |gendoc| documentation, scriptinfo prints the script's
short description, type, author, authors email, version, license and intro.
The |--brief| option gives very terse output, the |--markdown| option
generates a README file and the |--options| option lists the options
formatted for the |gendoc| documentation.

If the script has no path information and is not found, it is looked
up in PATH.

- short description is found in the line starting with: |= <scriptname> - |
- type is found in the shebang line.
- author and authors email are extracted from the line after the line
  starting with |= Author|.
- version must be on a single line and may be capitalized;
  the first occurrence will be used.  Recognized formats are:

   version = 1.23
   $version = 1.23
   my $version = 1.23
   version => 1.23a
   version = "1.23"
   version = '1.23d'

  Whitespace in these formats may be arbirarily long or absent,
  including at the start of the line, anything may follow at the end,
  but a single lower letter will be part of the version number.
- license is extracted from the line after the line starting with
  |= Copyright|.

The output of scriptinfo comes in three formats. The default is the long
format, which for scriptinfo itself is like this:

    script: scriptinfo
      type: bash
    author: Wybo Dekker
     email: wybodekker@me.com
   version: 2.00
   license: GNU General Public License
     intro: For a script with gendoc documentation, scriptinfo
            prints the script's short description, type, author,
            authors email, version, license and intro.  The --brief
            option gives very terse output, the --markdown option
            generates a README file and the --options option lists
            the options formatted for the gendoc documentation.

With the |--brief| option, exactly 3 lines are output, one for each
of short description, type, version. This is the output used by the
/instscript/ and /gendoc/ scripts. For scriptinfo itself it looks
like this:

   for a gendoc-documented script, print concise info
   bash
   2.00

With the |--markdown| option, a markdown version is produced, which is
useful as a README file for github uploads. This option disables the
|--brief| option. The output for scriptinfo itself is:

   scriptinfo                                                                 
                                                                              
  for a gendoc-documented script, print concise info                          
                                                                              
  ## Properties                                                               
                                                                              
              key │ value                                                     
  ────────────────┼─────────────────────────────────────────────────────────  
          script: │ ./scriptinfo                                              
            type: │ bash                                                      
          author: │ Wybo Dekker                                               
           email: │ wybodekker@me.com[1]                                      
         version: │ 2.00                                                      
         license: │ GNU General Public License                                
           intro: │ For a script with gendoc documentation, scriptinfo        
                  │ prints the script's short description, type, author,      
                  │ authors email, version, license and intro.  The --brief   
                  │ option gives very terse output, the --markdown option     
                  │ generates a README file and the --options option lists    
                  │ the options formatted for the gendoc documentation.       
                                                                              
  [1]: wybodekker@me.com mailto:wybodekker@me.com                             
                                                                              
  ## Options                                                                  
                                                                              
   option                       │ description                                 
  ──────────────────────────────┼───────────────────────────────────────────  
   General options:             │                                             
   -h,--help                    │ print short help and exit                   
   -H,--Help                    │ print full documentation and exit           
   -V,--version                 │ print version and exit                      
   Output options:              │                                             
   -b,--brief                   │ print brief output for usage in scripts     
   -m,--markdown                │ use markdown for github-compatible output   
   -o,--options                 │ list the options and exits                  


The |--options| option is useful when writing the documentation for a shell
script. When the option handler contains lines like these:

   (-n|--nothing)	# this option does nothing
   (-o|--output)	# set output file name to X

then the command:
   scriptinfo -o script
will generate lines like this:

-n,--nothing	# this option does nothing
-o,--output=X	# set output file name to X

which can be inserted in the documentation. Note that the single character X, 
when it occurs in the comment line(s), is considered to be an argument of the 
option.

When you are editing the script with vim, you can insert such lines by
giving the ex-command:

  :r!scriptinfo -o %

= C binaries
My C binaries are compiled from bash scripts that copy their /gendoc/
documentation into the C-source as text for the -h option; by inserting a
newline before the text it is ensured that all lines can be grepped from
the binary. The version information is not in these lines: it is obtained
by running the binary with the |-V| option, which must have been
programmed, of course.

= Author
[Wybo Dekker](wybodekker@me.com)

= Copyright
Released under the [GNU General Public License](www.gnu.org/copyleft/gpl.html)

= See also
gendoc(1), instscript(1)
DOC
)

# --- colors, help from embedded doc, die ---
[ -t 1 ] && { Red=$'\e[1;31m' Nor=$'\e[0m'; } || Red='' Nor=''
nv="-$Myname-${Version/./\\.} (press h for help, q to quit)"
helpsrt() { sed -n '/^= Synopsis/,/^= Desc/{//d;p}' <<<"$doc"; exit; }
helpall() { less -P"$nv" <<<"$doc"; exit; }
    die() { printf "%s$Nor\n" "${@/#/$Myname: $Red}" >&2; exit 1; }

:<<'DOC' #----------------------------------------------------------------------
= handle_options
synopsis:	 handle_options "$@"
description:	handle the options.
globals used:	 Myname Version
globals  set:	 args brief markdown listoptions
DOC
getopt -T || (( $? == 4 )) || die "Your getopt is not GNU"
handle_options() {
   local options
   options=$(getopt \
      -n "$Myname" \
      -o hHVIbnom \
      -l help,Help,version,brief,nocolor,markdown,options,test: \
      -- "$@"
   ) || exit 1
   eval set -- "$options"
   brief=false
   markdown=false
   listoptions=false
   while [ $# -gt 0 ]; do
      case $1 in
      ## General options:
      (-h|--help)	# print short help and exit
			helpsrt
			;;
      (-H|--Help)	# print full documentation and exit
			helpall
			;;
      (-V|--version)	# print version and exit
			echo $Version
			exit
			;;
      ## Output options:
      (-b|--brief)	# print brief output for usage in scripts
			brief=true
			shift
			;;
      (-m|--markdown)	# use markdown for github-compatible output
			markdown=true
			shift
			;;
      (-o|--options)	# list the options and exits
			listoptions=true
			shift
			;;
      (-I)		instscript "$0" ||
			   die 'the -I option is for developers only'
			exit
			;;
      (--)		shift
			break
			;;
      (*)		break
			;;
      esac
   done
   args=( "$@" )
}

:<<'DOC' #----------------------------------------------------------------------
= listopts
synopsis:	 listoptions
description:	List the options in the format we need in the documentation.
		We don't take this info from the documentation, because this is
		meant for developers, who may add, remove or change options and
		their comments and want to update the documentation. 
		This works for bash and zsh (where we scan the getopts call for
		info) and for ruby and python (where the built-in option |-h|
		provides the information.
		It does not for binaries of course, so here were trust that the 
		options |-H] and |-V| are programmed to provide the needed info.
globals used:	 Nor Red doc script type
globals  set:	 -
DOC
listopts() {
   local i j l ll e=()
   local w='[a-zA-Z0-9=]' s='[[:space:]]' so='[[:space:](]'
   if [[ $type =~ (ba|z)sh ]]; then
      expand "$script" |
	# remove upto the getopt call
	# remove upto the next case statement
	# remove from -I option to rest of script
	sed -ne "1,/=\$(getopt/d" \
	-e '1,/case/d' \
	-e '/(-I)/,$d;p' |
      while IFS= read -r l; do
         # subheadings start with «##»
         if [[ $l =~ ^$s*\#\#(.*) ]]; then
            echo -e "\n==${BASH_REMATCH[1]}"
         # find getopt entries with comment:
         elif [[ $l =~ ^$so+(-$w\|--$w+|-$w|--$w+)\)$s*(#$s.*)? ]]; then
            i="${BASH_REMATCH[1]/|/,}"
            j="${BASH_REMATCH[2]#\# }"
            if [[ -z $j ]]; then
               e+=("missing comment for $i")
               j="${Red}!! missing comment !!$Nor"
            fi
            [[ $i =~ -- ]] || i+='\t'
            [[ $i =~ ^-- ]] && i="   $i"
            while IFS= read -r ll; do
               [[ $ll =~ ^$s+#\ (.*) ]] || break
               j+="\n\t\t${BASH_REMATCH[1]}"
            done
            # if there is a single X in the comment, this
            # option takes an argument, so we add =X to the long option.
            [[ $j =~ (^|[^[:alnum:]_])X([^[:alnum:]_]|$) ]] && i+='=X'
            echo -e "$i\t$j"
         fi
      done
   elif [[ $type == C ]]; then
      sed -n '/^= Options/,/^= /{//d;p}' <<<"$doc"
   else
      # the ruby and python option parsers have it all built-in:
      $script -h |sed '1,/^Options/d'
   fi |unexpand -a
}    
:<<'DOC' #----------------------------------------------------------------------
= print-readme
synopsis:	 print-readme
description:	Print the README.md file for github
globals used:	 blok properties scriptname short
globals  set:	 -
DOC
print-readme() {
   cat <<-EOD
	# $scriptname
	$short
	# Properties
	|key|value|
	|-:|:-|
	$(properties+=("${blok:1:-1}") &&
	  printf "| %7s:|%s|\n" "${properties[@]}"
	)
	# Options
	|option|description|
	|:-|:-|
	$(listopts |
	  sed -z '
	    s=/\([^/]*\)/=*\1*=g
	    s=|\([^|]*\)|=`\1`=g'|
	  sed '
	    s/^== \(.*\)/|**\1**||/
	    /^$/d
	    s/\(.*\)\(\t\+\)\(.*\)/|\1\2|\3|/
	  ')
EOD
}

handle_options "$@"
set -- "${args[@]}"

test $# -eq 1 || die "I need 1 input file"

script="$1"
scriptname=${script##*/}
if [[ ! -f $script ]]; then
   if [[ $script =~ / ]]; then
      [[ -e $script ]] || die "$script: does not exist"
      [[ -f $script ]] || die "$script: not a regular file"
   else
      script=$(command -v "$1")
      [[ -z $script ]] && die "$1: not found here, nor in PATH"
   fi
fi
if [[ -L $script ]]; then
   script=$(readlink -f "$script")
fi

if [[ $(file --mime "$script") =~ binary$ ]]; then
  type=C
else
  type=$(head -1 "$script"|sed -E '
    s|^#!||
    s|^ */usr/bin/env +||
    s|^-S +||
    s|^ */.*/||
    s| .*$||
  ')
   : "${type:=<?>}"
fi

# My scripts must respond to the -V and -H options
vers=$($script -V 2>/dev/null) || die "$script does not respond to -V option"
doc=$($script -H 2>/dev/null) || die "$script does not respond to -H option"

short=$(sed -n "/^= ${scriptname} - / {s/.* - //;p;q}" <<<"$doc")
: "${short:=<?>}  ${type:=<?>} ${vers:=<?>}"

if $brief; then
   printf '%s\n' "$short" "$type" "$vers"
   exit
elif $listoptions; then
   listopts
   exit
fi

IFS='(' read -r author email < <(grep '^= Author' -A1 <<<"$doc"|tail -1|tr -d '[])')
license=$(grep -A1 '^= Copyright' <<<"$doc" |tail -1|sed 's/.*\[\(.*\)\].*/\1/')
intro=$(sed -n '/^= Description/,/^$/ {/^= Description/d; /^$/d; p; }' <<<"$doc")
: "${author:=<?>} ${email:=<?>} ${license:=<?>} ${intro:=<?>} ${opts:=<?>}" 
blok="$(echo "$intro" |fmt -w60 |sed '2,$s/^/         /')"
properties=(
   script  "$scriptname"
   short   "$short" 
   type    "$type"
   author  "$author"
   email   "$email"
   version "$vers"
   license "$license"
   intro
)

if $markdown; then
  blok="$(
    sed -z '
      s/|\([^|]*\)|/`\1`/g
      s=/\([^/]*\)/=*\1*=g
    ' <<<"$blok" |
    sed '
      s/^[[:space:]]*/|&|/
      s/$/|/;s/||/|/
    '
  )"
  p="${properties[9]}"
  properties[9]="[$p](mailto:$p)"
  print-readme
else
  properties[1]="$Red$scriptname$Nor"
  # shellcheck disable=SC2001
  blok="$(sed "s/|\([^ ]*\)|/$Bla\1$Nor/g" <<<"$blok")"
  properties+=("$blok")
  printf "$Blu%7s$Nor: %s\n" "${properties[@]}"
fi
