summaryrefslogtreecommitdiffstats
path: root/aconf/model/init.lua
diff options
context:
space:
mode:
Diffstat (limited to 'aconf/model/init.lua')
-rw-r--r--aconf/model/init.lua279
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