Index: tools/config.py
===================================================================
--- tools/config.py	(revision c01f8e6569d3ccfbdad77b7dfc725604ca76fece)
+++ tools/config.py	(revision 28f4adbc5088d8d67ea408f56eb3b35838031e6a)
@@ -3,5 +3,4 @@
 # Copyright (c) 2006 Ondrej Palkovsky
 # Copyright (c) 2009 Martin Decky
-# Copyright (c) 2010 Jiri Svoboda
 # All rights reserved.
 #
@@ -41,11 +40,11 @@
 import xtui
 
-RULES_FILE = sys.argv[1]
+INPUT = sys.argv[1]
 MAKEFILE = 'Makefile.config'
 MACROS = 'config.h'
-PRESETS_DIR = 'defaults'
-
-def read_config(fname, config):
-	"Read saved values from last configuration run or a preset file"
+PRECONF = 'defaults'
+
+def read_defaults(fname, defaults):
+	"Read saved values from last configuration run"
 	
 	inf = open(fname, 'r')
@@ -53,18 +52,18 @@
 	for line in inf:
 		res = re.match(r'^(?:#!# )?([^#]\w*)\s*=\s*(.*?)\s*$', line)
-		if res:
-			config[res.group(1)] = res.group(2)
+		if (res):
+			defaults[res.group(1)] = res.group(2)
 	
 	inf.close()
 
-def check_condition(text, config, rules):
+def check_condition(text, defaults, ask_names):
 	"Check that the condition specified on input line is True (only CNF and DNF is supported)"
 	
 	ctype = 'cnf'
 	
-	if (')|' in text) or ('|(' in text):
+	if ((')|' in text) or ('|(' in text)):
 		ctype = 'dnf'
 	
-	if ctype == 'cnf':
+	if (ctype == 'cnf'):
 		conds = text.split('&')
 	else:
@@ -72,23 +71,23 @@
 	
 	for cond in conds:
-		if cond.startswith('(') and cond.endswith(')'):
+		if (cond.startswith('(')) and (cond.endswith(')')):
 			cond = cond[1:-1]
 		
-		inside = check_inside(cond, config, ctype)
+		inside = check_inside(cond, defaults, ctype)
 		
 		if (ctype == 'cnf') and (not inside):
 			return False
 		
-		if (ctype == 'dnf') and inside:
+		if (ctype == 'dnf') and (inside):
 			return True
 	
-	if ctype == 'cnf':
+	if (ctype == 'cnf'):
 		return True
 	return False
 
-def check_inside(text, config, ctype):
+def check_inside(text, defaults, ctype):
 	"Check for condition"
 	
-	if ctype == 'cnf':
+	if (ctype == 'cnf'):
 		conds = text.split('|')
 	else:
@@ -97,5 +96,5 @@
 	for cond in conds:
 		res = re.match(r'^(.*?)(!?=)(.*)$', cond)
-		if not res:
+		if (not res):
 			raise RuntimeError("Invalid condition: %s" % cond)
 		
@@ -104,12 +103,12 @@
 		condval = res.group(3)
 		
-		if not condname in config:
+		if (not condname in defaults):
 			varval = ''
 		else:
-			varval = config[condname]
+			varval = defaults[condname]
 			if (varval == '*'):
 				varval = 'y'
 		
-		if ctype == 'cnf':
+		if (ctype == 'cnf'):
 			if (oper == '=') and (condval == varval):
 				return True
@@ -124,11 +123,11 @@
 				return False
 	
-	if ctype == 'cnf':
+	if (ctype == 'cnf'):
 		return False
 	
 	return True
 
-def parse_rules(fname, rules):
-	"Parse rules file"
+def parse_config(fname, ask_names):
+	"Parse configuration file"
 	
 	inf = open(fname, 'r')
@@ -139,9 +138,9 @@
 	for line in inf:
 		
-		if line.startswith('!'):
+		if (line.startswith('!')):
 			# Ask a question
 			res = re.search(r'!\s*(?:\[(.*?)\])?\s*([^\s]+)\s*\((.*)\)\s*$', line)
 			
-			if not res:
+			if (not res):
 				raise RuntimeError("Weird line: %s" % line)
 			
