diff options
Diffstat (limited to 'acf2/persistence/backends/augeas.lua')
-rw-r--r-- | acf2/persistence/backends/augeas.lua | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/acf2/persistence/backends/augeas.lua b/acf2/persistence/backends/augeas.lua new file mode 100644 index 0000000..010a52b --- /dev/null +++ b/acf2/persistence/backends/augeas.lua @@ -0,0 +1,153 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local topology = require('acf2.model.root').topology +local pth = require('acf2.path') + +local util = require('acf2.util') +local copy = util.copy + + +local function aug_path(path) return pth.join('/files', unpack(path)) end + +local function strip_name(name) + return type(name) == 'string' and name:match('^[^][/=)%s]+') or name +end + +local function ipath(path, index) return path..'['..index..']' end + + +local backend = require('acf2.object').class() + +function backend:init() self.aug = require('augeas').init() end + +function backend:find(path, leaf) + util.map( + function(comp) + assert( + comp == strip_name(comp) and ( + type(comp) == 'number' or not comp:match('^%.+$') + ) + ) + end, + path + ) + local res = aug_path(path) + + if #self.aug:match(res) == 0 and #path > 1 and leaf then + local index = path[#path] + if type(index) == 'number' then + local ppath = copy(path) + table.remove(ppath) + ppath = aug_path(ppath) + + if #self.aug:match(ppath) > 0 and #self.aug:match( + ppath..'/*' + ) == 0 then + return ipath(ppath, index), ppath, index + end + end + end + + return res +end + +function backend:get(path, top) + local tpe = top and top.type + local leaf = tpe and tpe ~= 'table' + local apath, mvpath = self:find(path, leaf or not tpe) + + local matches = self.aug:match(apath) + if mvpath then + assert(#matches < 2) + leaf = true + end + + if #matches == 0 then return end + + if #matches > 1 then + assert(not leaf) + local res = {} + path = copy(path) + for i, _ in ipairs(matches) do + table.insert(path, i) + if self:get(path) then table.insert(res, i) end + table.remove(path) + end + return res + end + + local value = self.aug:get(matches[1]) + if value then return tpe == 'table' and {1} or value end + if leaf then return end + + local names = {} + for _, child in ipairs(self.aug:match(apath..'/*')) do + names[strip_name(pth.name(child))] = true + end + return util.keys(names) +end + +function backend:set(mods) + local gcpaths = {} + + for _, mod in ipairs(mods) do + local path, value = unpack(mod) + + local delete = value == nil + self.aug:rm(aug_path(path)..(delete and '' or '/*')) + + local apath, mvpath, index = self:find(path, type(value) ~= 'table') + local mpath = mvpath or apath + + if not delete then + if #self.aug:match(mpath) == 0 then + + local function order(path) + return topology('/augeas'..path).order + end + local ord = order(pth.join('/', unpack(path))) + + for _, sibling in ipairs(self.aug:match(pth.parent(mpath)..'/*')) do + if order(strip_name(sibling):sub(7, -1)) > ord then + self.aug:insert(sibling, pth.name(mpath), true) + break + end + end + end + + if mvpath then + local size = #self.aug:match(mvpath) + while size < index do + self.aug:insert(ipath(mvpath, size), pth.name(mvpath)) + size = size + 1 + end + end + end + + if type(value) == 'table' then value = nil end + if not delete or mvpath then self.aug:set(apath, value) + elseif apath > '/' then apath = pth.parent(apath) end + + if delete or value == '' then gcpaths[mpath] = true end + end + + local function gc(path) + if path == '/' or #self.aug:match(path..'/*') > 0 then return end + if self.aug:rm(path.."[. = '']") > 0 then gc(pth.parent(path)) end + end + for p, _ in pairs(gcpaths) do gc(p) 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 |