#! /bin/sh
#
# This script is used to configure the linux kernel.
#
# It was inspired by a desire to not have to hit <enter> 9 million times
# or startup the X server just to change a single kernel parameter.  
#
# This script attempts to parse the configuration files, which are
# scattered throughout the kernel source tree, and creates a temporary
# set of mini scripts which are in turn used to create nested menus and
# radiolists.
#
# It uses a very modified/mutilated version of the "dialog" utility
# written by Savio Lam (lam836@cs.cuhk.hk). Savio is not responsible
# for this script or the version of dialog used by this script.
# Please do not contact him with questions. The official version of 
# dialog is available at sunsite.unc.edu or a sunsite mirror.
#
# Portions of this script were borrowed from the original Configure
# script used in linux-1.3.55.
#
# Please send comments / questions / bug fixes to roadcapw@cfw.com
#
#----------------------------------------------------------------------------


#
# Change this to TRUE if you prefer all kernel options listed
# in a single menu rather than the standard menu hierarchy.
#
# Don't forget to remove linux/.menuconfig.in
#
single_menu_mode=

#
# Make sure we're really running bash.
#
[ -z "$BASH" ] && { echo "Configure requires bash" 1>&2; exit 1; }

#
# Cache function definitions
#
set -h

#
# Extract available help for an option from Configure.help
# and send it to standard output.
#
# Most of this function was borrowed from the original kernel
# Configure script.
#
function extract_help () {
  if [ -f Documentation/Configure.help ]
  then
     #first escape regexp special characters in the argument:
     var=$(echo "$1"|sed 's/[][\/.^$*]/\\&/g')
     #now pick out the right help text:
     text=$(sed -n "/^$var[ 	]*\$/,\${
                        /^$var[ 	]*\$/b
                        /^#.*/b;/^[ 	]*\$/q
                        p
                    }" Documentation/Configure.help)

     if [ -z "$text" ]
     then
	  echo "There is no help available for this setting."
     else
	  echo "$text"
     fi
  else
	 echo "There is no help available for this setting."
  fi
}

#
# Activate a help dialog.
#
function help () {
	extract_help $2 >help.out
	$DIALOG --backtitle "$backtitle" --title "$1" --textbox help.out 20 75
	rm help.out
}


#
# Init temporary scripts for the current menu.
#
function menu_name () {
	echo -ne "$DIALOG --title '$1'\
			--backtitle '$backtitle' \
			--menu '$menu_instructions' \
			21 75 11 '$default' " >submenu
	>radiolists
}

#
# Additional comments are currently semi-supported
#
function comment () {
	comment_ctr=$[ comment_ctr + 1 ]
	echo -ne "': $comment_ctr' '--- $1' " >>submenu
}

#
# Don't need this yet, but we don't want to puke either.
#
function define_bool () {
	:	
}

#
# Add a submenu option to the menu currently under construction.
#
function submenu () {
	echo -ne "'activate_menu $2' '$1  --->' " >>submenu
}

#
# This is to handle the Sound configuration.
#
function yuck1 () {
	echo -ne "'clear ; $MAKE $2' '$1' " >>submenu
}

#
# Create a boolean (Yes/No) function for our current menu
# which calls our local bool function.
#
function bool () {
	eval $2=\${$2:-'n'}  x=\$$2

	case $x in
	y|m) yes='ON' no='OFF' flag="*"
	   ;;
	n) yes='OFF' no='ON' flag=" "
	   ;;
	esac

	echo -ne "'$2' '($flag) $1' " >>submenu

	echo -e "function $2 () { l_bool '$1' '$yes' '$no' '$2' }\n" \
		>>radiolists
}

#
# Handle a boolean (Yes/No) option.
#
function l_bool () {
	while true
	do
		if $DIALOG --title "$1" \
			--backtitle "$backtitle" \
			--radiolist "$radiolist_instructions" 12 70 2 \
			'y' 'Yes' $2 'n' 'No' $3 2>dialog.out
		then
			eval $4=`cat dialog.out`
			break
		fi
			
		help "$1" "$4"
	done
}

