summaryrefslogtreecommitdiffstats
path: root/acf
diff options
context:
space:
mode:
Diffstat (limited to 'acf')
-rw-r--r--acf/error.lua99
-rw-r--r--acf/init.lua18
-rw-r--r--acf/loader.lua23
-rw-r--r--acf/model/aaa.lua87
-rw-r--r--acf/model/combination.lua50
-rw-r--r--acf/model/field.lua241
-rw-r--r--acf/model/init.lua205
-rw-r--r--acf/model/model.lua204
-rw-r--r--acf/model/net.lua96
-rw-r--r--acf/model/node.lua275
-rw-r--r--acf/model/permission.lua22
-rw-r--r--acf/model/root.lua91
-rw-r--r--acf/model/set.lua47
-rw-r--r--acf/modules/awall.lua149
-rw-r--r--acf/modules/generic.lua14
-rw-r--r--acf/modules/net.lua25
-rw-r--r--acf/object.lua68
-rw-r--r--acf/path.lua111
-rw-r--r--acf/persistence/backends/augeas.lua153
-rw-r--r--acf/persistence/backends/files.lua107
-rw-r--r--acf/persistence/backends/json.lua79
-rw-r--r--acf/persistence/backends/null.lua10
-rw-r--r--acf/persistence/backends/volatile.lua46
-rw-r--r--acf/persistence/init.lua79
-rw-r--r--acf/persistence/util.lua22
-rw-r--r--acf/transaction/backend.lua70
-rw-r--r--acf/transaction/init.lua210
-rw-r--r--acf/util.lua84
28 files changed, 0 insertions, 2685 deletions
diff --git a/acf/error.lua b/acf/error.lua
deleted file mode 100644
index c3a8d63..0000000
--- a/acf/error.lua
+++ /dev/null
@@ -1,99 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local object = require('acf.object')
-local class = object.class
-
-local util = require('acf.util')
-
-local json = require('cjson')
-
-
-local ErrorTable = class()
-
-function ErrorTable:init() self.errors = {} end
-
-function ErrorTable:success() return not next(self.errors) end
-
-function ErrorTable:raise()
- if not self:success() then error(json.encode(self.errors)) end
-end
-
-
-local ErrorList = class(ErrorTable)
-
-function ErrorList:init(label)
- object.super(self, ErrorList):init()
- self.label = label
-end
-
-function ErrorList:insert(msg)
- table.insert(util.setdefault(self.errors, self.label, {}), msg)
-end
-
-
-M.ErrorDict = class(ErrorTable)
-
-function M.ErrorDict:collect(func, ...)
- local function pack(success, ...)
- local arg = {...}
- return success, success and arg or arg[1]
- end
-
- local arg = {...}
- local success, res = pack(
- xpcall(
- function() return func(unpack(arg)) end,
- function(err)
- local _, _, data = err:find('.-: (.+)')
- local success, res = pcall(json.decode, data)
- if success and type(res) == 'table' then return res end
- return data..'\n'..debug.traceback()
- end
- )
- )
-
- if success then return unpack(res) end
-
- if type(res) == 'table' then
- for label, errors in pairs(res) do
- for _, err in ipairs(errors) do
- table.insert(util.setdefault(self.errors, label, {}), err)
- end
- end
- else error(res) end
-end
-
-
-function M.raise(label, msg)
- local err = ErrorList(label)
- err:insert(msg)
- err:raise()
-end
-
-function M.relabel(label, ...)
- local err = M.ErrorDict()
- local res = {err:collect(...)}
- if err:success() then return unpack(res) end
-
- elist = ErrorList(label)
- for lbl, el in pairs(err.errors) do
- for _, e in ipairs(el) do elist:insert(lbl..': '..e) end
- end
- elist:raise()
-end
-
-function M.call(...)
- local err = M.ErrorDict()
- local res = {err:collect(...)}
- if err:success() then return true, unpack(res) end
- if err.errors.system then error(err.errors.system[1]) end
- return false, err.errors
-end
-
-
-return M
diff --git a/acf/init.lua b/acf/init.lua
deleted file mode 100644
index 62c76e9..0000000
--- a/acf/init.lua
+++ /dev/null
@@ -1,18 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-M.model = require('acf.model')
-
-require('acf.model.aaa')
-require('acf.loader')('modules')
-
-M.call = require('acf.error').call
-M.object = require('acf.object')
-M.path = require('acf.path')
-M.start_txn = require('acf.transaction')
-
-return M
diff --git a/acf/loader.lua b/acf/loader.lua
deleted file mode 100644
index 13b2db0..0000000
--- a/acf/loader.lua
+++ /dev/null
@@ -1,23 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-module(..., package.seeall)
-
-local pth = require('acf.path')
-
-local posix = require('posix')
-local stringy = require('stringy')
-
-return function(subdir)
- local comps = pth.split('acf/'..subdir)
- local res = {}
- for _, modfile in ipairs(posix.dir(pth.join(unpack(comps)))) do
- if stringy.endswith(modfile, '.lua') then
- local name = modfile:sub(1, -5)
- res[name] = require(table.concat(comps, '.')..'.'..name)
- end
- end
- return res
- end
diff --git a/acf/model/aaa.lua b/acf/model/aaa.lua
deleted file mode 100644
index 17ad98c..0000000
--- a/acf/model/aaa.lua
+++ /dev/null
@@ -1,87 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = require('acf.model')
-local object = require('acf.object')
-
-local digest = require('crypto').digest
-
-
-Role = M.new()
-Role.permissions = M.Set{type=M.Reference{scope='../../../permissions'}}
-
-
-local function hash_password(algorithm, salt, password)
- return algorithm..'$'..salt..'$'..digest(algorithm, salt..password)
-end
-
-local hash_pattern = '^(%w+)%$(%w+)%$%x+$'
-
-
-local Password = object.class(M.String)
-
-function Password:_validate(context, value)
- value = object.super(self, M.String):_validate(context, value)
- if not value or value:find(hash_pattern) then return value end
-
- local salt = ''
- for i = 1,12 do
- local c = math.random(48, 109)
- if c > 57 then c = c + 7 end
- if c > 90 then c = c + 6 end
- salt = salt..string.char(c)
- end
- return hash_password('sha256', salt, value)
-end
-
-
-User = M.new()
-User.password = Password
-User['real-name'] = M.String
-User.superuser = M.Boolean{default=false}
-User.roles = M.Set{type=M.Reference{scope='../../../roles'}}
-
-function User:check_password(password)
- if not self.password then return false end
- local _, _, algorithm, salt = self.password:find(hash_pattern)
- if not salt then return false end
- return hash_password(algorithm, salt, password) == self.password
-end
-
-function User:check_permission(permission)
- -- TODO audit trail
- print('check permission', permission)
-
- if self.superuser then return true end
-
- assert(getmetatable(self).txn:fetch('/auth/permissions')[permission])
-
- for _, role in M.node.pairs(self.roles, true) do
- for _, p in M.node.pairs(role.permissions, true) do
- if p == permission then return true end
- end
- end
- return false
-end
-
-
-Authentication = M.new()
-Authentication.users = M.Collection{type=User}
-Authentication.roles = M.Collection{type=Role}
-Authentication.permissions = M.Set{
- type=M.String,
- addr='/volatile/aaa/permissions'
-}
-
-M.register(
- 'auth',
- Authentication,
- {
- addr='/json'..require('posix').getcwd()..'/config/aaa.json',
- ui_name='Authentication'
- }
-)
-
-M.permission.defaults('/auth')
diff --git a/acf/model/combination.lua b/acf/model/combination.lua
deleted file mode 100644
index 19f84cc..0000000
--- a/acf/model/combination.lua
+++ /dev/null
@@ -1,50 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local err = require('acf.error')
-local raise = err.raise
-
-local fld = require('acf.model.field')
-local String = fld.String
-
-local to_field = require('acf.model.model').to_field
-
-local object = require('acf.object')
-local class = object.class
-local super = object.super
-
-
-local stringy = require('stringy')
-
-
-M.Range = class(String)
-
-function M.Range:init(params)
- super(self, M.Range):init(params)
- if not self.type then self.type = fld.Integer end
-end
-
-function M.Range:validate(context, value)
- local comps = stringy.split(value, '-')
- if #comps > 2 then raise(context.path, 'Invalid range') end
- for _, v in ipairs(comps) do to_field(self.type):_validate(context, v) end
-end
-
-
-M.Union = class(String)
-
-function M.Union:validate(context, value)
- super(self, M.Union):validate(context, value)
- for _, tpe in ipairs(self.types) do
- local field = to_field(tpe)
- if err.call(field.validate, field, context, value) then return end
- end
- raise(context.path, self.error or 'Invalid value')
-end
-
-
-return M
diff --git a/acf/model/field.lua b/acf/model/field.lua
deleted file mode 100644
index 4d539e8..0000000
--- a/acf/model/field.lua
+++ /dev/null
@@ -1,241 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local err = require('acf.error')
-local raise = err.raise
-
-local node = require('acf.model.node')
-
-local object = require('acf.object')
-local class = object.class
-local super = object.super
-
-local util = require('acf.util')
-
-
-local function contains(list, value)
- for k, v in ipairs(list) do if v == value then return true end end
- return false
-end
-
-M.Member = class()
-
-function M.Member:init(params)
- for k, v in pairs(params or {}) do
- if self[k] == nil then self[k] = v end
- end
-end
-
-function M.Member:auto_ui_name(name)
- if not name then return end
- return (name:sub(1, 1):upper()..name:sub(2)):gsub('-', ' ')
-end
-
-function M.Member:meta(context)
- return {
- name=self.name,
- description=self.description,
- ['ui-name']=self.ui_name or self:auto_ui_name(self.name)
- }
-end
-
-
-M.Field = class(M.Member)
-
-function M.Field:init(params)
- super(self, M.Field):init(params)
-
- if self.choice and not self['ui-choice'] then
- self['ui-choice'] = util.map(
- function(name) return self:auto_ui_name(name) end,
- self.choice
- )
- end
-
- if not self.widget then
- self.widget = self.choice and 'combobox' or 'field'
- end
-end
-
-function M.Field:meta(context)
- assert(self.dtype)
- local res = super(self, M.Field):meta(context)
-
- res.type = self.dtype
- res.required = self.required
- res.default = self.default
- res.choice = self.choice
- res.widget = self.widget
- res['ui-choice'] = self['ui-choice']
-
- return res
-end
-
-function M.Field:topology(context)
- return {
- {path=context.path, addr=context.addr, type=self.dtype}
- }
-end
-
-function M.Field:load(context)
- if not context.txn then return setmetatable({}, context) end
- local value = context.txn:get(context.addr)
- if value == nil then return self.default end
- return value
-end
-
-function M.Field:_validate(context, value)
- if self.required and value == nil then
- raise(context.path, 'Required value not set')
- end
- if self.choice and value ~= nil and not contains(self.choice, value) then
- raise(context.path, 'Invalid value')
- end
- if value ~= nil then self:validate(context, value) end
- return value
-end
-
-function M.Field:validate(context, value) end
-
-function M.Field:save(context, value)
- context.txn:set(context.addr, self:_validate(context, value))
-end
-
-function M.Field:validate_saved(context)
- self:save(context, self:load(context))
-end
-
-
-local Primitive = class(M.Field)
-
-function Primitive:validate(context, value)
- local t = self.dtype
- if type(value) ~= t then raise(context.path, 'Not a '..t) end
-end
-
-
-M.String = class(Primitive)
-
-function M.String:init(params)
- super(self, M.String):init(params)
- self.dtype = 'string'
-end
-
-function M.String:validate(context, value)
- super(self, M.String):validate(context, value)
- if self['max-length'] and value:len() > self['max-length'] then
- raise(context.path, 'Maximum length exceeded')
- end
- if self.pattern and not value:match('^'..self.pattern..'$') then
- raise(context.path, 'Invalid value')
- end
-end
-
-function M.String:meta(context)
- local res = super(self, M.String):meta(context)
- res['max-length'] = self['max-length']
- return res
-end
-
-
-M.Number = class(Primitive)
-
-function M.Number:init(params)
- super(self, M.Number):init(params)
- self.dtype = 'number'
-end
-
-function M.Number:_validate(context, value)
- return super(self, M.Number):_validate(
- context,
- value and tonumber(value) or value
- )
-end
-
-
-M.Integer = class(M.Number)
-
-function M.Integer:validate(context, value)
- super(self, M.Integer):validate(context, value)
- if math.floor(value) ~= value then raise(context.path, 'Not an integer') end
-end
-
-
-M.Boolean = class(Primitive)
-
-function M.Boolean:init(params)
- super(self, M.Boolean):init(params)
- self.dtype = 'boolean'
- self.widget = self.dtype
-end
-
-
-M.TreeNode = class(M.Field)
-
-function M.TreeNode:init(params)
- if not params.widget then params.widget = 'link' end
- super(self, M.TreeNode):init(params)
-end
-
-function M.TreeNode:topology(context)
- local res = super(self, M.TreeNode):topology(context)
- res[1].type = 'table'
- util.extend(res, node.topology(self:load(context, {create=true})))
- return res
-end
-
-function M.TreeNode:load(context, options)
- if context.txn and not (
- (
- options and options.create
- ) or self.create or context.txn:get(context.addr)
- ) then return end
- return self.itype(context, self.iparams)
-end
-
-function M.TreeNode:save(context, value)
- local path = context.path
-
- if value == path then return end
- if type(value) == 'string' then value = context.txn:fetch(value) end
- if object.isinstance(value, node.TreeNode) and node.path(value) == path then
- return
- end
-
- context.txn:set(context.addr)
-
- if value then
- if type(value) ~= 'table' then
- raise(path, 'Cannot assign primitive value')
- end
-
- context.txn:set(context.addr, {})
- local new = self:load(context, {create=true})
-
- local errors = err.ErrorDict()
- for k, v in node.pairs(value) do
- errors:collect(self.save_member, new, k, v)
- end
- errors:raise()
- end
-end
-
-function M.TreeNode.save_member(node, k, v) node[k] = v end
-
-
-M.Model = class(M.TreeNode)
-
-function M.Model:init(params)
- super(self, M.Model):init(params)
-
- assert(self.model)
- self.itype = self.model
- self.dtype = 'model'
-end
-
-
-return M
diff --git a/acf/model/init.lua b/acf/model/init.lua
deleted file mode 100644
index 1de5202..0000000
--- a/acf/model/init.lua
+++ /dev/null
@@ -1,205 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-M.error = require('acf.error')
-local raise = M.error.raise
-
-local combination = require('acf.model.combination')
-M.Union = combination.Union
-M.Range = combination.Range
-
-local fld = require('acf.model.field')
-local Field = fld.Field
-M.Boolean = fld.Boolean
-M.Integer = fld.Integer
-M.Number = fld.Number
-M.String = fld.String
-
-local model = require('acf.model.model')
-M.Action = model.Action
-M.new = model.new
-local to_field = model.to_field
-
-M.net = require('acf.model.net')
-
-local node = require('acf.model.node')
-M.node = {}
-for _, m in ipairs{
- 'List',
- 'Set',
- 'TreeNode',
- 'has_permission',
- 'insert',
- 'meta',
- 'mmeta',
- 'path',
- 'pairs',
- 'ipairs'
-} do M.node[m] = node[m] end
-
-M.permission = require('acf.model.permission')
-M.register = require('acf.model.root').register
-M.node.Set = require('acf.model.set').Set
-
-local object = require('acf.object')
-local class = object.class
-local isinstance = object.isinstance
-local super = object.super
-
-local pth = require('acf.path')
-local map = require('acf.util').map
-
-
-local stringy = require('stringy')
-
-
-M.Reference = class(Field)
-
-function M.Reference:init(params)
- if not params.widget then params.widget = 'reference' end
- super(self, M.Reference):init(params)
- self.dtype = 'reference'
- if not self.scope then self.scope = '/' end
-end
-
-function M.Reference:topology(context)
- local res = super(self, M.Reference):topology(context)
- res[1].scope = self.scope
- return res
-end
-
-function M.Reference:abs_scope(context)
- return pth.to_absolute(self.scope, node.path(context.parent))
-end
-
-function M.Reference:meta(context)
- local res = super(self, M.Reference):meta(context)
- res.scope = self:abs_scope(context)
- return res
-end
-
-function M.Reference:follow(context, value)
- return context.txn:fetch(pth.rawjoin(self:abs_scope(context), value))
-end
-
-function M.Reference:load(context, options)
- local ref = super(self, M.Reference):load(context)
- return (
- (not options or options.dereference ~= false) and context.txn and ref
- ) and self:follow(context, ref) or ref
-end
-
-function M.Reference:_validate(context, value)
- super(self, M.Reference):_validate(context, value)
-
- if value == nil then return end
-
- if isinstance(value, node.TreeNode) then value = node.path(value) end
-
- local path = context.path
- if type(value) ~= 'string' then raise(path, 'Path name must be string') end
-
- if pth.is_absolute(value) then
- local scope = self:abs_scope(context)
- local prefix = scope..'/'
- if not stringy.startswith(value, prefix) then
- raise(path, 'Reference out of scope ('..scope..')')
- end
- value = value:sub(prefix:len() + 1, -1)
- end
-
- -- assume one-level ref for now
- if #pth.split(value) > 1 then
- raise(path, 'Subtree references not yet supported')
- end
-
- -- TODO check instance type
- M.error.relabel(path, self.follow, self, context, value)
-
- return value
-end
-
-function M.Reference:deleted(context, addr)
- local target = self:load(context)
- if target and node.addr(target) == addr then
- -- TODO raise error for the target object
- raise(context.path, 'Refers to '..addr)
- end
-end
-
-
-M.Model = fld.Model
-
-
-M.Collection = class(fld.TreeNode)
-
-function M.Collection:init(params, itype)
- if params.create == nil then params.create = true end
- super(self, M.Collection):init(params)
-
- assert(self.type)
- self.itype = itype or node.Collection
- self.iparams = {
- destroy=self.destroy,
- layout=self.layout,
- required=self.required,
- ui_member=self.ui_member
- }
-
- self.dtype = 'collection'
-end
-
-function M.Collection:auto_ui_name(name)
- if not name then return end
- if name:sub(-1, -1) ~= 's' then name = name..'s' end
- return super(self, M.Collection):auto_ui_name(name)
-end
-
-function M.Collection:load(context, options)
- if not self.iparams.field then self.iparams.field = to_field(self.type) end
- return super(self, M.Collection):load(context, options)
-end
-
-
-M.List = class(M.Collection)
-function M.List:init(params) super(self, M.List):init(params, node.List) end
-
-
-M.Set = class(M.Collection)
-function M.Set:init(params)
- if not params.widget and isinstance(params.type, M.Reference) then
- params.widget = 'checkboxes'
- end
- super(self, M.Set):init(params, M.node.Set)
-end
-function M.Set.save_member(tn, k, v) node.insert(tn, v) end
-
-
--- experimental
-M.Mixed = class(M.Collection)
-
-function M.Mixed:init(params)
- params.type = M.Mixed
- super(self, M.Mixed):init(params, node.Mixed)
- self.pfield = Field()
-end
-
-function M.Mixed:topology(context) return {} end
-
-function M.Mixed:load(context)
- local value = self.pfield:load(context)
- if type(value) == 'table' then return super(self, M.Mixed):load(context) end
- return value
-end
-
-function M.Mixed:save(context, value)
- if type(value) == 'table' then super(self, M.Mixed):save(context, value)
- else self.pfield:save(context, value) end
-end
-
-
-return M
diff --git a/acf/model/model.lua b/acf/model/model.lua
deleted file mode 100644
index 6878497..0000000
--- a/acf/model/model.lua
+++ /dev/null
@@ -1,204 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local raise = require('acf.error').raise
-
-local fld = require('acf.model.field')
-local Field = fld.Field
-local Member = fld.Member
-
-local node = require('acf.model.node')
-local BoundMember = node.BoundMember
-
-local object = require('acf.object')
-local class = object.class
-local super = object.super
-local isinstance = object.isinstance
-
-local pth = require('acf.path')
-local util = require('acf.util')
-
-
-local function to_member(obj, params)
- if not params then params = {} end
- if object.issubclass(obj, M.Model) then
- params.model = obj
- return fld.Model(params)
- end
- local res = getmetatable(obj).class and obj or obj(params)
- assert(isinstance(res, Member))
- return res
-end
-
-function M.to_field(obj, params)
- local res = to_member(obj, params)
- assert(isinstance(res, Field))
- return res
-end
-
-
-M.Action = class(Member)
-
-function M.Action:init(params)
- super(self, M.Action):init(params)
-
- if not self.func then error('Function not defined for action') end
-
- if self.field then
- assert(type(self.field) == 'table')
- self.field = M.to_field(self.field)
- self.field.addr = '/null/action'
- end
-
- getmetatable(self).__newindex = function(t, k, v)
- assert(k == 'name')
- rawset(t, k, v)
- if t.field then t.field.name = v end
- end
-end
-
-function M.Action:meta(context)
- local res = super(self, M.Action):meta(context)
- if self.field then res.arg = self.field:meta(context) end
- return res
-end
-
-
-function M.new(base)
- if not base then base = M.Model end
-
- local res = class(base)
- res.members = base == node.TreeNode and {} or util.copy(base.members)
-
- local mt = util.copy(getmetatable(res))
-
- function mt.__index(t, k) return base[k] end
-
- function mt.__newindex(t, k, v)
- assert(v)
-
- local override = t[k]
- if type(v) == 'table' then v = to_member(v) end
-
- rawset(t, k, v)
-
- if isinstance(v, Member) then
- v.name = k
- if not override then table.insert(t.members, k) end
- end
- end
-
- setmetatable(res, mt)
-
- if isinstance(base, M.Model) then util.setdefaults(res, base) end
-
- return res
-end
-
-M.Model = M.new(node.TreeNode)
-
-function M.Model:init(context)
- super(self, M.Model):init(context)
-
- local mt = getmetatable(self)
-
- function mt.member(name, loose, tpe)
- local m = mt.class[name]
-
- if not tpe then tpe = Member end
- if not isinstance(m, tpe) then m = nil end
-
- if m == nil then
- if loose then return end
- raise(mt.path, 'Does not exist: '..name)
- end
-
- return BoundMember(self, name, m)
- end
-
- local function _members(tpe)
- local res = {}
- for _, name in ipairs(self.members) do
- local m = mt.member(name, true, tpe)
- if m then table.insert(res, m) end
- end
- return res
- end
-
- function mt.topology()
- local res = {}
- for _, f in ipairs(_members(Field)) do util.extend(res, f:topology()) end
- return res
- end
-
- function mt.load(k, options)
- local v = mt.class[k]
- local create = options and options.create
-
- if isinstance(v, Field) then
- v = BoundMember(self, k, v)
- if v.compute then return v:compute() end
- return v:load{create=create}
- end
-
- assert(mt.txn)
-
- if isinstance(v, M.Action) then
- local f = v.field and BoundMember(self, k, v.field)
- if create then return f and f:load{create=true} end
-
- return function(var)
- if f then f:save(var)
- elseif var ~= nil then
- raise(
- pth.join(mt.path, v.name),
- 'Action does not accept an input argument'
- )
- end
- local res = v.func(self, f and f:load())
- if f then mt.txn:set(v.field.addr) end
- return res
- end
- end
-
- return v
- end
-
- if not mt.txn then return end
-
-
- function mt.mmeta(name) return mt.member(name):meta() end
-
- function mt.save(k, v) return mt.member(k, false, Field):save(v) end
-
- local function tmeta(tpe)
- return util.map(function(m) return m:meta() end, _members(tpe))
- end
-
- mt.meta.type = 'model'
- mt.meta.fields = tmeta(Field)
- mt.meta.actions = tmeta(M.Action)
-
- function mt.members()
- return util.map(function(f) return f.name end, mt.meta.fields)
- end
-
- function mt.validate()
- for _, f in ipairs(_members(Field)) do
- if not f.compute then f:validate_saved() end
- end
- end
-
- if self.has_permission then
- function mt.has_permission(user, permission)
- return self:has_permission(user, permission)
- end
- end
-end
-
-
-return M
diff --git a/acf/model/net.lua b/acf/model/net.lua
deleted file mode 100644
index ae82f1e..0000000
--- a/acf/model/net.lua
+++ /dev/null
@@ -1,96 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local raise = require('acf.error').raise
-local Union = require('acf.model.combination').Union
-
-local fld = require('acf.model.field')
-local String = fld.String
-
-local object = require('acf.object')
-local class = object.class
-local super = object.super
-
-local update = require('acf.util').update
-
-
-local stringy = require('stringy')
-
-
-M.IPv4Address = class(String)
-
-function M.IPv4Address:validate(context, value)
- super(self, M.IPv4Address):validate(context, value)
- local function test(...)
- if #{...} ~= 4 then return true end
- for _, octet in ipairs{...} do
- if tonumber(octet) > 255 then return true end
- end
- end
- if test(value:match('^(%d+)%.(%d+)%.(%d+)%.(%d+)$')) then
- raise(context.path, 'Invalid IPv4 address')
- end
-end
-
-
-M.IPv6Address = class(String)
-
-function M.IPv6Address:validate(context, value)
- super(self, M.IPv6Address):validate(context, value)
-
- local function invalid() raise(context.path, 'Invalid IPv6 address') end
-
- if value == '' then invalid() end
-
- local comps = stringy.split(value, ':')
- if #comps < 3 then invalid() end
-
- local function collapse(i, ofs)
- if comps[i] > '' then return end
- if comps[i + ofs] > '' then invalid() end
- table.remove(comps, i)
- end
- collapse(1, 1)
- collapse(#comps, -1)
- if #comps > 8 then invalid() end
-
- local short = false
- for _, comp in ipairs(comps) do
- if comp == '' then
- if short then invalid() end
- short = true
- elseif not comp:match('^%x%x?%x?%x?$') then invalid() end
- end
- if (
- short and #comps == 3 and comps[2] == ''
- ) or (not short and #comps < 8) then
- invalid()
- end
-end
-
-
-M.IPAddress = class(Union)
-
-function M.IPAddress:init(params)
- super(self, M.IPAddress):init(
- update(
- params,
- {types={M.IPv4Address, M.IPv6Address}, error='Invalid IP address'}
- )
- )
-end
-
-
-M.Port = class(fld.Integer)
-
-function M.Port:validate(context, value)
- super(self, M.Port):validate(context, value)
- if value < 0 or value > 65535 then raise(context.path, 'Invalid port') end
-end
-
-
-return M
diff --git a/acf/model/node.lua b/acf/model/node.lua
deleted file mode 100644
index 56d3416..0000000
--- a/acf/model/node.lua
+++ /dev/null
@@ -1,275 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local raise = require('acf.error').raise
-
-local object = require('acf.object')
-local class = object.class
-local isinstance = object.isinstance
-local super = object.super
-
-local pth = require('acf.path')
-local util = require('acf.util')
-
-
-M.BoundMember = class()
-
-function M.BoundMember:init(parent, name, field)
- local pmt = getmetatable(parent)
- local mt = {}
-
- function mt.__index(t, k)
- local member = field[k]
- if type(member) ~= 'function' then return member end
- return function(self, ...)
- return member(
- field,
- {
- txn=pmt.txn,
- parent=parent,
- path=pth.join(pmt.path, name),
- addr=pth.to_absolute(
- field.addr or pth.escape(name), pmt.addr
- )
- },
- ...
- )
- end
- end
-
- setmetatable(self, mt)
-end
-
-
-M.TreeNode = class()
-
-function M.TreeNode:init(context)
- local mt = getmetatable(self)
- util.update(mt, context)
-
- mt.dereference = true
- mt.meta = {}
- function mt.get(k, create) return mt.load(k, {create=create}) end
-
- if not mt.txn then return end
-
- if mt.parent then
- mt.meta['ui-name'] = getmetatable(mt.parent).mmeta(
- pth.name(mt.path)
- )['ui-name']
- end
-
- function mt.save(k, v) rawset(self, k, v) end
- function mt.__index(t, k) return mt.get(k) end
- function mt.__newindex(t, k, v) mt.save(k, v) end
-
- function mt.has_permission(user, permission)
- local p = permission..mt.path
- if mt.txn:fetch('/auth/permissions')[p] then
- return user:check_permission(p)
- end
-
- if ({create=true, delete=true})[permission] then
- permission = 'modify'
- end
- return M.has_permission(mt.parent, user, permission)
- end
-
- mt.txn.validable[mt.path] = mt.addr
-end
-
-function M.TreeNode:fetch(path, create)
- if type(path) == 'string' then path = pth.split(path) end
-
- if #path == 0 then return self end
-
- local mt = getmetatable(self)
- local name = path[1]
- if not mt.member(name) then
- raise(mt.path, 'Member does not exist: '..name)
- end
-
- local next = mt.get(name, create)
- if next == nil and (not create or #path > 1) then
- raise(mt.path, 'Subordinate does not exist: '..name)
- end
-
- table.remove(path, 1)
- if #path > 0 and type(next) ~= 'table' then
- raise(pth.join(mt.path, name), 'Is a primitive value')
- end
-
- return M.TreeNode.fetch(next, path, create)
-end
-
-function M.TreeNode:search_refs(path)
- if type(path) == 'string' then path = pth.split(path) end
-
- if #path == 0 then return {} end
-
- local mt = getmetatable(self)
- local name = path[1]
- table.remove(path, 1)
-
- local function collect(name)
- local next = mt.load(name)
- if not next then return {} end
-
- local member = mt.member(name)
- if member.deleted then return {member} end
-
- return isinstance(next, M.TreeNode) and M.TreeNode.search_refs(
- next, path
- ) or {}
- end
-
- if name == pth.wildcard then
- local res = {}
- for _, member in ipairs(mt.members()) do
- util.extend(res, collect(member))
- end
- return res
- end
-
- return collect(name)
-end
-
-
-M.Collection = class(M.TreeNode)
-
-function M.Collection:init(context, params)
- super(self, M.Collection):init(context)
-
- self.init = nil
- self.fetch = nil
- self.search_refs = nil
-
- local mt = getmetatable(self)
- local field = M.BoundMember(self, pth.wildcard, params.field)
-
- function mt.topology() return field:topology() end
-
- function mt.member(name)
- return M.BoundMember(self, name, params.field)
- end
-
- function mt.load(k, options) return mt.member(k):load(options) end
-
- if not mt.txn then return end
-
- mt.meta.type = 'collection'
- mt.meta.members = field:meta()
- mt.meta['ui-member'] = params.ui_member or mt.meta['ui-name']:gsub('s$', '')
- mt.meta.widget = params.layout
-
- function mt.mmeta(name)
- local res = util.copy(mt.meta.members)
- if name ~= pth.wildcard then
- res['ui-name'] = mt.meta['ui-member']..' '..name
- end
- return res
- end
-
- function mt.members() return mt.txn:get(mt.addr) or {} end
-
- function mt.validate()
- if #mt.members() > 0 then return end
- if params.required then raise(mt.path, 'Collection cannot be empty') end
- if params.destroy then
- mt.txn:set(mt.addr)
- validate(mt.parent)
- end
- end
-
- function mt.save(k, v) mt.member(k):save(v) end
-end
-
-
-M.List = class(M.Collection)
-
-function M.List:init(context, params)
- super(self, M.List):init(context, params)
-
- local mt = getmetatable(self)
- mt.meta.type = 'list'
-
- local save = mt.save
- function mt.save(k, v)
- assert(type(k) == 'number')
- if v == nil then
- local len = #mt.members()
- while k < len do
- mt.save(k, mt.load(k + 1, {dereference=false}))
- k = k + 1
- end
- end
- save(k, v)
- end
-
- function mt.insert(v, i)
- local len = #mt.members()
- if not i then i = len + 1 end
- for j = len,i,-1 do mt.save(j + 1, mt.load(j, {dereference=false})) end
- mt.save(i, v)
- end
-end
-
-
--- experimental
-M.Mixed = class(M.Collection)
-
-function M.Mixed:init(context, params)
- super(self, M.Mixed):init(context, params)
-
- -- TODO dynamic meta: list non-leaf children
- local mt = getmetatable(self)
- mt.meta = {type='mixed', ['ui-name']=mt.path}
- function mt.mmeta(name)
- return {type='mixed', ['ui-name']=pth.join(mt.path, name)}
- end
-end
-
-
-local function meta_func(attr)
- return function(node, ...)
- local res = getmetatable(node)[attr]
- if type(res) == 'function' then return res(...) end
- return res
- end
-end
-
-for _, mf in ipairs{
- 'addr', 'has_permission', 'insert', 'meta', 'mmeta', 'path', 'topology'
-} do M[mf] = meta_func(mf) end
-
-
-function M.pairs(tbl, dereference)
- if not isinstance(tbl, M.TreeNode) then return pairs(tbl) end
-
- local mt = getmetatable(tbl)
- if dereference == nil then dereference = mt.dereference end
-
- local res = {}
- for _, member in ipairs(mt.members()) do
- res[member] = mt.load(member, {dereference=dereference})
- end
- return pairs(res)
-end
-
-local function _ipairs(mt, i)
- i = i + 1
- local v = mt.load(i)
- if v == nil then return end
- return i, v
-end
-function M.ipairs(tbl)
- if not isinstance(tbl, M.TreeNode) then return ipairs(tbl) end
- return _ipairs, getmetatable(tbl), 0
-end
-
-
-return M
diff --git a/acf/model/permission.lua b/acf/model/permission.lua
deleted file mode 100644
index 271d478..0000000
--- a/acf/model/permission.lua
+++ /dev/null
@@ -1,22 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local insert = require('acf.model.node').insert
-local start_txn = require('acf.transaction')
-
-function M.define(path, ...)
- local txn = start_txn()
- local db = txn:fetch('/auth/permissions')
- for _, permission in ipairs{...} do insert(db, permission..path) end
- txn:commit()
-end
-
-function M.defaults(path)
- M.define(path, 'read', 'create', 'modify', 'delete')
-end
-
-return M
diff --git a/acf/model/root.lua b/acf/model/root.lua
deleted file mode 100644
index 17c5cfb..0000000
--- a/acf/model/root.lua
+++ /dev/null
@@ -1,91 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local model = require('acf.model.model')
-local node = require('acf.model.node')
-local object = require('acf.object')
-local pth = require('acf.path')
-
-local util = require('acf.util')
-local setdefault = util.setdefault
-
-
-M.RootModel = model.new()
-
-function M.RootModel:init(txn)
- object.super(self, M.RootModel):init{txn=txn, path='/', addr='/null/root'}
-end
-
-function M.RootModel:has_permission(user, permission)
- return permission == 'read'
-end
-
-function M.RootModel:meta(path)
- local obj = self:fetch(path, true)
- if object.isinstance(obj, node.TreeNode) then return node.meta(obj) end
- return node.mmeta(self:fetch(pth.parent(path), true), pth.name(path))
-end
-
-
-local _topology = {}
-local order = 0
-
-function M.topology(addr, create)
- local top = _topology
- if type(addr) == 'table' then addr = util.copy(addr)
- else addr = pth.split(addr) end
-
- local function defaults(top)
- return util.setdefaults(top, {members={}, paths={}, referrers={}})
- end
-
- while #addr > 0 do
- if create then
- top = setdefault(defaults(top).members, addr[1], {})
- else
- top = top.members[addr[1]] or top.members[pth.wildcard]
- if not top then return end
- end
- table.remove(addr, 1)
- end
-
- return defaults(top)
-end
-
-function M.register(name, field, params)
- if not params then params = {} end
- params.create = true
- M.RootModel[name] = model.to_field(field, params)
-
- local root = M.RootModel()
-
- for _, record in ipairs(node.topology(root:fetch(name))) do
- local top = M.topology(record.addr, true)
-
- setdefault(top, 'order', order)
- order = order + 1
-
- local function set(k, v)
- setdefault(top, k, v)
- assert(top[k] == v)
- end
-
- set('type', record.type)
- table.insert(top.paths, record.path)
-
- if record.scope then
- local scope = node.addr(
- root:fetch(pth.to_absolute(record.scope, pth.parent(record.path)))
- )
- set('scope', scope)
- table.insert(M.topology(scope, true).referrers, record.path)
- end
- end
-end
-
-
-return M
diff --git a/acf/model/set.lua b/acf/model/set.lua
deleted file mode 100644
index b5b8dc3..0000000
--- a/acf/model/set.lua
+++ /dev/null
@@ -1,47 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local TreeNode = require('acf.model.field').TreeNode
-local npairs = require('acf.model.node').pairs
-local object = require('acf.object')
-
-
-M.Set = object.class(require('acf.model.node').List)
-
-function M.Set:init(context, params)
- assert(not object.isinstance(params.field, TreeNode))
- object.super(self, M.Set):init(context, params)
-
- local function find(value)
- for i, member in npairs(self) do
- if member == value then return i end
- end
- end
-
- local mt = getmetatable(self)
- mt.dereference = false
- mt.meta.type = 'set'
-
- function mt.get(k, create)
- local i = find(k)
- if i then return mt.load(i) end
- if create then return k end
- end
-
- function mt.__newindex(t, k, v)
- assert(v == nil)
- local i = find(k)
- if not i then return end
- mt.save(i, nil)
- end
-
- local insert = mt.insert
- function mt.insert(v) if not find(v) then insert(v) end end
-end
-
-
-return M
diff --git a/acf/modules/awall.lua b/acf/modules/awall.lua
deleted file mode 100644
index 01896b4..0000000
--- a/acf/modules/awall.lua
+++ /dev/null
@@ -1,149 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = require('acf.model')
-local object = require('acf.object')
-
-
-local Direction = object.class(M.String)
-function Direction:init(params)
- if not params then params = {} end
- params.choice = {'in', 'out'}
- object.super(self, Direction):init(params)
-end
-
-
--- TODO reference types?
-
-local IPSet = M.new()
--- TODO choices
-IPSet.type = M.String{required=true}
-IPSet.family = M.String{required=true, choice={'inet', 'inet6'}}
--- TODO only for bitmaps
-IPSet.range = M.Range{type=M.net.IPv4Address}
-
-local Service = M.new()
-Service.proto = M.String{required=true, ui_name='Protocol'}
-Service.port = M.Set{type=M.Range{type=M.net.Port}}
-Service['icmp-type'] = M.String{ui_name='ICMP type'}
-Service['ct-helper'] = M.String{ui_name='Connection tracking helper'}
-
--- TODO fw zone
-
-local Zone = M.new()
-Zone.iface = M.Set{type=M.String, ui_name='Interfaces'}
-Zone.addr = M.Set{type=M.String, ui_name='Addresses'}
-Zone['route-back'] = M.Boolean{default=false}
-
-local LogClass = M.new()
-LogClass.mode = M.String{
- required=true, default='log', choice={'log', 'nflog', 'ulog'}
-}
-LogClass.every = M.Integer{ui_name='Sampling frequency'}
-LogClass.limit = M.Integer
-LogClass.prefix = M.String
-LogClass.probability = M.Number
-LogClass.group = M.Integer
-LogClass.range = M.Integer
-LogClass.threshold = M.Integer
-
-local IPSetReference = M.new()
-IPSetReference.name = M.Reference{scope='/awall/ipset', required=true}
-IPSetReference.args = M.List{
- type=Direction, required=true, ui_name='Arguments'
-}
-
-local Rule = M.new()
-Rule['in'] = M.Set{
- type=M.Reference{scope='/awall/zone'}, ui_name='Ingress zones'
-}
-Rule.out = M.Set{
- type=M.Reference{scope='/awall/zone'}, ui_name='Egress zones'
-}
-Rule.src = M.Set{type=M.String, ui_name='Sources'}
-Rule.dest = M.Set{type=M.String, ui_name='Destinations'}
-Rule.ipset = M.Model{model=IPSetReference, ui_name='IP set'}
-Rule.ipsec = Direction{ui_name='Require IPsec'}
-Rule.service = M.Set{type=M.Reference{scope='/awall/service'}}
-Rule.action = M.String{choice={'accept'}}
-
-
-local PacketLogRule = M.new(Rule)
-PacketLogRule.log = M.Reference{scope='../../log', ui_name='Log class'}
-
--- TODO no service field
-local PolicyRule = M.new(PacketLogRule)
-PolicyRule.action = M.String{
- required=true, choice={'accept', 'drop', 'reject', 'tarpit'}
-}
-
-local Limit = M.new()
-Limit.count = M.Integer
-Limit.interval = M.Integer
-Limit.log = M.Reference{scope='../../../log'}
-
-local FilterRule = M.new(PolicyRule)
-FilterRule['conn-limit'] = M.Model{model=Limit, ui_name='Connection limit'}
-FilterRule['flow-limit'] = M.Model{model=Limit, ui_name='Flow limit'}
-FilterRule.dnat = M.net.IPv4Address{ui_name='DNAT target'}
-FilterRule['no-track'] = M.Boolean{default=false, ui_name='CT bypass'}
-FilterRule.related = M.List{type=Rule, ui_name='Related packet rules'}
-
-local DivertRule = M.new(Rule)
-DivertRule['to-port'] = M.Range{type=M.net.Port, ui_name='Target port'}
-
-local NATRule = M.new(DivertRule)
-NATRule['to-addr'] = M.Range{type=M.net.IPv4Address, ui_name='Target address'}
-
-local MarkRule = M.new(Rule)
-MarkRule.mark = M.Integer{required=true}
-
-local ClampMSSRule = M.new(Rule)
-ClampMSSRule.mss = M.Integer{ui_name='MSS'}
-
-
-local AWall = M.new()
--- TODO differentiate lists?
-AWall.service = M.Collection{type=M.List{type=Service}}
-AWall.zone = M.Collection{type=Zone}
-AWall.log = M.Collection{
- type=LogClass, ui_name='Log classes', ui_member='Log class'
-}
-AWall.policy = M.List{type=PolicyRule, ui_name='Policies', ui_member='Policy'}
-AWall['packet-log'] = M.List{
- type=PacketLogRule, ui_name='Logging', ui_member='Logging rule'
-}
-AWall.filter = M.List{type=FilterRule}
-AWall.dnat = M.List{type=NATRule, ui_name='DNAT', ui_member='DNAT rule'}
-AWall.snat = M.List{type=NATRule, ui_name='SNAT', ui_member='SNAT rule'}
-AWall.mark = M.List{
- type=MarkRule, ui_name='Packet marking', ui_member='Packet marking rule'
-}
-AWall['route-track'] = M.List{
- type=MarkRule, ui_name='Route tracking', ui_member='Route tracking rule'
-}
-AWall.tproxy = M.List{
- type=DivertRule,
- ui_name='Transparent proxy',
- ui_member='Transparent proxy rule'
-}
-AWall['clamp-mss'] = M.List{
- type=ClampMSSRule, ui_name='MSS clamping', ui_member='MSS clamping rule'
-}
-AWall['no-track'] = M.List{
- type=Rule, ui_name='CT bypass', ui_member='Connection tracking bypass rule'
-}
-AWall.ipset = M.Collection{type=IPSet, ui_name='IP sets', ui_member='IP set'}
-
-M.register(
- 'awall',
- AWall,
- {
- addr='/json'..require('posix').getcwd()..'/config/awall.json',
- ui_name='Alpine Wall'
- }
-)
-
-M.permission.defaults('/awall')
diff --git a/acf/modules/generic.lua b/acf/modules/generic.lua
deleted file mode 100644
index 5477bd0..0000000
--- a/acf/modules/generic.lua
+++ /dev/null
@@ -1,14 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
--- provided as an example, to be removed from production version
-
-local M = require('acf.model')
-
-M.register('proc', M.Mixed, {addr='/files/proc', ui_name='/proc'})
-M.permission.defaults('/proc')
-
-M.register('augeas', M.Mixed, {addr='/augeas'})
-M.permission.defaults('/augeas')
diff --git a/acf/modules/net.lua b/acf/modules/net.lua
deleted file mode 100644
index ad6ba95..0000000
--- a/acf/modules/net.lua
+++ /dev/null
@@ -1,25 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = require('acf.model')
-
-local Host = M.new()
-Host.address = M.net.IPAddress{addr='ipaddr'}
-Host.canonical = M.String{ui_name='Canonical name'}
-Host.alias = M.Set{type=M.String, ui_name='Aliases', ui_member='Alias'}
-
-local Resolv = M.new()
-Resolv.servers = M.List{type=M.net.IPAddress, addr='nameserver'}
-Resolv['search-domains'] = M.List{type=M.String, addr='search/domain'}
-
-local Net = M.new()
-Net['host-name'] = M.String{addr='/augeas/etc/hostname/hostname'}
-Net.hosts = M.List{type=Host, addr='/augeas/etc/hosts'}
-Net.resolver = M.Model{
- model=Resolv, addr='/augeas/etc/resolv.conf', ui_name='DNS resolver'
-}
-
-M.register('net', Net, {ui_name='Network'})
-M.permission.defaults('/net')
diff --git a/acf/object.lua b/acf/object.lua
deleted file mode 100644
index 64fbb9c..0000000
--- a/acf/object.lua
+++ /dev/null
@@ -1,68 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-function M.class(base)
- local cls = {}
-
- local mt = {
- __call=function(self, ...)
- local obj = {}
- setmetatable(obj, {__index=cls, class=cls})
- obj:init(...)
- return obj
- end
- }
-
- if not base and M.Object then base = M.Object end
- if base then
- cls._base = base
- mt.__index = base
- end
-
- return setmetatable(cls, mt)
-end
-
-
-M.Object = M.class()
-
-function M.Object:init(...) end
-
-
-function M.super(obj, cls)
- assert(M.isinstance(obj, cls))
-
- local mt = {}
-
- function mt.__index(t, k)
- local v = cls._base[k]
- if type(v) ~= 'function' then return v end
- return function(...)
- local arg = {...}
- arg[1] = obj
- return v(unpack(arg))
- end
- end
-
- return setmetatable({}, mt)
-end
-
-
-function M.issubclass(cls1, cls2)
- if cls1 == cls2 then return true end
- if cls1._base then return M.issubclass(cls1._base, cls2) end
- return false
-end
-
-function M.isinstance(obj, cls)
- if not obj or type(obj) ~= 'table' then return false end
- local mt = getmetatable(obj)
- if not mt then return false end
- return type(obj) == 'table' and mt.class and M.issubclass(mt.class, cls)
-end
-
-
-return M
diff --git a/acf/path.lua b/acf/path.lua
deleted file mode 100644
index d824141..0000000
--- a/acf/path.lua
+++ /dev/null
@@ -1,111 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local map = require('acf.util').map
-
-
-local up = {}
-M.wildcard = {}
-local special = {['..']=up, ['*']=M.wildcard}
-
-
-function M.is_absolute(path) return path:sub(1, 1) == '/' end
-
-
-function M.escape(comp)
- for symbol, item in pairs(special) do
- if comp == item then return symbol end
- end
- if type(comp) == 'number' then return tostring(comp) end
- local res = comp:gsub('([\\/])', '\\%1')
- return (special[res] or tonumber(res)) and '\\'..res or res
-end
-
-
-function M.rawjoin(p1, p2, ...)
- if not p2 then return p1 end
- if not M.is_absolute(p2) then p2 = '/'..p2 end
- return M.rawjoin((p1 == '/' and '' or p1)..p2, ...)
-end
-
-function M.join(parent, ...)
- return M.rawjoin(parent, unpack(map(M.escape, {...})))
-end
-
-
-function M.split(path)
- local res = {}
- local comp = ''
- local escaped
-
- local function merge(s)
- if s > '' then
- table.insert(res, not escaped and (special[s] or tonumber(s)) or s)
- end
- end
-
- while true do
- local prefix, sep, suffix = path:match('([^\\/]*)([\\/])(.*)')
- if not prefix then
- merge(comp..path)
- return res
- end
-
- comp = comp..prefix
- if sep == '\\' then
- comp = comp..suffix:sub(1, 1)
- escaped = true
- path = suffix:sub(2, -1)
- else
- merge(comp)
- comp = ''
- escaped = false
- path = suffix
- end
- end
-end
-
-
-function M.is_unique(path)
- for _, comp in ipairs(M.split(path)) do
- if comp == M.wildcard then return false end
- end
- return true
-end
-
-
-function M.to_absolute(path, base)
- if not M.is_absolute(path) then
- path = base..(base ~= '/' and '/' or '')..path
- end
- local comps = M.split(path)
- local i = 1
- while i <= #comps do
- if comps[i] == up then
- if i == 1 then error('Invalid path: '..path) end
- table.remove(comps, i - 1)
- table.remove(comps, i - 1)
- i = i - 1
- else i = i + 1 end
- end
- return M.join('/', unpack(comps))
-end
-
-
-function M.parent(path)
- local comps = M.split(path)
- table.remove(comps)
- return M.join('/', unpack(comps))
-end
-
-function M.name(path)
- local comps = M.split(path)
- return comps[#comps]
-end
-
-
-return M
diff --git a/acf/persistence/backends/augeas.lua b/acf/persistence/backends/augeas.lua
deleted file mode 100644
index 4cf623a..0000000
--- a/acf/persistence/backends/augeas.lua
+++ /dev/null
@@ -1,153 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-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 name:match('^[^][/=)%s]+') or name
-end
-
-local function ipath(path, index) return path..'['..index..']' end
-
-
-local 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 (
- 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
diff --git a/acf/persistence/backends/files.lua b/acf/persistence/backends/files.lua
deleted file mode 100644
index 7d03e12..0000000
--- a/acf/persistence/backends/files.lua
+++ /dev/null
@@ -1,107 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local topology = require('acf.model.root').topology
-local pth = require('acf.path')
-local util = require('acf.persistence.util')
-local copy = require('acf.util').copy
-
-local posix = require('posix')
-local stringy = require('stringy')
-
-
-local function get_scope(top)
- if not top or top.type ~= 'reference' or not pth.is_unique(top.scope) then
- return
- end
-
- return stringy.startswith(
- top.scope, '/files/'
- ) and top.scope:sub(7, -1) or nil
-end
-
-
-local backend = require('acf.object').class()
-
--- TODO cache expiration
-function backend:init() self.cache = {} end
-
-function backend:get(path, top)
- local name = pth.join('/', unpack(path))
-
- if not self.cache[name] then
- local t = posix.stat(name, 'type')
- if not t then return end
-
- if t == 'regular' then
- self.cache[name] = util.read_file(name)
-
- elseif t == 'link' then
- -- TODO handle relative symlinks
- local target = posix.readlink(name)
- assert(target)
-
- local scope = get_scope(top)
- assert(scope)
- scope = scope..'/'
-
- local slen = scope:len()
- assert(target:sub(1, slen) == scope)
- return target:sub(slen + 1, -1)
-
- elseif t == 'directory' then
- local res = {}
- for _, fname in ipairs(posix.dir(name)) do
- if not ({['.']=true, ['..']=true})[fname] then
- table.insert(res, pth.name(fname))
- end
- end
- return res
-
- else error('Unsupported file type: '..name) end
- end
-
- return self.cache[name]
-end
-
-function backend:set(mods)
- for _, mod in pairs(mods) do
- local path, value = unpack(mod)
- local name = pth.join('/', unpack(path))
-
- if value == nil then
- print('DEL', name)
-
- local t = posix.stat(name, 'type')
- if t == 'directory' then
- assert(posix.rmdir(name))
- elseif t then assert(os.remove(name)) end
-
- self.cache[name] = nil
-
- elseif type(value) == 'table' then
- assert(posix.mkdir(name))
-
- else
- local scope = get_scope(topology('/files'..name))
-
- if scope then
- -- TODO use relative symlink
- os.remove(name)
- assert(posix.link(pth.to_absolute(value, scope), name, true))
-
- else
- local file = util.open_file(name, 'w')
- file:write(tostring(value))
- file:close()
-
- self.cache[name] = value
- end
- end
- end
-end
-
-
-return backend
diff --git a/acf/persistence/backends/json.lua b/acf/persistence/backends/json.lua
deleted file mode 100644
index e0cd66e..0000000
--- a/acf/persistence/backends/json.lua
+++ /dev/null
@@ -1,79 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local pth = require('acf.path')
-local Cache = require('acf.persistence.backends.volatile')
-local util = require('acf.persistence.util')
-local copy = require('acf.util').copy
-
-local json = require('cjson')
-local posix = require('posix')
-
-
-local backend = require('acf.object').class()
-
-function backend:init()
- -- TODO cache expiration
- self.cache = {}
- self.dirty = {}
-end
-
-function backend:split_path(path)
- local fpath = copy(path)
- local jpath = {}
- local res
-
- while #fpath > 0 do
- local fp = pth.join('/', unpack(fpath))
- if self.cache[fp] then return fp, jpath end
- table.insert(jpath, 1, fpath[#fpath])
- table.remove(fpath)
- end
-
- fpath = '/'
-
- while true do
- fpath = pth.join(fpath, jpath[1])
- table.remove(jpath, 1)
-
- local t = posix.stat(fpath, 'type')
- if t == 'link' then t = posix.stat(posix.readlink(fpath), 'type') end
- if not t or not ({directory=true, regular=true})[t] then
- error('File or directory does not exist: '..fpath)
- end
-
- if t == 'regular' then return fpath, jpath end
-
- assert(#jpath > 0)
- end
-end
-
-function backend:get(path, top)
- local fpath, jpath = self:split_path(path)
- if not self.cache[fpath] then
- self.cache[fpath] = Cache(json.decode(util.read_file(fpath)))
- end
- return self.cache[fpath]:get(jpath, top)
-end
-
-function backend:set(mods)
- local dirty = {}
-
- for _, mod in ipairs(mods) do
- local path, value = unpack(mod)
- local fpath, jpath = self:split_path(path)
- self.cache[fpath]:_set(jpath, value)
- dirty[fpath] = true
- end
-
- for path, _ in pairs(dirty) do
- local file = util.open_file(path, 'w')
- file:write(json.encode(self.cache[path]:_get{}))
- file:close()
- end
-end
-
-
-return backend
diff --git a/acf/persistence/backends/null.lua b/acf/persistence/backends/null.lua
deleted file mode 100644
index 7ff58ce..0000000
--- a/acf/persistence/backends/null.lua
+++ /dev/null
@@ -1,10 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local backend = require('acf.object').class()
-function backend:get(path, top) if #path == 0 then return {} end end
-function backend:set(mods) end
-
-return backend
diff --git a/acf/persistence/backends/volatile.lua b/acf/persistence/backends/volatile.lua
deleted file mode 100644
index a83f7e3..0000000
--- a/acf/persistence/backends/volatile.lua
+++ /dev/null
@@ -1,46 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local util = require('acf.util')
-
-
-local backend = require('acf.object').class()
-
-function backend:init(data) self.data = data or {} end
-
-function backend:_get(path)
- local res = self.data
- for _, comp in ipairs(path) do
- if res == nil then return end
- assert(type(res) == 'table')
- res = res[comp]
- end
- return res
-end
-
-function backend:get(path, top)
- local res = self:_get(path)
- return type(res) == 'table' and util.keys(res) or res
-end
-
-function backend:_set(path, value)
- if type(value) == 'table' then value = {} end
-
- if #path == 0 then self.data = value
-
- else
- local comps = util.copy(path)
- local name = comps[#comps]
- table.remove(comps)
- self:_get(comps)[name] = value
- end
-end
-
-function backend:set(mods)
- for _, mod in ipairs(mods) do self:_set(unpack(mod)) end
-end
-
-
-return backend
diff --git a/acf/persistence/init.lua b/acf/persistence/init.lua
deleted file mode 100644
index 044d4b2..0000000
--- a/acf/persistence/init.lua
+++ /dev/null
@@ -1,79 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local loadmods = require('acf.loader')
-local topology = require('acf.model.root').topology
-local object = require('acf.object')
-local pth = require('acf.path')
-local util = require('acf.util')
-
-local stringy = require('stringy')
-
-
-local DataStore = object.class(
- require('acf.transaction.backend').TransactionBackend
-)
-
-function DataStore:init()
- object.super(self, DataStore):init()
- self.backends = util.map(
- function(m) return m() end,
- loadmods('persistence/backends')
- )
-end
-
-function DataStore:split_path(path)
- local comps = pth.split(path)
- local backend = self.backends[comps[1]]
- assert(backend)
- table.remove(comps, 1)
- return backend, comps
-end
-
-function DataStore:get(path)
- local backend, comps = self:split_path(path)
- local top = topology(path)
-
- local res = backend:get(comps, top)
-
- if top then
- local t = top.type
- if t and res ~= nil then
- local atype = type(res)
-
- if t == 'table' then assert(atype == 'table')
-
- else
- assert(atype ~= 'table')
-
- if t == 'string' then res = tostring(res)
- elseif t == 'number' then res = tonumber(res)
- elseif t == 'boolean' then
- res = (res and res ~= 'false') and true or false
- elseif t == 'reference' then assert(atype == 'string')
- else assert(false) end
- end
- end
- end
-
- return util.copy(res), self.mod_time[path] or 0
-end
-
-function DataStore:_set_multiple(mods)
- local bms = {}
-
- for _, mod in ipairs(mods) do
- local path, value = unpack(mod)
- local backend, comps = self:split_path(path)
- table.insert(util.setdefault(bms, backend, {}), {comps, value})
- end
-
- for backend, bm in pairs(bms) do backend:set(bm) end
-end
-
-
-return DataStore()
diff --git a/acf/persistence/util.lua b/acf/persistence/util.lua
deleted file mode 100644
index a657411..0000000
--- a/acf/persistence/util.lua
+++ /dev/null
@@ -1,22 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-function M.open_file(path, mode)
- local file = io.open(path, mode)
- if not file then error('Cannot open file: '..path) end
- return file
-end
-
-function M.read_file(path)
- local file = M.open_file(path)
- local data = ''
- for line in file:lines() do data = data..line end
- file:close()
- return data
-end
-
-return M
diff --git a/acf/transaction/backend.lua b/acf/transaction/backend.lua
deleted file mode 100644
index 4393101..0000000
--- a/acf/transaction/backend.lua
+++ /dev/null
@@ -1,70 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local M = {}
-
-local err = require('acf.error')
-
--- TODO each transaction backend (i.e. persistence manager or
--- transaction proper) should be implemented as a thread or have its
--- internal state stored in shared storage (with appropriate locking)
-
-
-local generation = 0
-function M.gen_number()
- generation = generation + 1
- return generation
-end
-
-
-M.TransactionBackend = require('acf.object').class()
-
-function M.TransactionBackend:init() self.mod_time = {} end
-
-function M.TransactionBackend:get_if_older(path, timestamp)
- local value, ts = self:get(path)
- if ts > timestamp then err.raise('conflict', path) end
- return value, ts
-end
-
-function M.TransactionBackend:set(path, value)
- self:set_multiple{{path, value}}
-end
-
-function M.TransactionBackend:set_multiple(mods)
- -- TODO delegate to PM backends?
- local timestamp = M.gen_number()
- local effective = {}
-
- local function tostr(s) return s ~= nil and tostring(s) or nil end
-
- for _, mod in ipairs(mods) do
- local path, value = unpack(mod)
-
- if type(value) == 'table' or type(
- self:get(path)
- ) == 'table' or self:get(path) ~= value then
-
- table.insert(effective, mod)
- self.mod_time[path] = timestamp
- end
- end
-
- self:_set_multiple(effective)
-end
-
--- TODO should be atomic, mutex with set_multiple
-function M.TransactionBackend:comp_and_setm(accessed, mods)
- local errors = err.ErrorDict()
- for path, timestamp in pairs(accessed) do
- errors:collect(self.get_if_older, self, path, timestamp)
- end
- errors:raise()
-
- self:set_multiple(mods)
-end
-
-
-return M
diff --git a/acf/transaction/init.lua b/acf/transaction/init.lua
deleted file mode 100644
index 2ca63c5..0000000
--- a/acf/transaction/init.lua
+++ /dev/null
@@ -1,210 +0,0 @@
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-local ErrorDict = require('acf.error').ErrorDict
-local root = require('acf.model.root')
-local object = require('acf.object')
-local pth = require('acf.path')
-local be_mod = require('acf.transaction.backend')
-
-local util = require('acf.util')
-local copy = util.copy
-
-
-local function remove_list_value(list, value)
- value = tostring(value)
-
- for i, v in ipairs(list) do
- if tostring(v) == value then
- table.remove(list, i)
- return
- end
- end
-
- assert(false)
-end
-
-
-local Transaction = object.class(be_mod.TransactionBackend)
-
-function Transaction:init(backend, validate)
- object.super(self, Transaction):init()
-
- self.backend = backend
-
- self.started = be_mod.gen_number()
- self.access_time = {}
-
- self.added = {}
- self.modified = {}
- self.deleted = {}
-
- self.validate = validate
- self.validable = {}
-
- self.root = root.RootModel(self)
-end
-
-function Transaction:check()
- if not self.backend then error('Transaction already committed') end
-end
-
-function Transaction:get(path)
- self:check()
-
- if self.deleted[path] then return nil, self.mod_time[path] end
- for _, tbl in ipairs{self.added, self.modified} do
- if tbl[path] ~= nil then
- return copy(tbl[path]), self.mod_time[path]
- end
- end
-
- local value, timestamp = self.backend:get_if_older(path, self.started)
- self.access_time[path] = timestamp
- return value, timestamp
-end
-
-function Transaction:_set_multiple(mods)
-
- local function set(path, value, new)
- local delete = value == nil
-
- if self.added[path] == nil and (not new or self.deleted[path]) then
- self.modified[path] = value
- self.deleted[path] = delete
- else self.added[path] = value end
- end
-
- for _, mod in ipairs(mods) do
- local path, value = unpack(mod)
-
- local ppath = pth.parent(path)
- local parent = self:get(ppath)
- if parent == nil then
- parent = {}
- self:set(ppath, parent)
- end
-
- local name = pth.name(path)
- local old = self:get(path)
-
- local is_table = type(value) == 'table'
- local delete = value == nil
-
- if delete then
- -- assume one-level refs for now
- local top = root.topology(ppath)
- if top then
- local errors = ErrorDict()
- for _, refs in ipairs(top.referrers) do
- for _, ref in ipairs(self.root:search_refs(refs)) do
- errors:collect(ref.deleted, ref, path)
- end
- end
- errors:raise()
- end
- end
-
- if type(old) == 'table' then
- if delete then
- for _, child in ipairs(old) do self:set(pth.join(path, child)) end
- elseif is_table then return
- elseif #old > 0 then
- error('Cannot assign a primitive value to non-leaf node '..path)
- end
- end
-
- if is_table then value = {} end
- set(path, value, old == nil)
-
- local function set_parent()
- set(ppath, parent)
- self.mod_time[ppath] = self.mod_time[path]
- end
-
- if old == nil and not delete then
- table.insert(parent, name)
- set_parent()
- elseif old ~= nil and delete then
- remove_list_value(parent, name)
- set_parent()
- end
- end
-end
-
-function Transaction:fetch(path) return self.root:fetch(path) end
-
-function Transaction:meta(path) return self.root:meta(path) end
-
-function Transaction:commit()
- self:check()
-
- if self.validate then
- local errors = ErrorDict()
- for path, addr in pairs(copy(self.validable)) do
- if self:get(addr) ~= nil then
- errors:collect(getmetatable(self:fetch(path)).validate)
- end
- end
- errors:raise()
- end
-
- local mods = {}
- local handled = {}
-
- local function insert(path, value)
- assert(not handled[path])
- table.insert(mods, {path, value})
- handled[path] = true
- end
-
- local function insert_add(path)
- if not handled[path] then
- local pp = pth.parent(path)
- if self.added[pp] then insert_add(pp) end
- insert(path, self.added[path])
- end
- end
-
- local function insert_del(path)
- if not handled[path] then
- local value = self.backend:get(path)
- if type(value) == 'table' then
- for _, child in ipairs(value) do
- local cp = pth.join(path, child)
- assert(self.deleted[cp])
- insert_del(cp)
- end
- end
- insert(path)
- end
- end
-
- for path, deleted in pairs(self.deleted) do
- if deleted then insert_del(path) end
- end
-
- for path, value in pairs(self.modified) do
- if type(value) ~= 'table' then insert(path, value) end
- end
-
- for path, _ in pairs(self.added) do insert_add(path) end
-
- self.backend:comp_and_setm(self.access_time, mods)
-
-
- if not self.validate then
- util.update(self.backend.validable, self.validable)
- end
-
- self.backend = nil
-end
-
-
-local store = require('acf.persistence')
-
-return function(txn, defer_validation)
- return Transaction(txn or store, not (txn and defer_validation))
- end
diff --git a/acf/util.lua b/acf/util.lua
deleted file mode 100644
index 3c93b47..0000000
--- a/acf/util.lua
+++ /dev/null
@@ -1,84 +0,0 @@
---- ACF utility functions.
---
--- @module acf.util
-
---[[
-Copyright (c) 2012-2013 Kaarle Ritvanen
-See LICENSE file for license details
---]]
-
-
-local M = {}
-
---- set default value for a key in a table unless it is already set.
--- @param t the table
--- @param k the key
--- @param v the default value
--- @return the value `t[k]`
-function M.setdefault(t, k, v)
- if t[k] == nil then t[k] = v end
- return t[k]
-end
-
---- merge a table into another.
--- Copy values for all keys from `src` to `dst` and optionally keep existing
--- values.
--- @param dst the destination table
--- @param src the source table
--- @param preserve a boolean. If true then will existing entries in `dst` be
--- kept.
--- @return the destination table, `dst`
-function M.update(dst, src, preserve)
- for k, v in pairs(src) do
- if not preserve or dst[k] == nil then dst[k] = v end
- end
- return dst
-end
-
---- copy default vaules from one table to another.
--- Copy all entries in `src` to `dst` but keep any already existing values
--- in `dst`.
--- @param dst the destination table
--- @param src the source table containing the default values
--- @return the destination table, `dst`
-function M.setdefaults(dst, src) return M.update(dst, src, true) end
-
---- copy a varable.
--- If `var` is a table, then the table is cloned, otherwise return the value
--- of `var`.
--- @param var the variable to copy
--- @return a clone of `var`
-function M.copy(var)
- return type(var) == 'table' and M.setdefaults({}, var) or var
-end
-
---- extend an array.
--- inserts all elements in `src` into `dst`
--- @param dst the destination array
--- @param src the source array
--- @return the destination array, `dst`
-function M.extend(dst, src)
- for _, v in ipairs(src) do table.insert(dst, v) end
- return dst
-end
-
---- extract the keys of a table as an array.
--- @param tbl a table
--- @return an array of keys
-function M.keys(tbl)
- local res = {}
- for k, v in pairs(tbl) do table.insert(res, k) end
- return res
-end
-
---- map a function over a table.
--- @param func a function with one argument
--- @param tbl the table
--- @return the transformed table
-function M.map(func, tbl)
- local res = {}
- for k, v in pairs(tbl) do res[k] = func(v) end
- return res
-end
-
-return M