# bash completion for john and unique commands (John the Ripper)
#
# This software is Copyright (c) 2012 Frank Dittrich
# and hereby released to the general public under the following terms:
# Redistribution and use in source and binary forms, with or without
# modification, are permitted.
#
# Minor improvements suggested by Aleksey Cherepanov have been
# incorporated.
#
#
# This bash completion script requires bash version >= 4,
# and extended pattern matching features enabled. If
# 	shopt -p extglob
# prints
#	shopt -s extglob
# then the extended pattern matching features are enabled. If this command
# prints
# 	shopt -u extglob
# then they are disabled.
#
# This file needs to be copied into the /etc/bash_completion.d/ directory,
# or wherever $BASH_COMPLETION_DIR points to - check the output of
# 	set | grep BASH_COMPLETION_DIR
#
# To make the new completion rules work, you need to logout and login,
# or source /etc/bash_completion instead.
#
# Alternatively, just add a line
# . <path_to_john's_source_directory>/john.bash_completion
# to your ~/.bashrc and logout/login.
#
# To use the same completion rules not just for john, but for
# differently named binaries (say john-omp, john-sse2i, john-avx,
# john-cuda, john-gpu, ...),
# just use this command to get the current completion rule settings:
#       complete -p john
#
# If the output is
#       complete -F _john john
# you can use this command to activate the same completion rules
# for john-omp:
#       complete -F _john john-omp
#
# To use these completion rules permanently, you might add
#       complete -F _john john-omp
# to your ~/.bashrc file.
#
#
# The code is still ugly, many things can probably be done more efficiently.
# Currently, grep, tr, and sed are the only external commands used.
# Trying to build a perfect completion script will be hard.
#
# If possible, I'd like to avoid the need to maintain this script whenever
# john gets a new option.
#
# john can either be globally installed (e.g. in /usr/bin/john),
# or it can be installed locally somewhere in a user's home directory.
# It can be an official version, or a community encanced (jumbo) version,
# with a variety of patches.
#
# FIXME: Is using __expand_tilde_by_ref OK?
#
# FIXME: For some reason completion for --option= only works
#	 if the cursor is at the end of the command,
#	 i.e. [[ ${COMP_POINT} -eq ${#COMP_LINE} ]]
#	 not if some words follow the one to be completed...
#	 If ${cur#*=} is not empty, completion works even in the middle
#	 of the command line
#	 This is annoying if I want to complete --rules= in
#	 ./john --rules= --config=test.conf
#
# FIXME: If there is a word -- preceding the current word
#	 which is to be completed, it cannot be an option, so file names
#	 should be used for completion.
#
# FIXME: Should completion for --make-charset really list existing .chr files?
#
# TODO:
#       --wordlist=~user/filename or --wordlist=~/dir/file doesn't work,
#	  but pressing [tab] expands this to something useful
#	  Where to fix this? In john? Some bash config option?
#
#       --external should not use all names of [List.External:..-.] sections,
#         but just sections without a generate() function, if --wordlist,
#         --incremental, --single or --markov is present on the command line;
#         and just those with a generate() function, if none of these options
#         is used
#         (WHAT IF the user intends to add a --wordlist option later?)
#

