summaryrefslogtreecommitdiffstats
path: root/aconf/model/model.lua
diff options
context:
space:
mode:
Diffstat (limited to 'aconf/model/model.lua')
-rw-r--r--aconf/model/model.lua228
1 files changed, 228 insertions, 0 deletions
diff --git a/aconf/model/model.lua b/aconf/model/model.lua
new file mode 100644
index 0000000..0ff41d1
--- /dev/null
+++ b/aconf/model/model.lua
@@ -0,0 +1,228 @@
+--[[
+Copyright (c) 2012-2014 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+local M = {}
+
+local raise = require('aconf.error').raise
+
+local fld = require('aconf.model.field')
+local Field = fld.Field
+local Member = fld.Member
+local Model = fld.Model
+local normalize_name = fld.normalize_name
+
+local node = require('aconf.model.node')
+local BoundMember = node.BoundMember
+
+local object = require('aconf.object')
+local class = object.class
+local super = object.super
+local isinstance = object.isinstance
+
+local pth = require('aconf.path')
+
+local util = require('aconf.util')
+local copy = util.copy
+local map = util.map
+
+
+local function to_member(obj, params)
+ if not params then params = {} end
+ if object.issubclass(obj, M.Model) then
+ params.model = obj
+ return 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 = node.null_addr
+ 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 copy(base.members)
+
+ local mt = copy(getmetatable(res))
+
+ function mt.__index(t, k) return base[k] end
+
+ function mt.__newindex(t, k, v)
+ assert(v)
+ k = normalize_name(k)
+
+ 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, 'model', true)
+
+ 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]
+ k = normalize_name(k)
+ if not v then v = mt.class[k] end
+
+ if isinstance(v, Field) then
+ return BoundMember(self, k, v):load(options)
+ end
+
+ assert(mt.txn)
+
+ if isinstance(v, M.Action) then
+ local f = v.field and BoundMember(self, k, v.field)
+ if options.create then return f and f:load(options) 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 f:_save() end
+ return res
+ end
+ end
+
+ return v
+ end
+
+ if self.is_removable then
+ function mt.removable() return self:is_removable() end
+ end
+
+ if not mt.txn then return end
+
+
+ function mt.mmeta(name) return mt.member(name):meta() end
+
+ function mt.save(k, v)
+ k = normalize_name(k)
+ mt.check_removable(k, v)
+ return mt.member(k, false, Field):save(v)
+ end
+
+ local function tmeta(tpe)
+ return map(function(m) return m:meta() end, _members(tpe))
+ end
+
+ function mt.init_meta(meta)
+ util.update(meta, {fields=tmeta(Field), actions=tmeta(M.Action)})
+ end
+
+ function mt.members()
+ return map(function(f) return f.name end, mt.meta().fields)
+ end
+
+ function mt.match(filter)
+ for k, v in pairs(filter) do
+ if not util.contains(v, mt.load(k)) then return false end
+ end
+ return true
+ end
+
+ function mt.validate()
+ for _, f in ipairs(_members(Field)) do
+ if mt.match(f.condition or {}) then f:validate_saved()
+ elseif f:_editable() then f:_save() end
+ end
+ if self.validate then self:validate() end
+ end
+
+ if self.has_permission then
+ function mt.has_permission(user, permission)
+ return self:has_permission(user, permission)
+ end
+ end
+
+ for _, f in ipairs(_members(Model)) do mt.load(f.name) end
+end
+
+
+return M