diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-03-10 22:45:18 +0200 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2014-03-24 01:18:13 +0200 |
commit | 7d9c43916b0600ac4879dfe9793eab807a83ab2b (patch) | |
tree | ec54ed64c9a557b6ea4ad88d31138a02d3e0cd04 /aconf/persistence/backends/augeas.lua | |
parent | cb6c243dc356ef1d46d7ddb96e6ea6ae007c6cca (diff) | |
download | aconf-7d9c43916b0600ac4879dfe9793eab807a83ab2b.tar.bz2 aconf-7d9c43916b0600ac4879dfe9793eab807a83ab2b.tar.xz |
rename ACF2 to Alpine Configurator (aconf)
Diffstat (limited to 'aconf/persistence/backends/augeas.lua')
-rw-r--r-- | aconf/persistence/backends/augeas.lua | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/aconf/persistence/backends/augeas.lua b/aconf/persistence/backends/augeas.lua new file mode 100644 index 0000000..a448e65 --- /dev/null +++ b/aconf/persistence/backends/augeas.lua @@ -0,0 +1,273 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local topology = require('aconf.model.root').topology +local class = require('aconf.object').class +local pth = require('aconf.path') +local tostr = require('aconf.persistence.util').tostring + +local util = require('aconf.util') +local copy = util.copy + + +local stringy = require('stringy') + + +local function array_join(tbl, value) + local res = copy(tbl) + table.insert(res, value) + return res +end + +local function array_without_last(tbl) + local res = copy(tbl) + res[#res] = nil + return res +end + + +local function basename(path) + local res, pred = path:match('^.*/([#%w._-]+)([^/]*)$') + assert(res) + assert(res ~= '#') + assert(pred == '' or pred:match('^%[.+%]$')) + return res +end + +local function append_pred(path, pred) return path..'['..pred..']' end + + +local function key_mode(mode) return mode and stringy.startswith(mode, '@') end + +local function key(mode) + assert(key_mode(mode)) + return mode == '@' and '.' or mode:sub(2, -1) +end + +local function append_key_pred(path, mode, value) + return append_pred(path, key(mode).." = '"..value.."'") +end + + +local function conv_path(path) + local res = '/files' + if #path == 0 then return res, nil, {} end + + path = copy(path) + local mode + local keys = {} + + repeat + local comp = path[1] + + if comp == '#' or key_mode(comp) then + assert(not mode) + mode = comp + elseif not mode then + res = res..'/'..comp + keys = {} + else + if mode == '#' then + assert(type(comp) == 'number') + res = append_pred(res, comp) + else + assert(type(comp) == 'string' and comp:match('^[%w %_-%.]+$')) + res = append_key_pred(res, mode, comp) + table.insert(keys, key(mode)) + end + mode = nil + end + + table.remove(path, 1) + until #path == 0 + + return res, mode, keys +end + + +local function aug_top(path) + path = copy(path) + table.insert(path, 1, 'augeas') + return topology(path) +end + + +local backend = class() + +function backend:init() self.aug = require('augeas').init() end + +function backend:get(path, top) + local apath, mode, keys = conv_path(path) + local existence = top == true + if existence or not top then top = aug_top(path) end + + local tpe = top and top.type + local leaf = tpe ~= 'table' and not mode + + local matches = self.aug:match(apath..(not leaf and not mode and '/*' or '')) + + if #matches == 0 and not mode then return end + + if mode and #path > 1 and not self:get( + array_without_last(array_without_last(path)), true + ) then + return + end + + if not tpe and not mode then + if #matches > 1 then + leaf = false + mode = '#' + else + local children = self.aug:match(apath..'/*') + if #children > 0 then + leaf = false + matches = children + end + end + end + + if leaf then + assert(#matches == 1) + return self.aug:get(apath) + end + + if existence then return true end + + local names = {} + + for i, child in ipairs(matches) do + local name + if not mode then + name = basename(child) + name = tonumber(name) or name + if util.contains(keys, name) then name = nil end + elseif mode == '#' then name = i + else + name = self.aug:get(child..(mode == '@' and '' or '/'..key(mode))) + end + + if name and self:get(array_join(path, name), true) then + names[name] = true + end + end + + return util.keys(names) +end + +function backend:set(mods) + local gc = {} + + for _, mod in ipairs(mods) do + local path, value = unpack(mod) + + local function insert(path, new) + local apath, mode, keys = conv_path(path) + if mode then path[#path] = nil end + if #path == 0 then return apath, keys end + local name = path[#path] + + local parent = array_without_last(path) + local ppath, pmode = conv_path(parent) + + if pmode then + gc[pth.join(unpack(array_without_last(parent)))] = false + end + + if pmode == '#' then + local count = #self.aug:match(ppath) + while count < name do + insert(parent, true) + count = count + 1 + end + return apath, keys + end + + local matches = self.aug:match(apath) + local count = #matches + + if count > 0 and not new then return apath, keys end + + if key_mode(pmode) then + apath = pmode == '@' and append_key_pred( + ppath, '@', '' + ) or append_pred(ppath, 'count('..key(pmode)..') = 0') + + matches = self.aug:match(apath) + assert(#matches < 2) + apath = matches[1] or insert(parent, true) + + local key = key(pmode) + self.aug:set(apath..'/'..key, name) + + return apath, keys + end + + if #matches == 0 then + matches = self.aug:match(ppath..'/*') + + local function order(path) + local top = aug_top(path) + return top and top.order + end + local ord = order(path) + + for _, sibling in ipairs(matches) do + local sord = order(array_join(parent, basename(sibling))) + if sord and sord > ord then + self.aug:insert(sibling, name, true) + return apath, keys + end + end + end + + if #matches == 0 then + if new then self.aug:set(apath, nil) end + return apath, keys + end + + self.aug:insert(matches[#matches], name) + return append_pred(apath, count + 1), keys + end + + local apath, keys = insert(path) + local is_table = type(value) == 'table' + + if not (is_table or util.contains(keys, '.')) then + self.aug:set(apath, value ~= nil and tostr(value) or nil) + end + + util.setdefault(gc, pth.join(unpack(path)), true) + end + + for path, _ in pairs(gc) do + local p = pth.split(path) + while #p > 0 do + local value = self:get(p) + + if ( + type(value) == 'string' and value ~= '' + ) or ( + type(value) == 'table' and #value > 0 + ) then + break + end + + if gc[pth.join(unpack(p))] ~= false then self.aug:rm(conv_path(p)) end + p[#p] = nil + end + end + + if self.aug:save() ~= 0 then + print('Augeas save failed') + for _, ep in ipairs(self.aug:match('/augeas//error')) do + print(ep, self.aug:get(ep)) + end + assert(false) + end +end + + +return backend |