summaryrefslogtreecommitdiffstats
path: root/aconf/persistence/backends/augeas.lua
diff options
context:
space:
mode:
authorKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2014-03-10 22:45:18 +0200
committerKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2014-03-24 01:18:13 +0200
commit7d9c43916b0600ac4879dfe9793eab807a83ab2b (patch)
treeec54ed64c9a557b6ea4ad88d31138a02d3e0cd04 /aconf/persistence/backends/augeas.lua
parentcb6c243dc356ef1d46d7ddb96e6ea6ae007c6cca (diff)
downloadaconf-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.lua273
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