summaryrefslogtreecommitdiffstats
path: root/acf/persistence/backends/augeas.lua
blob: d80f806d772db13fe74979bd612b32547dfea79f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
--[[
Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]

module(..., package.seeall)

local topology = require('acf.model.root').topology
local pth = require('acf.path')

local util = require('acf.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 string.match(name, '^[^][/=)%s]+') or name
end

local function ipath(path, index) return path..'['..index..']' end


backend = require('acf.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 not string.match(comp, '^%.+$'))
      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(string.sub(strip_name(sibling), 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