]> Dogcows Code - chaz/yoink/blobdiff - build/config.lua
build system enhancements
[chaz/yoink] / build / config.lua
diff --git a/build/config.lua b/build/config.lua
new file mode 100644 (file)
index 0000000..fa522cf
--- /dev/null
@@ -0,0 +1,998 @@
+--
+-- 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
+
This page took 0.035378 seconds and 4 git commands to generate.