-- lib/clod.lua -- -- Configuration Language Organised (by) Dots -- -- Copyright 2012 Daniel Silverstone -- local type = type local getinfo = debug.getinfo local pcall = pcall local loadstring = loadstring local tconcat = table.concat local setmetatable = setmetatable local setfenv = setfenv -- metatable for clod config operations local clod_mt = {} -- metadata for clod config instances local metadata = setmetatable({}, {__mode = "k"}) -- Methods for clod instances local methods = {} function methods:serialise() local entries = metadata[self].entries local retstr = {} local function serialise_entry(entry) local key, value, line = entry.key, entry.value, "" if entry.key then local vtype = type(value) if vtype == "string" then line = ("%s %q"):format(key, value) elseif vtype == "number" then line = ("%s = %d"):format(key, value) elseif vtype == "boolean" then line = ("%s = %s"):format(key, value and "true" or "false") else line = ("-- %s was a %s. Cannot serialise"):format(key, vtype) end end retstr[#retstr+1] = line end while entries do serialise_entry(entries) entries = entries.next end serialise_entry({}) return tconcat(retstr, "\n") end -- Metamethods for clod instances function clod_mt:__index(key) if key == "settings" then return gen_settings(self) elseif methods[key] then return methods[key] end end local function parse_config(conf, confname) local ret = {} local settings = {} local last_entry = {lineno = 0} local front_entry = last_entry local keys = {} local parse_mt = {} local function gen_hook(key) local ret = setmetatable({}, parse_mt) keys[ret] = key return ret end function parse_mt:__index(key) local prefix = keys[self] if not prefix then -- This is a global indexing, so return a fresh entry return gen_hook(key) end -- A 'local' indexing, so combine with the key return gen_hook(("%s.%s"):format(prefix, key)) end function parse_mt:__setindex(key, value) -- This is the equivalent of 'foo = "bar"' instead of 'foo "bar"' return self[key](value) end function parse_mt:__call(value) local key = assert(keys[self]) local curline = getinfo(2, "Snlf").currentline local entry = { key = key, value = value, lineno = curline } while last_entry.lineno < (curline - 1) do local empty = { lineno = last_entry.lineno + 1, prev = last_entry } last_entry.next = empty last_entry = empty end last_entry.next = entry entry.prev = last_entry last_entry = entry settings[key] = entry end local func, msg = loadstring(conf, ("@%s"):format(confname or "clod-config")) if not func then return nil, msg end local globs = setmetatable({}, parse_mt) setfenv(func, globs) local ok, err = pcall(func) if not ok then return nil, err end -- Successfully loaded the settings, they're in settings and front_entry -- points to line zero which we can now detach front_entry.next.prev = nil front_entry = front_entry.next -- Construct a return object ready for magic local ret = setmetatable({}, clod_mt) metadata[ret] = { settings = settings, entries = front_entry, } return ret end return { parse = parse_config, }