1require "table" 2require "io" 3require "string" 4 5-- 6local function auto( tab, key ) 7 return setmetatable( {}, { 8 __index = auto, 9 __newindex = assign, 10 parent = tab, 11 key = key 12 }) 13end 14 15local meta = { __index = auto } 16 17-- The if statement below prevents the table from being created if the value assigned is nil. 18-- This is, I think, technically correct but it might be desirable to use assignment to nil to force a table into existence. 19function assign( tab, key, val ) 20 -- if val ~= nil then 21 local oldmt = getmetatable( tab ) 22 oldmt.parent[ oldmt.key ] = tab 23 setmetatable( tab, meta ) 24 tab[ key ] = val 25 -- end 26end 27 28-- 29function AutoTable( _tab ) 30 return setmetatable( _tab, meta ) 31end 32 33 34 35 36-- exportstring( string ) 37-- returns a "Lua" portable version of the string 38local function exportstring( s ) 39 s = string.format( "%q",s ) 40 -- to replace 41 s = string.gsub( s,"\\\n","\\n" ) 42 s = string.gsub( s,"\r","\\r" ) 43 s = string.gsub( s,string.char(26),"\"..string.char(26)..\"" ) 44 return s 45end 46 47-- The Save Function 48function table.save( tbl,filename ) 49 50 local charS,charE = " ","\n" 51 local file,err 52 -- create a pseudo file that writes to a string and return the string 53 if not filename then 54 file = { write = function( self,newstr ) self.str = self.str..newstr end, str = "" } 55 charS,charE = "","" 56 -- write table to tmpfile 57 elseif filename == true or filename == 1 then 58 charS,charE,file = "","",io.tmpfile() 59 -- write table to file 60 -- use io.open here rather than io.output, since in windows when clicking on a file opened with io.output will create an error 61 else 62 file,err = io.open( filename, "w" ) 63 if err then return _,err end 64 end 65 -- initiate variables for save procedure 66 local tables,lookup = { tbl },{ [tbl] = 1 } 67 file:write( "return {"..charE ) 68 for idx,t in ipairs( tables ) do 69 if filename and filename ~= true and filename ~= 1 then 70 file:write( "-- Table: {"..idx.."}"..charE ) 71 end 72 file:write( "{"..charE ) 73 local thandled = {} 74 for i,v in ipairs( t ) do 75 thandled[i] = true 76 -- escape functions and userdata 77 if type( v ) ~= "userdata" then 78 -- only handle value 79 if type( v ) == "table" then 80 if not lookup[v] then 81 table.insert( tables, v ) 82 lookup[v] = #tables 83 end 84 file:write( charS.."{"..lookup[v].."},"..charE ) 85 elseif type( v ) == "function" then 86 file:write( charS.."loadstring("..exportstring(string.dump( v )).."),"..charE ) 87 else 88 local value = ( type( v ) == "string" and exportstring( v ) ) or tostring( v ) 89 file:write( charS..value..","..charE ) 90 end 91 end 92 end 93 for i,v in pairs( t ) do 94 -- escape functions and userdata 95 if (not thandled[i]) and type( v ) ~= "userdata" then 96 -- handle index 97 if type( i ) == "table" then 98 if not lookup[i] then 99 table.insert( tables,i ) 100 lookup[i] = #tables 101 end 102 file:write( charS.."[{"..lookup[i].."}]=" ) 103 else 104 local index = ( type( i ) == "string" and "["..exportstring( i ).."]" ) or string.format( "[%d]",i ) 105 file:write( charS..index.."=" ) 106 end 107 -- handle value 108 if type( v ) == "table" then 109 if not lookup[v] then 110 table.insert( tables,v ) 111 lookup[v] = #tables 112 end 113 file:write( "{"..lookup[v].."},"..charE ) 114 elseif type( v ) == "function" then 115 file:write( "loadstring("..exportstring(string.dump( v )).."),"..charE ) 116 else 117 local value = ( type( v ) == "string" and exportstring( v ) ) or tostring( v ) 118 file:write( value..","..charE ) 119 end 120 end 121 end 122 file:write( "},"..charE ) 123 end 124 file:write( "}" ) 125 -- Return Values 126 -- return stringtable from string 127 if not filename then 128 -- set marker for stringtable 129 return file.str.."--|" 130 -- return stringttable from file 131 elseif filename == true or filename == 1 then 132 file:seek ( "set" ) 133 -- no need to close file, it gets closed and removed automatically 134 -- set marker for stringtable 135 return file:read( "*a" ).."--|" 136 -- close file and return 1 137 else 138 file:close() 139 return 1 140 end 141end 142 143--// The Load Function 144function table.load( sfile ) 145 -- catch marker for stringtable 146 if string.sub( sfile,-3,-1 ) == "--|" then 147 tables,err = loadstring( sfile ) 148 else 149 tables,err = loadfile( sfile ) 150 end 151 if err then return _,err 152 end 153 tables = tables() 154 155 if tables == nil then 156 return _, "Config file is corrupted" 157 end 158 159 for idx = 1,#tables do 160 local tolinkv,tolinki = {},{} 161 for i,v in pairs( tables[idx] ) do 162 if type( v ) == "table" and tables[v[1]] then 163 table.insert( tolinkv, { i,tables[v[1]] } ) 164 end 165 if type( i ) == "table" and tables[i[1]] then 166 table.insert( tolinki, { i,tables[i[1]] } ) 167 end 168 end 169 170 -- we need to set AutoTable on every level, otherwise we get asserts for uknown settings 171 tables[idx] = AutoTable(tables[idx]) 172 173 -- link values, first due to possible changes of indices 174 for _,v in ipairs( tolinkv ) do 175 tables[idx][v[1]] = v[2] 176 end 177 -- link indices 178 for _,v in ipairs( tolinki ) do 179 tables[idx][v[2]],tables[idx][v[1]] = tables[idx][v[1]],nil 180 end 181 end 182 return AutoTable(tables[1]) 183end