@@ -150,10 +149,10 @@
 			vartype = res.group(3)
 			
-			rules.append((varname, vartype, name, choices, cond))
+			ask_names.append((varname, vartype, name, choices, cond))
 			name = ''
 			choices = []
 			continue
 		
-		if line.startswith('@'):
+		if (line.startswith('@')):
 			# Add new line into the 'choices' array
 			res = re.match(r'@\s*(?:\[(.*?)\])?\s*"(.*?)"\s*(.*)$', line)
@@ -165,10 +164,10 @@
 			continue
 		
-		if line.startswith('%'):
+		if (line.startswith('%')):
 			# Name of the option
 			name = line[1:].strip()
 			continue
 		
-		if line.startswith('#') or (line == '\n'):
+		if ((line.startswith('#')) or (line == '\n')):
 			# Comment or empty line
 			continue
@@ -182,5 +181,5 @@
 	"Return '*' if yes, ' ' if no"
 	
-	if default == 'y':
+	if (default == 'y'):
 		return '*'
 	
@@ -200,5 +199,5 @@
 	cnt = 0
 	for key, val in choices:
-		if (default) and (key == default):
+		if ((default) and (key == default)):
 			position = cnt
 		
@@ -208,141 +207,22 @@
 	(button, value) = xtui.choice_window(screen, name, 'Choose value', options, position)
 	
-	if button == 'cancel':
+	if (button == 'cancel'):
 		return None
 	
 	return choices[value][0]
 
-## Infer and verify configuration values.
-#
-# Augment @a config with values that can be inferred, purge invalid ones
-# and verify that all variables have a value (previously specified or inferred).
-#
-# @param config Configuration to work on
-# @param rules  Rules
-#
-# @return True if configuration is complete and valid, False
-#         otherwise.
-#
-def infer_verify_choices(config, rules):
-	"Infer and verify configuration values."
-	
-	for rule in rules:
-		varname, vartype, name, choices, cond = rule
-		
-		if cond and (not check_condition(cond, config, rules)):
-			continue
-		
-		if not varname in config:
-			value = None
-		else:
-			value = config[varname]
-		
-		if not validate_rule_value(rule, value):
-			value = None
-		
-		default = get_default_rule(rule)
-
-		#
-		# If we don't have a value but we do have
-		# a default, use it.
-		#
-		if value == None and default != None:
-			value = default
-			config[varname] = default
-		
-		if not varname in config:
+def check_choices(defaults, ask_names):
+	"Check whether all accessible variables have a default"
+	
+	for varname, vartype, name, choices, cond in ask_names:
+		if ((cond) and (not check_condition(cond, defaults, ask_names))):
+			continue
+		
+		if (not varname in defaults):
 			return False
 	
 	return True
 
-## Get default value from a rule.
-def get_default_rule(rule):
-	varname, vartype, name, choices, cond = rule
-	
-	default = None
-	
-	if vartype == 'choice':
-		# If there is just one option, use it
-		if len(choices) == 1:
-			default = choices[0][0]
-	elif vartype == 'y':
-		default = '*'
-	elif vartype == 'n':
-		default = 'n'
-	elif vartype == 'y/n':
-		default = 'y'
-	elif vartype == 'n/y':
-		default = 'n'
-	else:
-		raise RuntimeError("Unknown variable type: %s" % vartype)
-	
-	return default
-
-## Get option from a rule.
-#
-# @param rule  Rule for a variable
-# @param value Current value of the variable
-#
-# @return Option (string) to ask or None which means not to ask.
-#
-def get_rule_option(rule, value):
-	varname, vartype, name, choices, cond = rule
-	
-	option = None
-	
-	if vartype == 'choice':
-		# If there is just one option, don't ask
-		if len(choices) != 1:
-			if (value == None):
-				option = "?     %s --> " % name
-			else:
-				option = "      %s [%s] --> " % (name, value)
-	elif vartype == 'y':
-		pass
-	elif vartype == 'n':
-		pass
-	elif vartype == 'y/n':
-		option = "  <%s> %s " % (yes_no(value), name)
-	elif vartype == 'n/y':
-		option ="  <%s> %s " % (yes_no(value), name)
-	else:
-		raise RuntimeError("Unknown variable type: %s" % vartype)
-	
-	return option
-
-## Check if variable value is valid.
-#
-# @param rule  Rule for the variable
-# @param value Value of the variable
-#
-# @return True if valid, False if not valid.
-#
-def validate_rule_value(rule, value):
-	varname, vartype, name, choices, cond = rule
-	
-	if value == None:
-		return True
-	
-	if vartype == 'choice':
-		if not value in [choice[0] for choice in choices]:
-			return False
-	elif vartype == 'y':
-		if value != 'y':
-			return False
-	elif vartype == 'n':
-		if value != 'n':
-			return False
-	elif vartype == 'y/n':
-		if not value in ['y', 'n']:
-			return False
-	elif vartype == 'n/y':
-		if not value in ['y', 'n']:
-			return False
-	else:
-		raise RuntimeError("Unknown variable type: %s" % vartype)
-	
-	return True
-
-def create_output(mkname, mcname, config, rules):
+def create_output(mkname, mcname, defaults, ask_names):
 	"Create output configuration"
 	