#
# Same as bool() except options are (Module/No)
#
function mod_bool () {
	eval $2=\${$2:-'n'}  x=\$$2

	case $x in
	m) module='ON'  no='OFF' flag='M'
	   ;;
	*) module='OFF' no='ON'  flag=' '
	   ;;
	esac

	echo -ne "'$2' '($flag) $1' " >>submenu

	echo -e "function $2 () { l_mod_bool '$1' '$module' '$no' '$2' }\n" \
		>>radiolists
}

#
# Same as l_bool() except options are (Module/No)
#
function l_mod_bool() {
	while true
	do
		if $DIALOG --title "$1" \
			--backtitle "$backtitle" \
			--radiolist "$radiolist_instructions" 12 70 2 \
			'm' 'Module' $2 'n' 'No' $3 2>dialog.out
		then
			eval $4=`cat dialog.out`
			break
		fi
			
		help "$1" "$4"
	done
}

#
# Create a tristate (Yes/No/Module) radiolist function
# which calls our local tristate function.
#
# Collapses to a boolean (Yes/No) if module support is disabled.
#
function tristate () {
	if [ "$CONFIG_MODULES" != "y" ]
	then
		bool "$1" "$2"
	else
		eval $2=\${$2:-'n'}  x=\$$2
	
		case $x in
		y) yes='ON'  no='OFF' module='OFF' flag="*"
   		;;
		m) yes='OFF' no='OFF' module='ON' flag="M"
   		;;
		*) yes='OFF' no='ON'  module='OFF' flag=" "
   		;;
		esac
	
		echo -ne "'$2' '($flag) $1' " >>submenu
	
		echo -e "
		function $2 () { \
			l_tristate '$1' '$yes' '$no' '$module' '$2'
		}"  >>radiolists
	fi
}

#
# Handle a tristate (Yes/No/Module) option.
#
function l_tristate () {
	while true
	do
		if $DIALOG --title "$1" \
			--backtitle "$backtitle" \
			--radiolist "$radiolist_instructions" 13 70 3 \
			'y' 'Yes' $2 'n' 'No' $3 'm' 'Module' $4 \
			2>dialog.out
		then
			eval $5=`cat dialog.out`
			break
		fi
			
		help "$1" "$5"
	done
}

#
# Create a tristate radiolist function which is dependent on
# another kernel configuration option.
#
# Quote from the original configure script:
#
#       If the option we depend upon is a module,
#       then the only allowable options are M or N.  If Y, then
#       this is a normal tristate.  This is used in cases where modules
#       are nested, and one module requires the presence of something
#       else in the kernel.
#
function dep_tristate () {
	if [ "$CONFIG_MODULES" != "y" ]
	then
		bool "$1" "$2"
	else
		if  eval [ "_$3" != "_m" ]
		then
			tristate "$1" "$2" $3
		else
			mod_bool "$1" "$2"
		fi
	fi
}

#
# Create a function which will call our local int function.
# 
function int () {
	eval $2=\${$2:-"$3"} x=\$$2

	echo -ne "'$2' '($x) $1' " >>submenu

	echo -e "function $2 () { l_int '$1' '$2' '$3' '$x' }\n" >>radiolists
}

#
# Create a dialog for entering an integer into a kernel option.
#
function l_int () {
	while true
	do
		if $DIALOG --title "$1" \
			--backtitle "$backtitle" \
			--inputbox "$inputbox_instructions_int" \
			15 55 "$4" 2>dialog.out
		then
			answer="`cat dialog.out`"
			answer="${answer:-$3}"

			if expr $answer : '0$\|-?[1-9][0-9]*$' >/dev/null
			then
				eval $2="$answer"
			else
				eval $2="$3"
				echo -en "\007"
				${DIALOG} --backtitle "$backtitle" \
					--infobox "You have made an invalid entry." 3 43
				sleep 2
			fi

			break
		fi

		help "$1" "$2"
	done
}


