summaryrefslogtreecommitdiffstats
path: root/acf2/persistence/backends/augeas.lua
diff options
context:
space:
mode:
Diffstat (limited to 'acf2/persistence/backends/augeas.lua')
-rw-r--r--acf2/persistence/backends/augeas.lua153
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