@@ -358,7 +238,7 @@
 		sys.stderr.write("failed\n")
 	
-	if len(version) == 3:
+	if (len(version) == 3):
 		revision = version[1]
-		if version[0] != 1:
+		if (version[0] != 1):
 			revision += 'M'
 		revision += ' (%s)' % version[2]
@@ -379,26 +259,26 @@
 	defs = 'CONFIG_DEFS ='
 	
-	for varname, vartype, name, choices, cond in rules:
-		if cond and (not check_condition(cond, config, rules)):
-			continue
-		
-		if not varname in config:
-			value = ''
+	for varname, vartype, name, choices, cond in ask_names:
+		if ((cond) and (not check_condition(cond, defaults, ask_names))):
+			continue
+		
+		if (not varname in defaults):
+			default = ''
 		else:
-			value = config[varname]
-			if (value == '*'):
-				value = 'y'
-		
-		outmk.write('# %s\n%s = %s\n\n' % (name, varname, value))
-		
-		if vartype in ["y", "n", "y/n", "n/y"]:
-			if value == "y":
+			default = defaults[varname]
+			if (default == '*'):
+				default = 'y'
+		
+		outmk.write('# %s\n%s = %s\n\n' % (name, varname, default))
+		
+		if ((vartype == "y") or (vartype == "n") or (vartype == "y/n") or (vartype == "n/y")):
+			if (default == "y"):
 				outmc.write('/* %s */\n#define %s\n\n' % (name, varname))
 				defs += ' -D%s' % varname
 		else:
-			outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, value, varname, value))
-			defs += ' -D%s=%s -D%s_%s' % (varname, value, varname, value)
-	
-	if revision is not None:
+			outmc.write('/* %s */\n#define %s %s\n#define %s_%s\n\n' % (name, varname, default, varname, default))
+			defs += ' -D%s=%s -D%s_%s' % (varname, default, varname, default)
+	
+	if (revision is not None):
 		outmk.write('REVISION = %s\n' % revision)
 		outmc.write('#define REVISION %s\n' % revision)
@@ -419,7 +299,5 @@
 	return list
 
-## Ask user to choose a configuration profile.
-#
-def choose_profile(root, fname, screen, config):
+def read_preconfigured(root, fname, screen, defaults):
 	options = []
 	opt2path = {}
@@ -431,5 +309,5 @@
 		canon = os.path.join(path, fname)
 		
-		if os.path.isdir(path) and os.path.exists(canon) and os.path.isfile(canon):
+		if ((os.path.isdir(path)) and (os.path.exists(canon)) and (os.path.isfile(canon))):
 			subprofile = False
 			
@@ -439,91 +317,44 @@
 				subcanon = os.path.join(subpath, fname)
 				
-				if os.path.isdir(subpath) and os.path.exists(subcanon) and os.path.isfile(subcanon):
+				if ((os.path.isdir(subpath)) and (os.path.exists(subcanon)) and (os.path.isfile(subcanon))):
 					subprofile = True
 					options.append("%s (%s)" % (name, subname))
-					opt2path[cnt] = [name, subname]
+					opt2path[cnt] = (canon, subcanon)
 					cnt += 1
 			
