diff options
Diffstat (limited to 'acf2/model/node.lua')
-rw-r--r-- | acf2/model/node.lua | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/acf2/model/node.lua b/acf2/model/node.lua new file mode 100644 index 0000000..85fc416 --- /dev/null +++ b/acf2/model/node.lua @@ -0,0 +1,275 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = {} + +local raise = require('acf2.error').raise + +local object = require('acf2.object') +local class = object.class +local isinstance = object.isinstance +local super = object.super + +local pth = require('acf2.path') +local util = require('acf2.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 |