From a278292101608a2fc1c2ebb3a46f883b2ffc7076 Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Sat, 25 Aug 2012 10:36:28 +0100 Subject: CLOD: Add rudimentary list support so we can convert Gitano's group configs --- examples/configuration-lists.lua | 45 ++++++++++++++++++++++++ lib/clod.lua | 52 ++++++++++++++++++++++++++-- test/test-clod.lua | 75 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 examples/configuration-lists.lua diff --git a/examples/configuration-lists.lua b/examples/configuration-lists.lua new file mode 100644 index 0000000..cfecc91 --- /dev/null +++ b/examples/configuration-lists.lua @@ -0,0 +1,45 @@ +-- examples/configuration-lists.lua +-- +-- Demonstrate lists in clod configurations +-- +-- Copyright 2012 Daniel Silverstone +-- + +clod = require "clod" + +-- Clod's list support is a side-effect of its dictionary-like syntax +-- Essentially lists are not supported. In practice, Clod simulates +-- them by the ability to iterate subsets of the configuration. +-- You cannot usefully reorder lists, they're just dictionaries with +-- automatic keys. When Clod saves configurations, it changes trailing +-- i_[0-9]+ keys into a ["*"]. This is a tad 'magical' but never mind. + +local configuration = [[ +group.members["*"] "dsilvers" +group.members["*"] "liw" + +wont.see.this "ERROR" + +group.members["*"] "rjek" +group.members["*"] "robtaylor" +]] + +conf, err = clod.parse(configuration) +if not conf then + error(err) +end + +print("Input config:") +io.stdout:write(configuration) +print() +print() + +print("Group members in the conf (by conf:each('group.members') iterator):") +for key, value in conf:each("group.members") do + print(key, value) +end +print() +print() + +print("Serialised output:") +io.stdout:write(conf:serialise()) \ No newline at end of file diff --git a/lib/clod.lua b/lib/clod.lua index b1e5ccd..7696d5e 100644 --- a/lib/clod.lua +++ b/lib/clod.lua @@ -84,6 +84,20 @@ local function delete_entry(entry) end end +local function has_key(confmeta, key) + return confmeta.settings[key] ~= nil +end + +local function calculate_wild_key(confmeta, prefix) + local keyno = 1 + local keystr = ("%si_%s"):format(prefix, keyno) + while has_key(confmeta, keystr) do + keyno = keyno + 1 + keystr = ("%si_%s"):format(prefix, keyno) + end + return keystr +end + function settings_mt:__newindex(subkey, value) local meta = metadata[self] local confmeta = metadata[meta.conf] @@ -94,6 +108,11 @@ function settings_mt:__newindex(subkey, value) if meta.prefix then key = meta.prefix .. "." .. subkey end + local wild_prefix, last_key_element = key:match("^(.-)([^.]+)$") + if last_key_element == "*" then + -- Wild insert, so calculate a unique key to use + key = calculate_wild_key(confmeta, wild_prefix) + end if value == nil then -- removing an entry... if confmeta.settings[key] then @@ -178,6 +197,10 @@ function methods:serialise() local function serialise_entry(entry) local key, value, line = entry.key, entry.value, "" if key then + local wild_prefix = key:match("^(.-)%.i_[0-9]+$") + if wild_prefix then + key = wild_prefix .. '["*"]' + end local vtype = type(value) assert((vtype == "string" or vtype == "number" or vtype == "boolean"), "Unexpected " .. vtype .. " in key: " .. key) @@ -201,6 +224,24 @@ function methods:serialise() return tconcat(retstr, "\n") end +function methods:each(prefix) + if prefix then + prefix = "^" .. prefix:gsub("%.", "%%.") + end + local function iterator(confmeta, prev_key) + local next_key, next_value = next(confmeta.settings, prev_key) + if prefix then + while next_key and not next_key:match(prefix) do + next_key, next_value = next(confmeta.settings, next_key) + end + end + if next_key and next_value then + return next_key, next_value.value + end + end + return iterator, metadata[self], nil +end + -- Metamethods for clod instances function clod_mt:__index(key) if key == "settings" then @@ -236,14 +277,19 @@ local function parse_config(conf, confname) if type(value) == "table" or type(value) == "function" then error("Clod does not support " .. type(value) .. "s as values") end - return self[key](value) + return self[key](value, 1) end - function parse_mt:__call(value) + function parse_mt:__call(value, offset) if type(value) == "table" or type(value) == "function" then error("Clod does not support " .. type(value) .. "s as values") end local key = assert(keys[self]) - local curline = getinfo(2, "Snlf").currentline + local wild_prefix, last_key_element = key:match("^(.-)([^.]+)$") + if last_key_element == "*" then + -- Wild insert, so calculate a unique key to use + key = calculate_wild_key({settings=settings}, wild_prefix) + end + local curline = getinfo(2 + (offset or 0), "Snlf").currentline local entry = { key = key, value = value, lineno = curline } while last_entry.lineno < (curline - 1) do local empty = { diff --git a/test/test-clod.lua b/test/test-clod.lua index d64587c..f8baa77 100644 --- a/test/test-clod.lua +++ b/test/test-clod.lua @@ -181,6 +181,81 @@ function suite.insert_into_blank_two_similar_one_dissimilar_variant() assert(conf:serialise() == 'person.name "Lars"\nperson.age = 42\n\npants.style "Y-Front"\n') end +function suite.parse_wild_key() + local conf = assert(clod.parse('foo["*"] "bar"')) + assert(conf.settings.foo.i_1 == "bar") +end + +function suite.insert_wild_key() + local conf = assert(clod.parse("")) + conf.settings["foo.*"] = "bar" + assert(conf.settings.foo.i_1 == "bar") +end + +function suite.insert_two_wild_keys() + local conf = assert(clod.parse("")) + conf.settings["foo.*"] = "bar" + conf.settings["foo.*"] = "baz" + assert(conf.settings.foo.i_1 == "bar") + assert(conf.settings.foo.i_2 == "baz") +end + +function suite.serialise_wild_keys() + local input_str = 'foo["*"] "bar"\n' + local conf = assert(clod.parse(input_str)) + assert(conf:serialise() == input_str) +end + +function suite.iterate_all() + local input_str = [[ +project.name "Clod" +project.description "Badgering stoats" + +owner.name "Daniel" +]] + local conf = assert(clod.parse(input_str)) + local had_name, had_desc, had_owner + for k, v in conf:each() do + if k == "project.name" then + had_name = true + elseif k == "project.description" then + had_desc = true + elseif k == "owner.name" then + had_owner = true + else + assert(false) + end + end + assert(had_name) + assert(had_desc) + assert(had_owner) +end + +function suite.iterate_subset() + local input_str = [[ +project.name "Clod" +project.description "Badgering stoats" + +owner.name "Daniel" +]] + local conf = assert(clod.parse(input_str)) + local had_name, had_desc, had_owner + for k, v in conf:each("project") do + if k == "project.name" then + had_name = true + elseif k == "project.description" then + had_desc = true + elseif k == "owner.name" then + had_owner = true + else + assert(false) + end + end + assert(had_name) + assert(had_desc) + assert(not had_owner) +end + local count_ok = 0 for _, testname in ipairs(testnames) do -- print("Run: " .. testname) -- cgit v1.2.1