-			if not subprofile:
+			if (not subprofile):
 				options.append(name)
-				opt2path[cnt] = [name]
+				opt2path[cnt] = (canon, None)
 				cnt += 1
 	
 	(button, value) = xtui.choice_window(screen, 'Load preconfigured defaults', 'Choose configuration profile', options, None)
 	
-	if button == 'cancel':
+	if (button == 'cancel'):
 		return None
 	
-	return opt2path[value]
-
-## Read presets from a configuration profile.
-#
-# @param profile Profile to load from (a list of string components)
-# @param config  Output configuration
-#
-def read_presets(profile, config):
-	path = os.path.join(PRESETS_DIR, profile[0], MAKEFILE)
-	read_config(path, config)
-	
-	if len(profile) > 1:
-		path = os.path.join(PRESETS_DIR, profile[0], profile[1], MAKEFILE)
-		read_config(path, config)
-
-## Parse profile name (relative OS path) into a list of components.
-#
-# @param profile_name Relative path (using OS separator)
-# @return             List of components
-#
-def parse_profile_name(profile_name):
-	profile = []
-	
-	head, tail = os.path.split(profile_name)
-	if head != '':
-		profile.append(head)
-	
-	profile.append(tail)
-	return profile
+	read_defaults(opt2path[value][0], defaults)
+	if (opt2path[value][1] != None):
+		read_defaults(opt2path[value][1], defaults)
 
 def main():
-	profile = None
-	config = {}
-	rules = []
-	
-	# Parse rules file
-	parse_rules(RULES_FILE, rules)
-	
-	# Input configuration file can be specified on command line
-	# otherwise configuration from previous run is used.
-	if len(sys.argv) >= 4:
-		profile = parse_profile_name(sys.argv[3])
-		read_presets(profile, config)
-	elif os.path.exists(MAKEFILE):
-		read_config(MAKEFILE, config)
-	
-	# Default mode: check values and regenerate configuration files
-	if (len(sys.argv) >= 3) and (sys.argv[2] == 'default'):
-		if (infer_verify_choices(config, rules)):
-			create_output(MAKEFILE, MACROS, config, rules)
+	defaults = {}
+	ask_names = []
+	
+	# Parse configuration file
+	parse_config(INPUT, ask_names)
+	
+	# Read defaults from previous run
+	if os.path.exists(MAKEFILE):
+		read_defaults(MAKEFILE, defaults)
+	
+	# Default mode: only check defaults and regenerate configuration
+	if ((len(sys.argv) >= 3) and (sys.argv[2] == 'default')):
+		if (check_choices(defaults, ask_names)):
+			create_output(MAKEFILE, MACROS, defaults, ask_names)
 			return 0
 	
-	# Hands-off mode: check values and regenerate configuration files,
-	# but no interactive fallback
-	if (len(sys.argv) >= 3) and (sys.argv[2] == 'hands-off'):
-		# We deliberately test sys.argv >= 4 because we do not want
-		# to read implicitly any possible previous run configuration
-		if len(sys.argv) < 4:
-			sys.stderr.write("Configuration error: No presets specified\n")
-			return 2
-		
-		if (infer_verify_choices(config, rules)):
-			create_output(MAKEFILE, MACROS, config, rules)
-			return 0
-		
-		sys.stderr.write("Configuration error: The presets are ambiguous\n")
-		return 1
-	
-	# Check mode: only check configuration
-	if (len(sys.argv) >= 3) and (sys.argv[2] == 'check'):
-		if infer_verify_choices(config, rules):
+	# Check mode: only check defaults
+	if ((len(sys.argv) >= 3) and (sys.argv[2] == 'check')):
+		if (check_choices(defaults, ask_names)):
 			return 0
 		return 1
@@ -535,8 +366,8 @@
 		while True:
 			
-			# Cancel out all values which have to be deduced
-			for varname, vartype, name, choices, cond in rules:
-				if (vartype == 'y') and (varname in config) and (config[varname] == '*'):
-					config[varname] = None
+			# Cancel out all defaults which have to be deduced
+			for varname, vartype, name, choices, cond in ask_names:
+				if ((vartype == 'y') and (varname in defaults) and (defaults[varname] == '*')):
+					defaults[varname] = None
 			
 			options = []