#
# Create a function which will call our local int function.
# 
function hex () {
	eval $2=\${$2:-"$3"} x=\${$2##*[x,X]}

	echo -ne "'$2' '($x) $1' " >>submenu

	echo -e "function $2 () { l_hex '$1' '$2' '$3' '$x' }\n" >>radiolists
}

#
# Create a dialog for entering a hexidecimal into a kernel option.
#
function l_hex () {
	while true
	do
		if $DIALOG --title "$1" \
			--backtitle "$backtitle" \
			--inputbox "$inputbox_instructions_hex" \
			15 55 "$4" 2>dialog.out
		then
			answer="`cat dialog.out`"
			answer="${answer:-$3}"
			answer="${answer##*[x,X]}"

			if expr $answer : '[0-9a-fA-F]+$' >/dev/null
			then
				eval $2="$answer"
			else
				eval $2="$3"
				echo -en "\007"
				${DIALOG} --backtitle "$backtitle" \
					--infobox "You have made an invalid entry." 3 43
				sleep 2
			fi

			break
		fi

		help "$1" "$2"
	done
}

#
# Create a function which will call our local One-of-Many choice list.
#
function choice () {
	#
	# Need to remember params cause the're gonna get reset.
	#
	title=$1
	choices=$2
	default=$3
	current=

	#
	# Find out if one of the choices is already set.
	# If it's not then make it the default.
	#
	set -- $choices
	firstchoice=$2

	while [ -n "$2" ]
	do
		if eval [ "_\$$2" = "_y" ]
		then
			current=$1
			break
		fi
		shift ; shift
	done

	: ${current:=$default}

	echo -ne "'$firstchoice' '($current) $title' " >>submenu

	echo -e "
	function $firstchoice () {
		l_choice '$title' \"$choices\" $current
	}\n" >>radiolists
}


function l_choice () {
	#
	# Need to remember params cause the're gonna get reset.
	#
	title="$1"
	choices="$2"
	current="$3"

	#
	# Scan current value of choices and set radiolist switches.
	#
	list=
	set -- $choices
	firstchoice=$2
	while [ -n "$2" ]
	do
		case "$1" in
		"$current")	list="$list $2 $1 ON "  ;;
		*)		list="$list $2 $1 OFF " ;;
		esac
			
		shift ; shift
	done

	while true
	do
		if $DIALOG --title "$title" \
			--backtitle "$backtitle" \
			--radiolist "$radiolist_instructions" \
			22 70 11 $list 2>dialog.out
		then
			choice=`cat dialog.out`
			break
		fi

		help "$title" "$firstchoice"
	done

	#
	# Now set the boolean value of each option base on
	# the selection made from the radiolist.
	#
	set -- $choices
	while [ -n "$2" ]
	do
		if [ "$2" = "$choice" ]
		then
			eval $2="y"
		else
			eval $2="n"
		fi
		
		shift ; shift
	done
}


#
# Now parse the configuration files and create our mini scripts.
# Each script represents a separate menu which when taken together
# form a linked menu tree.  Since one configuration file
# may source another elsewhere in the kernel source tree, this 
# function is recursive.
#
function parse_config_files () {
	if [ "_$single_menu_mode" = "_TRUE" ]
	then
		parse_single_menu $1
		return
	fi

	while read command args
	do
		case $command in

		#
		# Will it ever happen that mainmenu_option will be followed
		# by anything other then "next_comment"?  This assumes not..
		#
		mainmenu_option) 
			comment_is_option=TRUE
			;;

		comment)
			if [ "$comment_is_option" ]
			then
				comment_is_option=

				stack="$submenu$stack"

				menu_no=$[menu_no + 1]
				x="submenu$menu_no"

				echo "submenu $args $x" >> $submenu

				submenu=$x
				echo menu_name $args >$submenu
			else
				echo comment $args >> $submenu
			fi
			;;
	
		endmenu)
			submenu="${stack%%*}"
			stack="${stack#*}"
			;;

		mainmenu_name)
			echo menu_name "'Main Menu'" > submenu0
			;;

		\$MAKE) echo "yuck1 'Configure (You must do this!)' '$args'"\
				>>$submenu
			;;

		source)
			parse_config_files "$args"
			;;

		'#'|'')  : ;;

		*)	echo $command $args >> $submenu
			;;
		esac
	done < $1
}

#
# Parses configuration files into a single menu structure.
#
function parse_single_menu () {

	while read command args
	do
		case $command in

		mainmenu_option | \
		endmenu)
			: ;;

		mainmenu_name)
			echo menu_name "'Main Menu'" > submenu0
			;;

		\$MAKE) echo "yuck1 'Configure (You must do this!)' '$args'"\
				>>$submenu
			;;

		source)
			parse_single_menu "$args"
			;;

		'#'|'')  : ;;

		*)	echo $command $args >> $submenu
			;;
		esac
	done < $1
}

