--[[ IniFile: basically a Lua rewrite of SM's IniFile class that serves as the basis for the sm-ssc UserPrefs and ThemePrefs configuration systems. Note that this is a namespace, not a class per se. --]] -- TODO: move this into a more general section -- func takes a key and a value function foreach_by_sorted_keys(tbl, keys, func) table.sort(keys) for _, key in ipairs(keys) do func(key, tbl[key]) end end function foreach_ordered( tbl, func ) local string_keys= {} local number_keys= {} -- First person to to use this on a table that uses something else as keys -- gets to extend this function to cover more types. And a beating. -Kyz for k,_ in pairs(tbl) do if type(k) == "string" then table.insert(string_keys, k) elseif type(k) == "number" then table.insert(number_keys, k) end end -- iterate in sorted order foreach_by_sorted_keys(tbl, number_keys, func) foreach_by_sorted_keys(tbl, string_keys, func) end -- redeclared here for my sanity's sake -- TODO: declare these as global variables local RageFile = { READ = 1, WRITE = 2, STREAMED = 4, SLOW_FLUSH = 8 } -- IniFile namespace IniFile = { StrToKeyVal = function( str ) local key, value = str:match( "(.+)=(.*)" ) -- key is always a string, but value may be num, bool, or nil. -- do a few quick checks to see which one it is. -- if it's a nil, convert it to an empty string and return if value == nil then value = ""; return key, value; end -- if it's a number, convert it in place and return if tonumber(value) ~= nil then value = tonumber(value); return key, value; end -- not a number, so let's try a boolean value if value == "true" then value = true; elseif value == "false" then value = false; end return key, value end, ReadFile = function( file_path ) Trace( "IniFile.ReadFile( " .. file_path .. " )" ) local file = RageFileUtil.CreateRageFile() if not file:Open(file_path, RageFile.READ) then Warn( string.format("ReadFile(%s): %s",file_path,file:GetError()) ) file:destroy() return { } -- return a blank table end local tbl = { } local current = tbl while not file:AtEOF() do local str = file:GetLine() --ignore comments. if not str:find("^%s*#") then -- is this a section? local sec = str:match( "^%[(.+)%]" ) -- if so, set focus there; otherwise, try to -- read a key/value pair (ignore blank lines) if sec then -- if this section doesn't exist, create it tbl[sec] = tbl[sec] and tbl[sec] or { } current = tbl[sec] --Warn( "Switching section to " .. sec ) else local k, v = IniFile.StrToKeyVal( str ) if k and v ~= nil then current[k] = v end end end end file:Close() file:destroy() return tbl end, WriteFile = function( file_path, tbl ) Trace( "IniFile.WriteFile( " .. file_path .. " )" ) local file = RageFileUtil.CreateRageFile() if not file:Open(file_path, RageFile.WRITE) then Warn( string.format("WriteFile(%s): %s",file_path,file:GetError()) ) file:destroy() return false end -- declare functions so we can write with foreach_ordered local function put_pair( k, v ) file:PutLine( string.format("%s=%s", k, tostring(v)) ) end local function put_section( section, pair ) file:PutLine( "[" .. section .. "]" ) foreach_ordered( pair, put_pair ) file:PutLine("") -- put a blank line between sections end -- each base key is a section and its value is a -- table of key-value pairs under that section. foreach_ordered( tbl, put_section ) file:Close() file:destroy() return true end };