@@ -546,36 +377,50 @@
 			options.append("  --- Load preconfigured defaults ... ")
 			
-			for rule in rules:
-				varname, vartype, name, choices, cond = rule
-				
-				if cond and (not check_condition(cond, config, rules)):
+			for varname, vartype, name, choices, cond in ask_names:
+				
+				if ((cond) and (not check_condition(cond, defaults, ask_names))):
 					continue
 				
-				if varname == selname:
+				if (varname == selname):
 					position = cnt
 				
-				if not varname in config:
-					value = None
+				if (not varname in defaults):
+					default = None
 				else:
-					value = config[varname]
-				
-				if not validate_rule_value(rule, value):
-					value = None
-				
-				default = get_default_rule(rule)
-
-				#
-				# If we don't have a value but we do have
-				# a default, use it.
-				#
-				if value == None and default != None:
-					value = default
-					config[varname] = default
-				
-				option = get_rule_option(rule, value)
-				if option != None:
-					options.append(option)
+					default = defaults[varname]
+				
+				if (vartype == 'choice'):
+					# Check if the default is an acceptable value
+					if ((default) and (not default in [choice[0] for choice in choices])):
+						default = None
+						defaults.pop(varname)
+					
+					# If there is just one option, use it
+					if (len(choices) == 1):
+						defaults[varname] = choices[0][0]
+						continue
+					
+					if (default == None):
+						options.append("?     %s --> " % name)
+					else:
+						options.append("      %s [%s] --> " % (name, default))
+				elif (vartype == 'y'):
+					defaults[varname] = '*'
+					continue
+				elif (vartype == 'n'):
+					defaults[varname] = 'n'
+					continue
+				elif (vartype == 'y/n'):
+					if (default == None):
+						default = 'y'
+						defaults[varname] = default
+					options.append("  <%s> %s " % (yes_no(default), name))
+				elif (vartype == 'n/y'):
+					if (default == None):
+						default = 'n'
+						defaults[varname] = default
+					options.append("  <%s> %s " % (yes_no(default), name))
 				else:
-					continue
+					raise RuntimeError("Unknown variable type: %s" % vartype)
 				
 				opt2row[cnt] = (varname, vartype, name, choices)
@@ -588,9 +433,9 @@
 			(button, value) = xtui.choice_window(screen, 'HelenOS configuration', 'Choose configuration option', options, position)
 			
-			if button == 'cancel':
+			if (button == 'cancel'):
 				return 'Configuration canceled'
 			
-			if button == 'done':
-				if (infer_verify_choices(config, rules)):
+			if (button == 'done'):
+				if (check_choices(defaults, ask_names)):
 					break
 				else:
@@ -598,33 +443,31 @@
 					continue
 			
-			if value == 0:
-				profile = choose_profile(PRESETS_DIR, MAKEFILE, screen, config)
-				if profile != None:
-					read_presets(profile, config)
+			if (value == 0):
+				read_preconfigured(PRECONF, MAKEFILE, screen, defaults)
 				position = 1
 				continue
 			
 			position = None
-			if not value in opt2row:
+			if (not value in opt2row):
 				raise RuntimeError("Error selecting value: %s" % value)
 			
 			(selname, seltype, name, choices) = opt2row[value]
 			
-			if not selname in config:
-				value = None
+			if (not selname in defaults):
+					default = None
 			else:
-				value = config[selname]
-			
-			if seltype == 'choice':
-				config[selname] = subchoice(screen, name, choices, value)
-			elif (seltype == 'y/n') or (seltype == 'n/y'):
-				if config[selname] == 'y':
-					config[selname] = 'n'
+				default = defaults[selname]
+			
+			if (seltype == 'choice'):
+				defaults[selname] = subchoice(screen, name, choices, default)
+			elif ((seltype == 'y/n') or (seltype == 'n/y')):
+				if (defaults[selname] == 'y'):
+					defaults[selname] = 'n'
 				else:
-					config[selname] = 'y'
+					defaults[selname] = 'y'
 	finally:
 		xtui.screen_done(screen)
 	
-	create_output(MAKEFILE, MACROS, config, rules)
+	create_output(MAKEFILE, MACROS, defaults, ask_names)
 	return 0
 
