diff options
Diffstat (limited to 'aconf/model/init.lua')
-rw-r--r-- | aconf/model/init.lua | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/aconf/model/init.lua b/aconf/model/init.lua new file mode 100644 index 0000000..c0b5998 --- /dev/null +++ b/aconf/model/init.lua @@ -0,0 +1,279 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = {} + +M.error = require('aconf.error') +local raise = M.error.raise +local relabel = M.error.relabel + +M.binary = require('aconf.model.binary') + +local combination = require('aconf.model.combination') +M.Union = combination.Union +M.Range = combination.Range + +local fld = require('aconf.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('aconf.model.model') +M.Action = model.Action +M.new = model.new +local to_field = model.to_field + +M.net = require('aconf.model.net') + +local node = require('aconf.model.node') +M.node = {} +for _, m in ipairs{ + 'List', + 'Set', + 'TreeNode', + 'contains', + 'has_permission', + 'insert', + 'meta', + 'mmeta', + 'name', + 'parent', + 'path', + 'pairs', + 'ipairs' +} do M.node[m] = node[m] end + +M.permission = require('aconf.model.permission') +M.register = require('aconf.model.root').register +M.service = require('aconf.model.service') +M.node.Set = require('aconf.model.set').Set +M.time = require('aconf.model.time') + +local object = require('aconf.object') +local class = object.class +local isinstance = object.isinstance +local super = object.super + +M.path = require('aconf.path') +local store = require('aconf.persistence') +local def_store = require('aconf.persistence.defer') + +local util = require('aconf.util') +local setdefault = util.setdefault +local update = util.update + + +local stringy = require('stringy') + + +M.Reference = class(Field) + +function M.Reference:init(params) + super(self, M.Reference):init( + util.setdefaults( + params, {on_delete='restrict', scope='/', widget='reference'} + ) + ) + self.dtype = 'reference' + self.dereference = true + self.filter = fld.conv_filter(self.filter) +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 M.path.to_absolute(self.scope, node.path(context.parent)) +end + +-- assume one-level refs for now +function M.Reference:_choice(context) + local res = {} + + local txn = context.txn + local obj = relabel('system', txn.fetch, txn, self:abs_scope(context)) + assert(isinstance(obj, node.Collection)) + + for k, v in node.pairs(obj) do + local ch = {enabled=true} + + if isinstance(v, node.TreeNode) then + ch.ref = node.path(v) + if M.path.is_subordinate(context.path, ch.ref) then ch = nil end + if ch then + ch['ui-value'] = M.path.name(ch.ref) + ch.be_value = M.path.escape(ch['ui-value']) + ch.value = self.dereference and ch.ref or ch.be_value + if self.filter then + assert(isinstance(v, model.Model)) + if not node.match(v, self.filter) then ch.enabled = false end + end + end + + else + local ep = M.path.escape(v) + update(ch, {be_value=ep, value=ep, ['ui-value']=v}) + end + + if ch then table.insert(res, ch) end + end + + return res +end + +function M.Reference:meta(context) + return update( + super(self, M.Reference):meta(context), + {scope=self:abs_scope(context), dynamic=self.filter and true or false} + ) +end + +function M.Reference:follow(context, value) + return context.txn:fetch(M.path.rawjoin(self:abs_scope(context), value)) +end + +function M.Reference:load(context, options) + local ref = super(self, M.Reference):load(context) + return ( + setdefault( + options or {}, 'dereference', self.dereference + ) and context.txn and ref + ) and self:follow(context, ref) or ref +end + +function M.Reference:normalize(context, value) + 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 + + local rel = value + + if M.path.is_absolute(rel) then + local scope = self:abs_scope(context) + local prefix = scope..'/' + if not stringy.startswith(rel, prefix) then + raise(path, 'Reference out of scope ('..scope..')') + end + rel = rel:sub(prefix:len() + 1, -1) + end + + -- TODO check instance type + relabel(path, self.follow, self, context, rel) + + return self.dereference and value or rel +end + +function M.Reference:deleted(context, addr) + local target = self:load(context, {dereference=true}) + + if target and node.addr(target) == addr then + local policy = self.on_delete + + if policy == 'restrict' then + -- TODO raise error for the target object + raise(context.path, 'Refers to '..addr) + end + + local parent = context.parent + local path = context.path + + if policy == 'cascade' then + path = node.path(parent) + parent = node.parent(parent) + else assert(policy == 'set-null') end + + node.save(parent, M.path.name(path)) + 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, + editable=self:_editable(), + key=self.key, + 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) + if isinstance(self.iparams.field, fld.Model) then + setdefault(self.iparams, 'layout', 'tabular') + end + 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 + + +function M.trigger(phase, addr, func) store:trigger(phase, addr, func) end +function M.defer(addr) def_store:defer(addr) end + + +return M |