#
# This is the menu tree's bootstrap.
#
# Executes a mini script to create a set of functions, one per configuration
# option.  These functions will in turn execute dialog commands or recursively
# call other mini scripts.
#
function activate_menu () {

	while true
	do
		comment_ctr=0
		$1 "$default"		#Create the radiolists and dialog cmd
		. radiolists		#Read in the dialog functions.

		. submenu 2>dialog.out	#Activate this menu

		case "$?" in
		0)
			defaults="`cat dialog.out`$defaults"  #pseudo stack
			source dialog.out
			default="${defaults%%*}" defaults="${defaults#*}"
			;;
		2)	
			read selection <dialog.out
			default="${selection##* }"
			case "$selection" in
			*"-->"*) : ;;
			*)	 eval help $selection ;;
			esac
			;;
		255|1)
			break
			;;
		esac
	done
}

#
# Just what it says.
#
save_configuration () {
	${DIALOG} --backtitle "$backtitle" \
		  --infobox "Saving your new kernel configuration..."  3 43

	#
	# Now, let's redefine the configuration functions for final
	# output to the config files.
	#
	function bool () {
		eval define_bool "$2" "\${$2:-n}"
	}

	function tristate () {
		eval define_bool "$2" "\${$2:-n}"
	}

	function dep_tristate () {
		eval x=\${$2:-n}
		if eval [ "_$3" != "_m" ]
		then
			tristate "$1" "$2"
		else
			if [ "$x" = "y" ]
			then
				x="m"
			fi
			define_bool "$2" "$x"
		fi
	}

	function int () {
		eval x=\${$2:-"$3"}
		echo "$2=$x" 		>>$CONFIG
		echo "#define $2 ($x)"	>>$CONFIG_H
	}

	function hex () {
		eval x=\${$2:-"$3"}
		echo "$2=$x" 			 >>$CONFIG
		echo "#define $2 0x${x##*[x,X]}" >>$CONFIG_H
	}

	function define_bool () {
   		case "$2" in
         	y)
                	echo "$1=y" 		>>$CONFIG
                	echo "#define $1 1"	>>$CONFIG_H
                	;;

         	m)
			if [ "$CONFIG_MODULES" = "y" ]
			then
                		echo "$1=m"		>>$CONFIG
                		echo "#undef  $1"	>>$CONFIG_H
			else
                		echo "$1=y" 		>>$CONFIG
                		echo "#define $1 1"	>>$CONFIG_H
			fi
                	;;

         	n)
                	echo "# $1 is not set"	>>$CONFIG
                	echo "#undef  $1"	>>$CONFIG_H
                	;;
        	esac
	}

	function choice () {
		#
		# Find the first choice that's already set to 'y'
		#
		choices="$2"
		default="$3"
		current=

		set -- $choices
		while [ -n "$2" ]
		do
			if eval [ "_\$$2" = "_y" ]
			then
				current=$1
				break
			fi
			shift ; shift
		done

		#
		# Use the default if none were set.  
		#
		: ${current:=$default}

		#
		# Then extract the actual option from the list of choices.
		#
		current=${choices#*$current} ; set $current

		define_bool "$1" "y"
	}

	function mainmenu_name () {
		:
	}

	function mainmenu_option () {
		comment_is_option=TRUE
	}

	function endmenu () {
		:
	}

	function comment () {
		if [ "$comment_is_option" ]
		then
			comment_is_option=
			echo        >>$CONFIG
			echo "#"    >>$CONFIG
			echo "# $1" >>$CONFIG
			echo "#"    >>$CONFIG

			echo         >>$CONFIG_H
			echo "/*"    >>$CONFIG_H
			echo " * $1" >>$CONFIG_H
			echo " */"   >>$CONFIG_H
		fi
	}

	#
	# This will hopfully prevent the sound configuration from
	# running again.  (blagh!)
	#
	MAKE=:

	CONFIG=.tmpconfig
	CONFIG_H=.tmpconfig.h

	echo "#" >$CONFIG
	echo "# Automatically generated by make menuconfig: don't edit" >>$CONFIG
	echo "#" >>$CONFIG

	echo "/*" >$CONFIG_H
	echo " * Automatically generated by make menuconfig: don't edit" >>$CONFIG_H
	echo " */" >>$CONFIG_H
	
	. $CONFIG_IN

	if [ -f .config ]
	then
		rm -f .config.old
		mv .config .config.old
	fi
	mv .tmpconfig .config
	mv .tmpconfig.h include/linux/autoconf.h
}