# different implementations for completion logic for these options:
# --rules --single --incremental --restore --status, --markov,
# --wordlist (if value is not mandatory for the john version), --show,
# --loopback, and some --list=... options like --list=help[:WHAT]
# for __john_completion=[2|any other value]
#
# john
## on my system, have() is a dummy function which always return "yes", so get rid of calling it...
## have grep && have sed && have tr &&
_john()
{
	local first cur options valopts compreplya compreplyb encodings formats subformats list hidden dir cmd i ver ver1 ver2 ver3 prev words prefix

	# Without LC_ALL=C, [A-Z] match [a-z] (case "${cur}" in ... esac)
	LC_ALL=C

	COMPREPLY=()

	if [[ "${COMP_WORDBREAKS}" == *:* ]] ; then
		_get_comp_words_by_ref -n := cur prev words
	else
		_get_comp_words_by_ref -n = cur prev words
		# If the colon is not part of COMP_WORDBREAKS, e.g., due to
		# including this line into your ~/.bashrc, as mentioned in
		# /etc/bash_completion ...
		# 	export COMP_WORDBREAKS="${COMP_WORDBREAKS//:}"
		# just replace : with = in -opt:val
		if [[ "${cur}" == -*:* ]] ; then
			if [[ "${cur}" == -*[:=]*[:=]* ]] ; then
				return 0
			fi
			cur="${cur//:/=}"
			COMPREPLY=( $(compgen -W "${cur}" -- ${cur}) )
			compopt -o nospace
			return 0
		fi
	fi

#	We need to make sure we run the correct program, not some other program
#	called john which is located somewhere in $PATH
	first="${COMP_WORDS[0]}"
#	Most options are listed at the begin of the line, but the line with the --pipe option
#	does have trailing spaces, and --stdin is mentioned after --wordlist=FILE.
#
#	All options (the '=' will be removed for options with an optional value)
	options=""
# FIXME: How do I suppress the error message if someone tries to be clever: cd run; ./john --[tab] ???
	options="`${first} 2>/dev/null|sed -n -e 's#^ *\(--[a-z-]*=\?\(LIST\)\?\).*$#\1#' -e '/^--/ p'` --stdin"
	if [[ "_${options}" == "_ --stdin" ]] ; then
		_filedir_xspec 2> /dev/null
		return 0
	fi

#	Just those options that can be used together with a value, even if that value is optional:
	valopts=`${first} 2>/dev/null|grep '^ *--[a-z\[-]*='|grep -v '^ *--subformat='|sed 's#^ *\([a-z=-]*\).*$#\1#'`
#	This is used to decide whether or not the completion should add a trailing space.
#	(That means, for a jumbo build, --rules doesn't get a trailing space, but for the john version
#	distributed by fedora16, --rules does get a trailing space during completion.
#	The same applies for --show and single)

#	now add the "hidden options" (not mentioned in the usage output, but in doc/OPTIONS and
#       with --list=hidden-options
#	Currently, all hidden options do have mandatory values (--option=value), this makes
#       addition of these easier
	hidden=`${first} --list=hidden-options 2>/dev/null|sed 's#^\(--[a-z-]*=\?\).*$#\1#'`

	case "${cur}" in
		-?(-)f?(o|or|orm|orma|ormat)+(=|:)dynamic*)
			if [[ "${cur#*f}" == [=:]* || "${cur#*f}" == o[=:]* || "${cur#*f}" == or[=:]* ]] ; then
				if [[ `echo "${valopts}"|grep -c "^-*${cur%[=:]*}"` -ne 1 ]] ; then
					return 0
				fi
			fi
			subformats=`${first} --list=subformats|sed 's#^\(User\)\?Format = \(dynamic_[0-9]*\).*$#\2#'`
			if [[ "_${subformats}" != _dynamic_0* ]] ; then
				subformats=`${first} --subformat=LIST|sed 's#^\(User\)\?Format = \(dynamic_[0-9]*\).*$#\2#'`
				if [[ "_${subformats}" != _dynamic_0* ]] ; then
					return 0
				fi
			fi
			cur=${cur#*[=:]}
			COMPREPLY=( $(compgen -W "${subformats}" -- ${cur}) )
			return 0
			;;
		-?(-)f?(o|or|orm|orma|ormat)+(=|:)*)
			if [[ "${cur#*f}" == [=:]* || "${cur#*f}" == o[=:]* || "${cur#*f}" == or[=:]* ]] ; then
				if [[ `echo "${valopts}"|grep -c "^-*${cur%[=:]*}"` -ne 1 ]] ; then
					return 0
				fi
			fi
			cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z`
			formats=`${first} |sed -n -e '/^--format/,$ {' -e 's#^--format=[ A-Za-z]*:##' -e '/^--/ b' -e 's#^ *##' -e 's#\<dynamic_n\>#dynamic#' -e 's#^\(.*\)$#\1#' -e 's#/# #g' -e 'p }'|LC_ALL=C tr A-Z a-z`
			COMPREPLY=( $(compgen -W "${formats}" -- ${cur}) )
			if [[ "${COMPREPLY[0]}_" == dynamic_ ]] ; then
				compopt -o nospace
			fi
			return 0
			;;

		--restore|--status)
			if [[ "_${__john_completion}" == "_2" ]] ; then
				COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) )
				compopt -o nospace
			else
				prev="${cur}"
				cur=""
				_filedir "rec"
				for (( i=0; i < ${#COMPREPLY[@]}; i++)); do
					COMPREPLY[$i]="${prev}=${COMPREPLY[$i]%*.rec}"
				done
				COMPREPLY[${#COMPREPLY[@]}]="${prev}"
			fi
			return 0
			;;
		-?(-)re?(s|st|sto|stor|store)+(=|:)*|-?(-)sta?(t|tu|tus)+(=|:)*)
			if [[ "${cur}" == -re[=:]* || "${cur}" == --re[=:]* ]] ; then
				if [[ `echo "${valopts}"|grep -c "^--re"` -ne 1 ]] ; then
					return 0
				fi
			fi
# If there is no .rec file in the current directory, the old completion logic will show all files:
##echo _`for f in *.rec; do echo ${f%.rec};done`_
			cur=${cur#*[=:]}
# cd $JOHN/ or Private home for system-wide builds, if ./john --list=build-info works?
# NO, this would be wrong!
# .rec files are stored in the current directory (or a subdirectory if the session name contains a slash)

			__expand_tilde_by_ref cur 2>/dev/null
			_filedir "rec"
			for (( i=0; i < ${#COMPREPLY[@]}; i++)); do
				# Do I have to add the trailing / for directories? Apparently not!
				COMPREPLY[$i]="${COMPREPLY[$i]%*.rec}"
			done
			return 0
			;;
		--wordlist)
			if [[ "${valopts}" == *${cur}=* || "_${__john_completion}" == "_2" ]] ; then
				COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) )
				compopt -o nospace
			fi
			return 0
			;;
		-?(-)w?(o|or|ord|ordl|ordli|ordlis|ordlist)+(=|:)*)
			cur=${cur#*[=:]}
			 __expand_tilde_by_ref cur 2>/dev/null
			_filedir
			return 0
			;;
 		--rules|--single)
			if [[ "${valopts}" == *${cur}* ]] ; then
				if [[ "_${__john_completion}" == "_2" ]] ; then
					COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) )
					compopt -o nospace
				else
					cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=rules #"`
					list=`${cmd} 2>/dev/null`
					if [[ $? -ne 0 ]] ; then
						list=`${first} --list=rules 2>/dev/null`
					fi
					if [[ $? -ne 0 ]] ; then
						list="single wordlist NT"
					fi
					list=`echo "${list}"|sed 's# #\n#g'|sed "s#^\(.\)#${cur}=\1#"`
					list="${list} ${cur}"
					COMPREPLY=( $(compgen -W "${list}" -- ${cur}) )
				fi
			else
				COMPREPLY=( $(compgen -W "${cur}" -- ${cur}) )
			fi
			return 0
			;;
		-?(-)ru?(l|le|les)+(=|:)*|-?(-)si?(n|ng|ngl|ngle)+(=|:)*)
			# let's assume every john version which supports --single=
			# also supports --rules=, and vice versa
			if [[ "${valopts}" == *--rules* ]] ; then
				cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=rules #"`
				list=`${cmd} 2>/dev/null`
				if [[ $? -ne 0 ]] ; then
					list=`${first} --list=rules 2>/dev/null`
				fi
				if [[ $? -eq 0 ]] ; then
					cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z`
					COMPREPLY=( $(compgen -W "${list}" -- ${cur}) )
				else
					cur=${cur#*[=:]}
					COMPREPLY=( $(compgen -W "NT single wordlist" -- ${cur}) )
				fi
			fi
			return 0
			;;
		-?(-)e?(x|xt|xte|xter|xtern|xterna|xternal)+(=|:)*)
			if [[ "${cur}" == -e[=:]* || "${cur}" == --e[=:]* ]] ; then
				if [[ `echo "${valopts}"|grep -c "^-*${cur%[=:]*}"` -ne 1 ]] ; then
					return 0
				fi
			fi
			cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=externals #"`
			list=`${cmd} 2>/dev/null`
			if [[ $? -ne 0 ]] ; then
				list=`${first} --list=externals 2>/dev/null`
			fi
			if [[ $? -ne 0 ]] ; then
				list="Filter_Alpha Filter_Digits Filter_Alnum Filter_LanMan LanMan Double Parallel Strip Keyboard"
				ver=`${first} 2>/dev/null|sed -n '/^John the Ripper password cracker, ver/ s#^John the Ripper password cracker, ver[a-z :]*\([0-9.]*\).*$#\1#p'`
				ver1=`echo $ver|sed 's#^\([0-9]*\).*$#\1#'`
				ver2=`echo $ver|sed 's#^[0-9]*.\([0-9]*\).*$#\1#'`
				ver3=`echo $ver|sed 's#^[0-9]*.[0-9]*.\([0-9]*\).*$#\1#'`
				if [[ "_${ver3}" == "_" ]] ; then
					ver3=0
				fi
				if [[ $ver1 -eq 1 && $ver2 -eq 7 ]] ; then
					if [[ $ver3 -ge 3 ]] ; then
						list="${list} DumbForce KnownForce"
					fi
					if [[ $ver3 -ge 7 ]] ; then
						list="${list} DateTime Repeats Subsets AtLeast1-Simple AtLeast1-Generic Policy"
					fi
					if [[ $ver3 -ge 8 ]] ; then
						list="${list} AppendLuhn"
					fi
					if [[ $ver3 -ge 9 ]] ; then
						list="${list} AutoAbort AutoStatus"
					fi
				else
					if [[ $ver1 -gt 1 || $ver1 -eq 1 && ver2 -gt 7 ]] ; then
						list="${list} DumbForce KnownForce DateTime Repeats Subsets AtLeast1-Simple AtLeast1-Generic Policy AppendLuhn AutoAbort AutoStatus"
					fi
				fi
				cur=${cur#*[=:]}
			else
				cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z`
			fi
			COMPREPLY=( $(compgen -W "${list}" -- ${cur}) )
			return 0
			;;
		--incremental)
			if [[ "_${__john_completion}" == "_2" ]] ; then
				COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) )
				compopt -o nospace
			else
				cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=inc-modes #"`
				list=`${cmd} 2>/dev/null`
				if [[ $? -ne 0 ]] ; then
					list=`${first} --list=inc-modes 2>/dev/null`
				fi
				if [[ $? -ne 0 ]] ; then
					list="All Alpha Digits Alnum LanMan"
				fi
				list=`echo "${list}"|sed 's# #\n#g'|sed "s#^\(.\)#${cur}=\1#"`
				list="${list} ${cur}"
				COMPREPLY=( $(compgen -W "${list}" -- ${cur}) )
			fi
			return 0
			;;
		-?(-)i?(n|nc|ncr|ncre|ncrem|ncreme|ncremen|ncrement|ncrementa|ncremental)+(=|:)*)
			cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=inc-modes #"`
			list=`${cmd} 2>/dev/null`
			if [[ $? -ne 0 ]] ; then
				list=`${first} --list=inc-modes 2>/dev/null`
			fi
			if [[ $? -eq 0 ]] ; then
				cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z`
				COMPREPLY=( $(compgen -W "${list}" -- ${cur}) )
			else
				cur=${cur#*[=:]}
				COMPREPLY=( $(compgen -W "All Alpha Digits Alnum LanMan" -- ${cur}) )
			fi
			return 0
			;;
		-?(-)m?(a|ak|ake|ake-|ake-c|ake-ch|ake-cha|ake-char|ake-chars|ake-charse|ake-charset)+(=|:)*)
			if [[ "${cur#*[=:]}" != *e* ]] ; then
				if [[ `echo "${valopts}"|grep -c "^-*${cur%[=:]*}"` -ne 1 ]] ; then
					return 0
				fi
			fi
			cur=${cur#*[=:]}
			#redirect stderr just in case __expand_tilde_by_ref
			#doesn't exist everywhere
			#(I'm a bit worried because of the __ at the begin.
			#May be this function isn't part of an "official" API.)
			__expand_tilde_by_ref cur 2>/dev/null
# FIXME:		should I just use directories for completion, not files,
# FIXME:		to make overwriting existing files harder?
			_filedir "chr"
			return 0
			;;
		--stdout)
			COMPREPLY=( $(compgen -W "--stdout --stdout=LENGTH" -- ${cur}) )
			return 0
			;;
		--markov)
			if [[ "${valopts}" == *${cur}* ]] ; then
				if [[ "_${__john_completion}" == "_2" ]] ; then
					COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) )
					compopt -o nospace
				else
					# FIXME: Should I mention [MIN_LEVEL-] and [MIN_LENGTH-]?
					#        I think not all jumbo versions support these.
					# FIXME: How to find out whether --markov=MODE is
					#        supported?
					#        Assume it is supported if at least one
					#        section [Markov:mode] exists, e.g. [Markov:Default]?
					COMPREPLY=( $(compgen -W "--markov --markov=LEVEL[:START[:END[:LENGTH]]] --markov=MODE --markov=MODE:LEVEL[:START[:END[:LENGTH]]]" -- ${cur}) )
				fi
			fi
			return 0
			;;
		-?(-)mar?(k|ko|kov)+(:|=)*)
			if [[ "${valopts}" == *--markov* ]] ; then
				# Ignore the  --markov=[MINLVL-]LEVEL[:START[:END[:[MINLEN-]LENGTH]]]?
				# Just try completion for --markov=MODE for all [Markov:...] sections?
				if [[ "${hidden}" == *--list=* || "${valopts}" == *--list=* ]] ; then
					cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z`
					# Don't include subsection names that contain a ':' or that
					# contain just '-' and digits
					list=`${first} --list=Markov 2>/dev/null | LC_ALL=C sed 's#^.*:.*$##'|LC_ALL=C sed 's#^[-0-9]*$##'`
					COMPREPLY=( $(compgen -W "${list}" -- ${cur}) )
					# no trailing space, just in case the user wants to add :lvl...
					compopt -o nospace
				fi
			fi
			return 0
			;;
		--test)
			if [[ "${valopts}" == *${cur}* ]] ; then
				COMPREPLY=( $(compgen -W "--test --test=SECONDS" -- ${cur}) )
			else
				COMPREPLY=( $(compgen -W "${cur}" -- ${cur}) )
			fi
			return 0
			;;
		--show)
			if [[ "${valopts}" == *${cur}* ]] ; then
				if [[ "_${__john_completion}" == "_2" ]] ; then
                                	COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) )
                                	compopt -o nospace
                        	else
					COMPREPLY=( $(compgen -W "--show --show=LEFT" -- ${cur}) )
				fi
			else
				COMPREPLY=( $(compgen -W "--show" -- ${cur}) )
			fi
			return 0
			;;
		-?(-)sho?(w)+(=|:)?([Ll]|[Ll][Ee]|[Ll][Ee][Ff]|[Ll][Ee][Ff][Tt]))
			if [[ "${valopts}" == *--show* ]] ; then
				cur=`echo ${cur#*[=:]}|LC_ALL=C tr a-z A-Z`
				COMPREPLY=( $(compgen -W "LEFT" -- ${cur}) )
			fi
			return 0
			;;
		--users=?(-)+(L|U)*|--groups=+(-|G)*|--shells=+(-|S)*|--salts=+(-|C)*)
			return 0
			;;
		--users=?(-))
			cur=${cur#*=}
			COMPREPLY=( $(compgen -W "LOGIN,... UID,... -LOGIN,... -UID,..." -- ${cur}) )
			return 0
			;;
		--groups=*)
			cur=${cur#*=}
			COMPREPLY=( $(compgen -W "GID,... -GID,..." -- ${cur}) )
			return 0
			;;
		--shells=*)
			cur=${cur#*=}
			COMPREPLY=( $(compgen -W "SHELL,... -SHELL,..." -- ${cur}) )
			return 0
			;;
		--salts=*)
			cur=${cur#*=}
			COMPREPLY=( $(compgen -W "COUNT -COUNT" -- ${cur}) )
			return 0
			;;
		-?(-)en?(c|co|cod|codi|codin|coding)+(=|:)*)
			if [[ "${valopts}" == *--encoding=* ]] ; then
				# --encoding=LIST writes to stderr
				list=`${first} --list=\? 2>/dev/null|sed 's#\(,\)\?\( or\)\?[ ]*[<].*$##; s#,##g'`
				if [[ "_${list}" == *encoding* ]] ; then
					cmd="${first} --list=encodings"
				else
					cmd="${first} --encoding=LIST"
				fi
				encodings=`${cmd} 2>&1|grep -v 'Supported encodings'|sed 's#[,)]##g'|sed 's#(or ##g'`
			cur=${cur#*[=:]}
				if [[ ${COMP_CWORD} -eq 2 || ${COMP_CWORD} -eq 3 && "_${cur}" != "_" ]] ; then
					# Don't add LIST if --list=encodings is supported
					if [[ "_${list}" != *encoding* ]] ; then
						encodings="${encodings} LIST"
						# make sure LIST will be the first option:
						LC_ALL=C
					fi
				fi
				COMPREPLY=( $(compgen -W "${encodings}" -- ${cur}) )
			fi
			return 0
			;;
		-?(-)po?(t)+(=|:)*)
			if  [[ "${valopts}" == *--pot=* ]] ; then
				# if --pot= is used, john always looks for the file $PWD
				# (tested with system-wide and local build of john)
				cur=${cur#*[=:]}
				#redirect stderr just in case __expand_tilde_by_ref
				#doesn't exist everywhere
				#(I'm a bit worried because of the __ at the begin.
				#May be this function isn't part of an "official" API.)
				__expand_tilde_by_ref cur 2>/dev/null
				_filedir "pot"
			fi
			return 0
			;;
		-?(-)co?(n|nf|nfi|nfig)+(=|:)*)
			if [[ "${valopts}" == *--config=* ]] ; then
				# if --config= is used, john always looks for files in $PWD
				# (tested for system-wide and local builds)
				cur=${cur#*[=:]}
				__expand_tilde_by_ref cur 2>/dev/null
				_filedir '@(conf|ini)'
			fi
			return 0
			;;
		--loopback)
			if [[ "${valopts}" == *--loopback* && "_${__john_completion}" == "_2" ]] ; then
				COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) )
				compopt -o nospace
			fi
			return 0
			;;
		-?(-)loo?(p|pb|pba|pbac|pback)+(=|:)*)
			if [[ "${valopts}" == *--loopback* ]] ; then
				cur=${cur#*[=:]}
				__expand_tilde_by_ref cur 2>/dev/null
				_filedir "pot"
			fi
			return 0
			;;
		-?(-)sav?(e|e-|e-m|e-me|e-mem|e-memo|e-memor|e-memory)+(=|:)*)
			cur=${cur#*[=:]}
			COMPREPLY=( $(compgen -W "1 2 3" -- ${cur}) )
			return 0
			;;
		-?(-)reg?(e|en|en-|en-l|en-lo|en-los|en-lost|en-lost-|en-lost-s|en-lost-sa|en-lost-sal|en-lost-salt|en-lost-salts)+(=|:)*)
			if [[ "${valopts}" == *--regen-lost-salts=* ]] ; then
				cur=${cur#*[=:]}
				COMPREPLY=( $(compgen -W "1 2 3 4 5" -- ${cur}) )
			fi
			return 0
			;;
		-?(-)su?(b|bf|bfo|bfor|bform|bforma|bformat)+(=|:)*)
			if [[ "${options}" == *--subformat=LIST* ]] ; then
				cur=`echo ${cur#*[=:]}|LC_ALL=C tr a-z A-Z`
				COMPREPLY=( $(compgen -W "LIST" -- ${cur}) )
			else
				if [[ "${hidden}" == *--subformat=* ]] ; then
					cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z`
					# Should I test if --format=crypt (or -fo:crypt ...) is specified?
					# Should I really parse the output of
					# 	$[first} --test --format=crypt --subformat=?
					# (BTW: The output is wrong. It lists upper case formats,
					# but expects lower case instead)
					# should I even test this (with --test=0, and filter out those
					# with a message:
					# appears to be unsupported on this system; will not load such hashes.
					subformats=`${first} --test=0 --format=crypt --subformat=? 2>&1|sed -n 's#,# #g;/^Subformat / s#^[^:]*:\(.*\)$#\L\1# p'`
					COMPREPLY=( $(compgen -W "${subformats}" -- ${cur}) )
				fi
			fi
			return 0
			;;
		-?(-)+(pla?(t|tf|tfo|tfor|tform)|d?(e|ev|evi|evic|evice))+(=|:)[Ll]?([Ii]|[Ii][Ss]|[Ii][Ss][Tt]))
			list=`${first} --list=\? 2>/dev/null|sed 's#\(,\)\?\( or\)\?[ ]*[<].*$##; s#,##g'`
			# Only complete to lIST if --list=cuda-devices and --list=opencl-devices don't exist
			# CUDA doesn't allow --device=LIST
			# workaround: check if --platform= is allowed
			if [[ "${valopts}" == *--platform=* && "_${list}" != *-devices* ]] ; then
				cur=`echo ${cur#*[=:]}|LC_ALL=C tr a-z A-Z`
				COMPREPLY=( $(compgen -W "LIST" -- ${cur}) )
			fi
			return 0
			;;
		-?(-)+(pla?(t|tf|tfo|tfor|tform)|d?(e|ev|evi|evic|evice))+(=|:))
			list=`${first} --list=\? 2>/dev/null|sed 's#\(,\)\?\( or\)\?[ ]*[<].*$##; s#,##g'`
			# Only list possible completions if --list=cuda-devices
			# and --list=opencl-devices don't exist.
			# --device=LIST isn't supported for CUDA, but for CUDA
			# --platform= is not a valid option
			if [[ "${valopts}" == *--platform=* && "_${list}" != *-devices* ]] ; then
				# Calling john --platform=LIST just to find possible completions
				# will take too long
				cur=${cur#*[=:]}
				COMPREPLY=( $(compgen -W "LIST N" -- ${cur}) )
			fi
			return 0
			;;
		-?(-)li?(s|st)+(+(=|:)+([a-z_-]):|:+([a-z._-])=)*([A-Za-z0-9:._-]))
			cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z`
			cmd=${cur%[=:]*}
			prefix=""
			if [[ "_${cmd}" == _*:* ]] ; then
				prefix=${cmd#*:}
				prefix=${prefix#*:}
				cmd=${cmd%:*}
				cur=${cur#*[=:]}
			fi
			cur=${cur#*[=:]}
			# Q&D fix, a general solution will take longer:
			if [[ ( "_${cmd}" == _parameters || "_${cmd}" == _list-data ) && "_${prefix}" != "_" ]] ; then
				# list subsection names
				list=`${first} --list=${prefix} 2>/dev/null|sed 's#^.*\s.*$##'`
			else
				list=`${first} --list=help:${cmd} 2>/dev/null |sed 's#,#\n#g'`
			fi
			if [[ $? -eq 0 ]] ; then
				COMPREPLY=( $(compgen -W "${list}" -- ${cur}) )
			fi
			return 0
			;;
		-?(-)l?(i|is|ist)+(=|:)*)
			if [[ "${hidden}" == *--list=* || "${valopts}" == *--list=* ]] ; then
				#meanwhile, there can be more than one option name starting with l...
				if [[ "${cur#*l}" == [=:]* ]] ; then
					if [[ `echo "${valopts}"|grep -c "^-*${cur%[=:]*}"` -ne 1 ]] ; then
						return 0
					fi
				fi
				cur=${cur#*[=:]}
				# the --list=? output changed, that's why a more complex regex is used
				# to cover all cases.
				# Meanwhile, even --list=format-methods[:WHICH] works
				# (or --list:format-methods[:WHICH] or --list:format-methods=WHICH, but
				# not --list:format-methods=WHICH)
				# format-methods[:WHICH],
				list=`${first} --list=\? 2>/dev/null|sed 's#\(,\)\?\(or\)\?[ ]*[<].*$##; s#,##g'`
				if [[ $? -eq 0 ]] ; then
					# add "?" to the list of possible completions, but don't add any
					# section names like "Options"...
					if [[ "${list}" == *help* ]] ; then
						# Don't advertise --list=?
						COMPREPLY=( $(compgen -W "${list}" -- ${cur}) )
					else
						# Add "?" to the possible completions
						COMPREPLY=( $(compgen -W "${list} ?" -- ${cur}) )
					fi
					# if the only value contains a ':', special treatment required
					if [[ ${#COMPREPLY[@]} -eq 1 && "_${COMPREPLY[0]}" == _*:* ]] ; then
						if [[ "_${COMPREPLY[0]}" == _*\[:* ]] ; then
							COMPREPLY[0]=${COMPREPLY[0]%\[*}
							if [[ "_${__john_completion}" == "_2" && "_${COMPREPLY[0]}" == _${cur} ]] ; then
								COMPREPLY[0]=${COMPREPLY[0]%:*}:
							fi
						else
							COMPREPLY[0]=${COMPREPLY[0]%:*}:
						fi
						compopt -o nospace
					fi
				fi
			fi
			return 0
			;;
		-*+(=|:))
			return 0;
			;;
		-*)
			compreplya=`compgen -W "${options} ${hidden}" -- ${cur}`
			if [[ "_${compreplya}_" == "__" ]] ; then
				cur="-${cur}"
				compreplya=`compgen -W "${options} ${hidden}" -- ${cur}`
			fi
			compreplyb=`compgen -W "${valopts} ${hidden}" -- ${cur}`
			COMPREPLY=( $(compgen -W "${options} ${hidden}" -- ${cur}) )
			if [[ "_${compreplya}" == "_${compreplyb}" ]] ; then
				compopt -o nospace
			fi
			return 0
			;;
		*)
			compopt  -o bashdefault -o default
			return 0
			;;
	esac
} &&
complete -F _john john
# unique
## have grep && have sed &&
_unique()
{
	local first cur usage options valopts compreplya compreplyb

	COMPREPLY=()

	_get_comp_words_by_ref -n = cur

# we need to make sure we run the correct program, not some other program
# called unique which is located somewhere in $PATH
	first="${COMP_WORDS[0]}"
	usage=`${first}|grep '^Usage:'|sed 's#^Usage:\? \?[^ ]*unique *##'`
	case "_${cur}" in
#		_|_${first})
#			if [[ "_${usage}" != "_OUTPUT-FILE" ]] ; then
#				COMPREPLY=( $(compgen -W "${usage}" -- "") )
#				_filedir
#			else
#				compopt -o bashdefault -o default
#			fi
#			return 0
#			;;
		_-cut=*|_-mem=*)
			return 0
			;;
		_-inp=*|_-ex_file=*|_-ex_file_only=*)
			if [[ "_${usage}" != "_OUTPUT-FILE" ]] ; then
				cur=${cur#*=}
				__expand_tilde_by_ref cur 2>/dev/null
				_filedir
			fi
			return 0
			;;
		_-*=)
			compopt -o bashdefault -o default
			return 0
			;;
		_-*)
			if [[ "_${usage}_" != "_OUTPUT-FILE_" ]] ; then
				options=`echo ${usage}|sed 's# #\n#g'|grep '^\[.*\]$'|sed 's#^.\(.*\).$#\1#'|sed 's#=.*$#=#'`
				valopts=`echo "${options}"|grep '='`
				compreplya=`compgen -W "${options}" -- ${cur}`
				compreplyb=`compgen -W "${valopts}" -- ${cur}`
				if [[ "_${compreplya}" == "_${compreplyb}" ]] ; then
					COMPREPLY=( $(compgen -W "${valopts}" -- "${cur}") )
					compopt -o nospace
				else
					COMPREPLY=( $(compgen -W "${options}" -- "${cur}") )
				fi
			fi
			return 0
			;;
		_*)
			compopt -o bashdefault -o default
			return 0
			;;
	esac
} &&
complete -F _unique unique
