+--
+-- Yoink Build System
+-- Execute this script with Lua to prepare the project for compiling.
+--
+-- Copyright (c) 2011, Charles McGarvey
+-- Distributable under the terms and conditions of the 2-clause BSD
+-- license; see the file COPYING for a complete text of the license.
+--
+
+
+--local function trace(event, line)
+ --local s = debug.getinfo(2).short_src
+ --print(s .. ":" .. line)
+--end
+--debug.sethook(trace, "l")
+
+
+-- Convert the arguments into standard form. Each argument should be a
+-- string. A table is returned with the arguments' standard form as keys.
+-- If an argument has a value, it will correspond to the appropriate key in
+-- the table. This function supports autoconf-style arguments, including:
+-- 1. -h, --help produce a --help key with a value of true.
+-- 2. --enable-feature=[yes|no|whatever], --disable-feature produce a
+-- --enable-feature key with a value of true, false, or a string.
+-- 3. --with-package=[yes|no|whatever], --without-feature produce a
+-- --with-package key with a value of true, false, or a string.
+-- 4. SYMBOL=VALUE produce a SYMBOL key with VALUE as the string value.
+-- 5. Anything else will appear as a key in the table as-is with a value of
+-- true.
+-- If multiple arguments mapping to the same key are present in the
+-- argument list, the last one is used.
+local function parseArguments(...)
+ local result = {}
+ local filters = {}
+
+ local function setPair(key, value) result[key] = value end
+ local function setTrue(key) result[key] = true end
+ local function addLibrarySearchPath(path)
+ package.path = path .. "/?.lua;" .. package.path
+ package.cpath = path .. "/?.so;" .. package.cpath
+ end
+
+ table.insert(filters, {"^-h$", function() result["--help"] = true end})
+ table.insert(filters, {"^-L(.*)$", addLibrarySearchPath})
+
+ table.insert(filters, {"^(--enable%-[%w_-]+)=?(.*)$", setPair})
+ table.insert(filters, {"^--disable%-([%w_-]+)$", function(feature)
+ setPair("--enable-" .. feature, "no")
+ end})
+
+ table.insert(filters, {"^(--with%-[%w_-]+)=?(.*)$", setPair})
+ table.insert(filters, {"^--without%-([%w_-]+)$", function(package)
+ setPair("--with-" .. package, "no")
+ end})
+
+ table.insert(filters, {"^([%w_-]+)=(.*)$", setPair})
+ table.insert(filters, {"^([%w_-]+)$", setTrue})
+
+ for _,a in ipairs(arg)
+ do
+ for _,filter in pairs(filters)
+ do
+ local matches = {a:match(filter[1])}
+ if matches[1] then filter[2](unpack(matches)) break end
+ end
+ end
+
+ return result
+end
+
+
+local arguments = parseArguments(unpack(arg))
+local interactive = arguments["--interactive"]
+local rules = arguments["--rules"] or "options.lua"
+local configfile = arguments["--configfile"] or "config.mk"
+local printendmsg = arguments["--print-instructions"]
+local exportHeader = arguments["--export-header"]
+local exportTerms = arguments["--export-terms"]
+
+
+local util = require "utility"
+util.logfile = "config.log"
+
+
+local printInfo = util.printer("%s - %s\n")
+local printWarning = util.printer("\a%s - %s\n", "33")
+local printError = util.printer("\a%s - %s\n", "31")
+local function beginProcess(title, caption)
+ print(string.format("%s:\n", title))
+ local updater = util.printer(" [%3d%%] %s\n")
+ updater(0, caption)
+ return function(progress, caption)
+ if progress ~= nil
+ then
+ if 0.0 <= progress and progress <= 1.0 then progress = progress * 100 end
+ return updater(progress, caption)
+ end
+ print()
+ return 0
+ end
+end
+
+local function loadDialog(name, configfile)
+ local dialog
+ local result, err = pcall(function() dialog = require "dialog" end)
+ if not result
+ then
+ printWarning(err)
+ return nil
+ end
+ dialog.title = string.format("%s - %s Configuration", configfile, name)
+ printInfo = dialog.msgbox
+ printWarning = dialog.msgbox
+ printError = dialog.msgbox
+ beginProcess = dialog.gauge
+ return dialog
+end
+
+
+local function topologicalSort(lookup)
+ local dependants = util.copy(lookup, "v", {v = function() return 0 end})
+ for _,option in pairs(lookup)
+ do
+ for _,dep in ipairs(option:getDirectDependants())
+ do
+ dependants[dep] = dependants[dep] + 1
+ end
+ end
+
+ local sorted = {}
+ local queued = {}
+ for symbol,count in pairs(dependants)
+ do
+ if count == 0 then table.insert(queued, symbol) end
+ end
+ while 0 < #queued
+ do
+ local symbol = table.remove(queued)
+ table.insert(sorted, symbol)
+ for _,dep in ipairs(lookup[symbol]:getDirectDependants())
+ do
+ dependants[dep] = dependants[dep] - 1
+ if dependants[dep] == 0 then table.insert(queued, dep) end
+ end
+ end
+
+ local remaining = {}
+ for symbol,count in pairs(dependants)
+ do
+ if 0 < count then table.insert(remaining, symbol) end
+ end
+ if 0 < #remaining
+ then
+ printWarning("Q.A. Notice", "One or more circular dependencies were detected involving these symbols: "
+ .. table.concat(remaining, ", "))
+ for _,symbol in ipairs(remaining) do table.insert(sorted, symbol) end
+ end
+
+ return sorted
+end
+
+local function checkSymbols(symbols)
+ local check = topologicalSort(symbols)
+ local isErr = false
+ local updateTask = function() end
+ if 1 < #check
+ then
+ updateTask = beginProcess("Checking Symbols", "Resolving dependencies...", 6, 65)
+ end
+ for i = 1, #check
+ do
+ local option = symbols[check[i]]
+ if option:validate() == nil then isErr = true end
+ updateTask(i / #check,
+ (option:getSymbol() .. ": " .. option:toExpandedString()):truncate(60))
+ end
+ updateTask()
+ if isErr then return nil end
+ return true
+end
+
+
+---------------------------------------------------------------------------
+local Option = util.class()
+---------------------------------------------------------------------------
+
+function Option:__init(rule, lookup, objects)
+ self.name = rule.name
+ self.caption = rule.caption
+ self.help = rule.help
+ self.value = rule.value
+ self.symbol = rule.symbol
+ self.cmdline = rule.cmdline
+ self.config = rule.config
+ self.export = rule.export
+ self.visible = rule.visible
+ self.check = rule.check
+ self.temp = rule.temp
+ self.before = rule.before
+ self.after = rule.after
+ self.lookup = lookup or {}
+ self.objects = objects or {}
+
+ if type(self.check) == "function" then setfenv(self.check, self.lookup) end
+ if type(self.visible) == "function" then setfenv(self.visible, self.lookup) end
+
+ if self.symbol ~= nil
+ then
+ if self.lookup[self.symbol] ~= nil
+ then
+ printWarning("duplicate symbol defined:", self.symbol)
+ end
+ self.lookup[self.symbol] = self.value
+ self.objects[self.symbol] = self
+ end
+end
+
+-- Get the symbol of the option. The symbol is used as a reference in the
+-- string values of other options. It must be unique.
+function Option:getSymbol()
+ return self.symbol
+end
+
+-- Get the argument of the option.
+function Option:getArg()
+ return self.cmdline
+end
+
+-- Get the value of the option as a string.
+function Option:toString()
+ return tostring(self.lookup[self.symbol])
+end
+
+-- Get the value of the option as an expanded string. For most types of
+-- options, this is the same as Option:toString().
+function Option:toExpandedString()
+ return self:toString()
+end
+
+function Option:getDirectDependants()
+ return self.before or {}
+end
+
+function Option:getUnsortedDependants(dependants)
+ dependants = dependants or {}
+ if dependants[self.symbol] then return dependants end
+ dependants[self.symbol] = self
+ if self.before
+ then
+ for _,dep in ipairs(self.before)
+ do
+ local option = self.objects[dep]
+ if option
+ then
+ dependants = option:getUnsortedDependants(dependants)
+ else
+ printWarning("invalid dependency: " .. dep)
+ end
+ end
+ end
+ return dependants
+end
+
+function Option:addDependant(dependency)
+ self.before = self.before or {}
+ table.insert(self.before, dependency)
+end
+
+function Option:convertDependenciesToDependants()
+ if self.after
+ then
+ local dep = table.remove(self.after)
+ while dep ~= nil
+ do
+ local option = self.objects[dep]
+ if option
+ then
+ option:addDependant(self.symbol)
+ else
+ printWarning("invalid dependency: " .. dep)
+ end
+ dep = table.remove(self.after)
+ end
+ end
+end
+
+function Option:getObjects()
+ return self.objects
+end
+
+-- Check the value of the option for validity.
+-- Returns a valid value based on the given value, or nil (with an error
+-- message) if the value is invalid and could not be converted to a valid
+-- value.
+function Option:validate(value)
+ local err
+ if value == nil then value = self.lookup[self.symbol] end
+ self.problem = nil
+ if type(self.check) == "function"
+ then
+ value, err = self.check(value)
+ if value == nil then self.problem = err return nil, err end
+ end
+ self.lookup[self.symbol] = value
+ return value, err
+end
+
+-- Set the value of the option to the value in a new lookup table.
+function Option:applyTable(lookup)
+end
+
+-- Set the value of the option based on a table of argument flags.
+function Option:applyArguments()
+end
+
+-- Set the value of the option.
+function Option:set(value)
+ self.lookup[self.symbol] = value
+ if checkSymbols(self:getUnsortedDependants())
+ then
+ return self.lookup[self.symbol]
+ end
+end
+
+-- Write the symbol and current value to the config file.
+function Option:writeSymbol(fd)
+ if self.symbol ~= nil and not self.temp
+ then
+ fd:write(util.align(self.symbol, "= " .. tostring(self.lookup[self.symbol]), 16) .. "\n")
+ --fd:write(string.format("%s\t= %s\n", self.symbol, tostring(self.lookup[self.symbol])))
+ end
+end
+
+-- Write the option value to the config header file.
+function Option:writeCppLine(fd)
+end
+
+-- Write the option value as a string replacement to the sed file.
+function Option:writeSedLine(fd)
+ if self.export
+ then
+ local value = self:toExpandedString():gsub("/", "\\/")
+ local function writeLine(key)
+ key = tostring(key):gsub("/", "\\/")
+ fd:write(string.format("s/@%s@/%s/g\n", key, value))
+ end
+ if type(self.export) == "table"
+ then
+ for _,key in ipairs(self.export) do writeLine(key) end
+ else
+ writeLine(self.export)
+ end
+ end
+end
+
+-- Run an interactive menu with the given dialog context. The type of menu
+-- run will depend on the type of option.
+function Option:showMenu(dialog)
+end
+
+-- Get whether or not there is a problem with the current value of the
+-- option.
+function Option:isValid()
+ return type(self.problem) ~= "string"
+end
+
+-- Get a human-readable description of the problem(s) this option faces
+-- before it can be considered valid.
+function Option:getProblem()
+ return self.problem
+end
+
+-- Get the name to be used for this option in the interactive menu.
+function Option:getMenuItem()
+ if not self:isValid() then return self.name .. " <!>" end
+ return self.name
+end
+
+-- Get the label used to represent this option in a menu.
+function Option:getLabel()
+ return ""
+end
+
+-- Get whether or not this option should be visible in a menu of options.
+-- Returns true if option is visible, false otherwise.
+function Option:isVisible()
+ if type(self.visible) == "function" then return self.visible() end
+ return true
+end
+
+-- If the option has an argument flag, print out a line with information
+-- about the purpose of this option.
+function Option:printHelpLine()
+ if self.cmdline ~= nil
+ then
+ print(util.align(" " .. self:getArg(), self.caption, 32))
+ end
+end
+
+function Option:getHelpText()
+ local value = self:toString()
+ local help = self.help or "No help available.\n"
+ local name = self.name or "Unnamed"
+ local symbol = self:getSymbol() or "None"
+ local arg = self:getArg() or "None"
+
+ local problem = self:getProblem()
+ if problem then problem = "\nProblem(s):\n" .. problem
+ else problem = "" end
+
+ return [[
+
+]] .. help .. [[
+
+ Option Name: ]] .. name .. [[
+
+ Symbol: ]] .. symbol .. [[
+
+ Current Value: ]] .. value .. [[
+
+ Argument: ]] .. arg .. [[
+
+]] .. problem
+end
+
+
+---------------------------------------------------------------------------
+local StringOption = util.class(Option)
+---------------------------------------------------------------------------
+
+-- Get the expanded option value. Symbols which begin with dollar signs
+-- will be substituted for their values.
+function StringOption:toExpandedString()
+ local function expand(value)
+ _, value, count = pcall(string.gsub, value, "%$%(([%w_]+)%)", self.lookup)
+ if not count then count = 0 end
+ return value, count
+ end
+ local value = self.lookup[self.symbol]
+ local iterations = 0
+ while iterations < 8
+ do
+ local count = 0
+ value, count = expand(value)
+ if count == 0 then return value end
+ iterations = iterations + 1
+ end
+ return value
+end
+
+function StringOption:applyTable(lookup)
+ local value = lookup[self.symbol]
+ if type(value) == "string" then self.lookup[self.symbol] = tostring(value) end
+end
+
+function StringOption:applyArguments(args)
+ local value = args[self.cmdline]
+ if value ~= nil then self:validate(tostring(value)) end
+end
+
+function StringOption:writeCppLine(fd)
+ if self.config
+ then
+ local value = self:toExpandedString()
+ local function writeLine(key)
+ if type(self.value) == "string"
+ then
+ fd:write(string.format("#define %s %q\n", key, value))
+ else
+ fd:write(string.format("#define %s %s\n", key, value))
+ end
+ end
+ if type(self.config) == "table"
+ then
+ for _,key in ipairs(self.config) do writeLine(key) end
+ else
+ writeLine(self.config)
+ end
+ end
+end
+
+function StringOption:showMenu(dialog)
+ local code, item = dialog.inputbox(self.name, self.caption, self.lookup[self.symbol])
+ if code == 0 and item ~= self.lookup[self.symbol]
+ then
+ return self:set(item)
+ end
+end
+
+function StringOption:getLabel()
+ return self:toExpandedString()
+end
+
+
+---------------------------------------------------------------------------
+local EnumOption = util.class(StringOption)
+---------------------------------------------------------------------------
+
+-- An enumeration option is like a string, but it can only hold certain
+-- pre-determined values. In a rule, it is distinguished by its value key
+-- which refers to an array of the possible (string) values.
+function EnumOption:__init(rule, lookup, objects)
+ Option.__init(self, rule, lookup, objects)
+ self.lookup[self.symbol] = self.value[1]
+end
+
+function EnumOption:getArg()
+ if self.cmdline == nil then return nil end
+ return self.cmdline .. "=[" .. table.concat(self.value, "|") .. "]"
+end
+
+function EnumOption:validate(str)
+ str = str or self.lookup[self.symbol]
+ for _,value in ipairs(self.value)
+ do
+ if value == str then return Option.validate(self, str) end
+ end
+ self.problem = "The assigned value (" .. str .. ") is not a valid option."
+ return nil, self.problem
+end
+
+function EnumOption:applyArguments(args)
+ -- Just like applying arguments for strings, but if the argument is
+ -- false, assume the first value should be assigned.
+ local value = args[self.cmdline]
+ if value == false then self:validate(self.value[1]) return end
+ StringOption.applyArguments(self, args)
+end
+
+function EnumOption:writeCppLine(fd)
+ if self.config
+ then
+ local value = self:toString():upper()
+ local function writeLine(key)
+ key = tostring(key) .. "_" .. value
+ fd:write(string.format("#define %s 1\n", key))
+ end
+ if type(self.config) == "table"
+ then
+ for _,key in ipairs(self.config) do writeLine(key) end
+ else
+ writeLine(self.config)
+ end
+ end
+end
+
+function EnumOption:showMenu(dialog)
+ local menuitems = {}
+ for _,value in ipairs(self.value)
+ do
+ table.insert(menuitems, {value, ""})
+ end
+ local code, item = dialog.menu(
+ self.name,
+ self.caption,
+ menuitems,
+ self.lookup[self.symbol],
+ {["--ok-label"] = "Select"}
+ )
+ if code == 0 and item ~= self.lookup[self.symbol]
+ then
+ return self:set(item)
+ end
+end
+
+function EnumOption:getLabel()
+ return "[" .. StringOption.getLabel(self) .. "]"
+end
+
+
+---------------------------------------------------------------------------
+local BooleanOption = util.class(Option)
+---------------------------------------------------------------------------
+
+function BooleanOption:toString()
+ if self.lookup[self.symbol] then return "Yes" else return "No" end
+end
+
+function BooleanOption:applyTable(lookup)
+ local value = lookup[self.symbol]
+ if type(value) == "boolean" then self.lookup[self.symbol] = value end
+end
+
+function BooleanOption:applyArguments(args)
+ local value = args[self.cmdline]
+ if value == "yes" or value == "" then value = true end
+ if value == "no" then value = false end
+ if type(value) == "boolean" then self:validate(value) end
+end
+
+function BooleanOption:writeCppLine(fd)
+ if self.config
+ then
+ local value = self.lookup[self.symbol]
+ local function writeLine(key)
+ -- Reverse the value if key starts with a bang.
+ local value = value
+ if key:byte(1) == 33 then key = key:sub(2) value = not value end
+ if value
+ then
+ fd:write("#define " .. key .. " 1\n")
+ else
+ fd:write("/* #undef " .. key .. " */\n")
+ end
+ end
+ if type(self.config) == "table"
+ then
+ for _,key in ipairs(self.config) do writeLine(key) end
+ else
+ writeLine(self.config)
+ end
+ end
+end
+
+function BooleanOption:showMenu(dialog)
+ local item = not self.lookup[self.symbol]
+ return self:set(item)
+end
+
+function BooleanOption:getLabel()
+ if self.lookup[self.symbol] then return "[X]" else return "[ ]" end
+end
+
+
+---------------------------------------------------------------------------
+local NullOption = util.class(Option)
+---------------------------------------------------------------------------
+
+-- Null options don't really hold any useful information; they just
+-- translate to a blank line in the menuconfig. Any non-table object in a
+-- rule will become this type of option.
+function NullOption:__init()
+ self.name = ""
+ self.lookup = {}
+end
+
+function NullOption:validate()
+ return true
+end
+function NullOption:isVisible()
+ return true
+end
+
+
+---------------------------------------------------------------------------
+local GroupOption = util.class(Option)
+---------------------------------------------------------------------------
+
+local function optionFactory(rule, lookup, objects)
+ local jump = setmetatable({
+ string = StringOption,
+ number = StringOption,
+ table = EnumOption,
+ boolean = BooleanOption
+ }, {
+ __index = function() return GroupOption end
+ })
+ if type(rule) == "table"
+ then
+ return jump[type(rule.value)](rule, lookup, objects)
+ else
+ -- If the rule is not a table, just insert a placeholder option for
+ -- a blank line.
+ return NullOption()
+ end
+end
+
+-- A GroupOption is not really an option itself, but a group of options.
+function GroupOption:__init(rule, lookup, objects)
+ Option.__init(self, rule, lookup, objects)
+ self.children = {}
+
+ for _,child in ipairs(rule)
+ do
+ table.insert(self.children, optionFactory(child, self.lookup, self.objects))
+ end
+end
+
+function GroupOption:toString()
+ return "n/a"
+end
+
+-- Call the method with an arbitrary number of arguments to each
+-- sub-option.
+function GroupOption:recurse(method, ...)
+ for _,child in ipairs(self.children)
+ do
+ child[method](child, unpack(arg))
+ end
+end
+
+function GroupOption:convertDependenciesToDependants()
+ self:recurse("convertDependenciesToDependants")
+end
+
+-- Validate each sub-option in order. The validation will short-circuit
+-- and return nil (with an error message) upon the first failed validation.
+function GroupOption:validate()
+ for _,child in ipairs(self.children)
+ do
+ local result, err = child:validate()
+ if result == nil then return result, err end
+ end
+ return true
+end
+
+function GroupOption:isValid()
+ for _,child in ipairs(self.children)
+ do
+ if not child:isValid() then return false end
+ end
+ return true
+end
+
+function GroupOption:getProblem()
+ local problems = ""
+ for _,child in ipairs(self.children)
+ do
+ local problem = child:getProblem()
+ if problem
+ then
+ local symbol = child:getSymbol()
+ if symbol
+ then
+ problems = problems .. util.align(symbol .. ":", problem, 16) .. "\n"
+ else
+ problems = problems .. problem .. "\n"
+ end
+ util.align(symbol, problem, 16)
+ end
+ end
+ if problems == "" then return nil end
+ return util.trim(problems)
+end
+
+function GroupOption:applyTable(lookup)
+ self:recurse("applyTable", lookup)
+end
+
+function GroupOption:applyArguments(args)
+ self:recurse("applyArguments", args)
+end
+
+function GroupOption:writeSymbol(fd)
+ if self.name ~= nil then
+ fd:write(string.format("\n# %s\n", self.name))
+ self:recurse("writeSymbol", fd)
+ end
+end
+
+function GroupOption:writeCppLine(fd)
+ self:recurse("writeCppLine", fd)
+end
+
+function GroupOption:writeSedLine(fd)
+ self:recurse("writeSedLine", fd)
+end
+
+
+function GroupOption:showMenu(dialog)
+ local running, dirty, selected = true
+ while running
+ do
+ local menuitems = {}
+ for _,value in ipairs(self.children)
+ do
+ if type(value) ~= "table"
+ then
+ table.insert(menuitems, value)
+ elseif type(value.name) == "string" and value:isVisible()
+ then
+ local name = value:getMenuItem()
+ local label = value:getLabel()
+ local caption = ""
+ if type(value.caption) == "string"
+ then
+ caption = value.caption
+ menuitems["HELP " .. value.caption] = value
+ end
+ menuitems[name] = value
+ table.insert(menuitems, {name, label, caption})
+ end
+ end
+
+ local code, item = dialog.menu(
+ self.name,
+ self.caption,
+ menuitems,
+ selected,
+ {
+ ["--ok-label"] = "Select",
+ ["--cancel-label"] = "Exit",
+ ["--item-help"] = true,
+ ["--help-button"] = true
+ }
+ )
+
+ if code == 0
+ then
+ local value = menuitems[item]
+ if type(value) == "table"
+ then
+ if value:showMenu(dialog) ~= nil then dirty = "changed" end
+ selected = value:getMenuItem()
+ else
+ selected = value
+ end
+ elseif code == 2
+ then
+ local value = menuitems[item]
+ if value
+ then
+ dialog.msgbox(value.name, value:getHelpText(), {["--no-collapse"] = true})
+ selected = value:getMenuItem()
+ else
+ dialog.msgbox("No Help",
+ "Sorry, no help is available for this option.")
+ end
+ else
+ running = false
+ end
+ end
+ return dirty
+end
+
+function GroupOption:getLabel()
+ return "-->"
+end
+
+function GroupOption:printHelpLine()
+ if self:isVisible() and self.name ~= nil then
+ print(string.format("\n%s:", self.name))
+ self:recurse("printHelpLine")
+ end
+end
+
+
+-- Print arguments and usage information for all of the sub-options.
+function GroupOption:printHelp(name)
+ print([[
+
+This script prepares ]] .. name .. [[ for building on your system.
+Usage: ./configure [OPTION]... [VAR=VALUE]...]])
+
+ self:printHelpLine()
+ print()
+end
+
+-- Set values of the sub-options based on a table that is loaded from a
+-- file. The table is formatted as one or more lines with keys and values
+-- separated by an equal sign. The pound sign (#) serves to start a
+-- comment on the line. The first equal sign on a line is used as the
+-- separator, so keys cannot have an equal sign.
+function GroupOption:loadFromFile(filepath)
+ local lookup = {}
+ local fd = io.open(filepath)
+ if fd
+ then
+ for line in fd:lines()
+ do
+ if not line:find("^%s*#")
+ then
+ key, value = line:match("^%s*(%S.-)%s*=%s*(.*)%s*")
+ if value
+ then
+ if value:upper() == "TRUE" then value = true
+ elseif value:upper() == "FALSE" then value = false end
+ end
+ if key then lookup[key] = value end
+ end
+ end
+ fd:close()
+ end
+ self:applyTable(lookup)
+end
+
+-- Save a table with the current values of the sub-options to a file. The
+-- file can be reloaded with GroupOption:loadFromFile.
+function GroupOption:saveToFile(filepath)
+ local fd = io.open(filepath, "w")
+ if fd
+ then
+ fd:write(string.format("\n# Auto-generated: %s", os.date()))
+ self:writeSymbol(fd)
+ fd:write("\n")
+ else
+ printError("couldn't save config file to:", filepath)
+ end
+end
+
+-- Exports the config header file. The key-value pairs of sub-options with
+-- "config" defined will be written to the file.
+function GroupOption:exportHeader(filepath)
+ local fd = io.open(filepath, "w")
+ self:writeCppLine(fd)
+ fd:close()
+end
+
+-- Exports the search 'n replace file. The key-value pairs of sub-options
+-- with "export" defined will be written to the file.
+function GroupOption:exportTerms(filepath)
+ local fd = io.open(filepath, "w")
+ self:writeSedLine(fd)
+ fd:close()
+end
+
+-- Run menuconfig. This is different from showMenu in that this method
+-- tracks changes and returns an action: save, cancel, nochange or error.
+function GroupOption:runMenu()
+ local result, dirty = nil, false
+ while result == nil
+ do
+ if self:showMenu(dialog) ~= nil then dirty = true end
+ if not self:isValid()
+ then
+ local code = dialog.yesno("Oh drat!",
+ "There is at least one problem with the configuration, marked with <!>, and the configuration will not be saved. Do you really want to exit without saving?",
+ {
+ ["--extra-button"] = false,
+ ["--ok-label"] = "Exit",
+ ["--cancel-label"] = "Back"
+ })
+ if code == 0 or code == 255 then result = "error" end
+ elseif dirty
+ then
+ local code = dialog.yesno("Save Changes?",
+ "Your configuration has been altered. Do you want to save the changes?",
+ {
+ ["--ok-label"] = "Save",
+ ["--cancel-label"] = "Back",
+ ["--extra-button"] = true,
+ ["--extra-label"] = "Exit"
+ })
+ if code == 0 then result = "save"
+ elseif code == 3 or code == 255 then result = "cancel" end
+ else
+ result = "nochange"
+ end
+ end
+ return result
+end
+
+
+local loadFn = assert(loadfile(rules))
+local name, rules = loadFn()
+
+local options = optionFactory(rules)
+if arguments["--help"] then options:printHelp(name) os.exit() end
+
+options:loadFromFile(configfile)
+if exportHeader or exportTerms
+then
+ if exportHeader then options:exportHeader(exportHeader) end
+ if exportTerms then options:exportTerms(exportTerms) end
+ os.exit()
+end
+
+print()
+printInfo(name, "Preparing for building and installation on your system.\n")
+options:applyArguments(arguments)
+
+if interactive then loadDialog(name, configfile) end
+
+options:convertDependenciesToDependants()
+checkSymbols(options:getObjects())
+
+if dialog
+then
+ local action = options:runMenu()
+ if action == "exit" or action == "error"
+ then
+ print("configuration aborted by user request")
+ os.exit(1)
+ else
+ options:saveToFile(configfile)
+ print("configuration saved to " .. configfile)
+ end
+elseif options:isValid()
+then
+ options:saveToFile(configfile)
+else
+ printError("Uh oh!", [[
+There is at least one unresolved problem with the configuration.
+]] .. options:getProblem() .. "\n")
+ os.exit(2)
+end
+
+if printendmsg ~= "no"
+then
+ printInfo("Configuration complete!", [[
+To finish the installation, type:
+ make
+ make install
+]])
+end
+
+-- vi:ts=4 sw=4 tw=75
+