#
# Remove temporary files
#
cleanup () {
	cleanup1
	cleanup2
}

cleanup1 () {
	rm -f submenu* radiolists dialog.out help.out
}

cleanup2 () {
	rm -f .tmpconfig .tmpconfig.h
}


menu_instructions="\
Arrow keys navigate the menu.  \
Highlighted letters are shortcuts. \
Select an item with <Space Bar> or <Enter>. \
When finished press <E> or <X> or <Esc>.  \
(*) indicates an option will be compiled into the kernel.  \
(M) indicates an option will be compiled as a module."

radiolist_instructions="\
Use the arrow keys to navigate this radiolist or \
press the first letter of the item you wish to select \
followed by the <SPACE BAR>.
Press <H> for additional information about this option."

inputbox_instructions_int="\
Please enter a decimal value between 1 and 9999. \
Fractions will not be accepted.  \
Use the <TAB> key to move from the input field to buttons below it."

inputbox_instructions_hex="\
Please enter a hexidecimal value. \
Use the <TAB> key to move from the input field to buttons below it."

backtitle="Linux Kernel Configuration"

DIALOG="./scripts/lxdialog/lxdialog"

comment_is_option=
menu_no=0
submenu=submenu0
kernel_version="${VERSION}.${PATCHLEVEL}.${SUBLEVEL}"

trap "cleanup ; rm -f .menuconfig.in ; exit 1" 1 2 15

#
# Locate default files.
#
DEFAULT=""
if [ "$1" = "-d" ] ; then
	DEFAULT="-d"
	shift
fi

CONFIG_IN=./config.in
if [ "$1" != "" ] ; then
	CONFIG_IN=$1
fi

DEFAULTS=arch/$ARCH/defconfig
if [ -f .config ]; then
  DEFAULTS=.config
fi

if [ -f $DEFAULTS ]; then
  echo "#"
  echo "# Using defaults found in" $DEFAULTS
  echo "#"
  . $DEFAULTS
else
  echo "#"
  echo "# No defaults found"
  echo "#"
fi


#
# Convert the configuration files into our mini scripts.
# Then put all the mini scripts into a single file which should
# stick around and act as a cache until the kernel release changes
# or "make mrproper".
#
if [ -e .menuconfig.in ]
then
	read x <.menuconfig.in
	if [ "$x" != "# $kernel_version" ]
	then
		rm -f .menuconfig.in
	fi
fi

if [ ! -e .menuconfig.in ]
then
	$DIALOG	--backtitle "$backtitle" \
		--infobox "Compiling configuration script..." 3 40

	rm -f submenu*
	parse_config_files $CONFIG_IN

	#
	# For handling Sound configuration. (Yuk!)
	#
	echo '$MAKE $*' >submenu999

	echo "# $kernel_version" >.menuconfig.in

	for i in submenu*
	do
		echo "function $i () {"
		echo 'default=$1'
		cat $i
		echo -e "}\n\n"
	done >> .menuconfig.in

	rm -f submenu*
fi

source .menuconfig.in

#
# Start the ball rolling from the top.
#
activate_menu submenu0

#
# We're done with the menu scripts.  Get rid of them.
#
cleanup1

#
# Confirm and Save
#
if $DIALOG --backtitle "$backtitle" \
	   --yesno "Do you wish to save your new kernel configuration?" 5 60
	   
then
	save_configuration

	$DIALOG --backtitle "$backtitle" --infobox "\
The linux kernel is now hopefully configured for your setup.  \
Check the top-level Makefile for additional configuration, \
and do a 'make dep ; make clean' if you want to be sure all \
the files are correctly re-made." 7 60
else
	$DIALOG --backtitle "$backtitle" \
		--infobox "Your new configuration was not saved." 3 42 
	
fi


exit 0

