diff options
Diffstat (limited to 'acf/model/init.lua')
-rw-r--r-- | acf/model/init.lua | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/acf/model/init.lua b/acf/model/init.lua new file mode 100644 index 0000000..6934796 --- /dev/null +++ b/acf/model/init.lua @@ -0,0 +1,206 @@ +--[[ +Copyright (c) 2012 Kaarle Ritvanen +See LICENSE file for license details +--]] + +module(..., package.seeall) + +local fld = require('acf.model.field') +local Field = fld.Field + +local model = require('acf.model.model') +new = model.new +to_field = model.to_field + +node = require('acf.model.node') + +local object = require('acf.object') +local class = object.class +local super = object.super + +local pth = require('acf.path') +local map = require('acf.util').map + + +-- TODO object-specific actions + +-- TODO access control + + +local Primitive = class(Field) + +function Primitive:validate(txn, path, value) + local t = self.dtype + if type(value) ~= t then error('Not a '..t..': '..tostring(value)) end +end + + +String = class(Primitive) + +function String:init(params) + super(self, String):init(params) + self.dtype = 'string' +end + +function String:validate(txn, path, value) + if self['max-length'] and string.len(value) > self['max-length'] then + error('Maximum length exceeded: '..value) + end +end + +function String:meta(txn, path, addr) + local res = super(self, String):meta(txn, path, addr) + res['max-length'] = self['max-length'] + return res +end + + +Number = class(Primitive) + +function Number:init(params) + super(self, Number):init(params) + self.dtype = 'number' +end + +function Number:_validate(txn, path, value) + return tonumber(super(self, Number):_validate(txn, path, value)) +end + + +Integer = class(Number) + +function Integer:validate(txn, path, value) + if math.floor(value) ~= value then error('Not an integer: '..value) end +end + + +Boolean = class(Primitive) + +function Boolean:init(params) + super(self, Boolean):init(params) + self.dtype = 'boolean' + self.widget = self.dtype +end + + +Reference = class(Field) + +function Reference:init(params) + super(self, Reference):init(params) + self.dtype = 'reference' + if not self.scope then self.scope = '/' end +end + +function Reference:abs_scope(path) + return pth.to_absolute(self.scope, pth.parent(pth.parent(path))) +end + +function Reference:meta(txn, path, addr) + local res = super(self, Reference):meta(txn, path, addr) + res.scope = self:abs_scope(path) + + local base = txn:search(res.scope) + local objs = base and txn:get(getmetatable(base).addr) or {} + res.choice = map(function(p) return pth.join(res.scope, p) end, objs) + res['ui-choice'] = objs + + return res +end + +function Reference:follow(txn, path, value) + return txn:search(pth.join(self:abs_scope(path), value)) +end + +function Reference:load(txn, path, addr) + local ref = super(self, Reference):load(txn, path, addr) + return ref and self:follow(txn, path, ref) or nil +end + +function Reference:_validate(txn, path, value) + super(self, Reference):_validate(txn, path, value) + + if value == nil then return end + + if object.isinstance(value, node.TreeNode) then + value = getmetatable(value).path + end + if value and pth.is_absolute(value) then + local scope = self:abs_scope(path) + local prefix = scope..'/' + if not stringy.startswith(value, prefix) then + error('Reference out of scope ('..scope..')') + end + value = string.sub(value, string.len(prefix) + 1, -1) + end + + -- assume one-level ref for now + assert(not string.find(value, '/')) + if not self:follow(txn, path, value) then + error('Does not exist: '..path) + end + -- TODO check instance type + + return value +end + + +Model = fld.Model + + +Collection = class(fld.TreeNode) + +function Collection:init(params, ctype) + super(self, Collection):init(params) + assert(self.type) + self.ctype = ctype or node.Collection + self.dtype = 'collection' + self.widget = self.dtype +end + +function Collection:load(txn, path, addr) + -- automatically create missing collection (TODO: make this configurable?) + return self.ctype(txn, path, addr, to_field(self.type), self.required) +end + + +PrimitiveList = class(Collection) + +function PrimitiveList:init(params) + super(self, PrimitiveList):init(params, node.PrimitiveList) +end + + +-- experimental +Mixed = class(Collection) + +function Mixed:init() + super(self, Mixed):init({type=Mixed}, node.Mixed) +end + +function Mixed:load(txn, path, addr) + local value = Primitive.load(self, txn, path, addr) + if type(value) == 'table' then + return super(self, Mixed):load(txn, path, addr) + end + return value +end + +function Mixed:save(txn, path, addr, value) + if type(value) == 'table' then + super(self, Mixed):save(txn, path, addr, value) + else Primitive.save(self, txn, path, addr, value) end +end + + + +RootModel = new() + +function RootModel:init(txn) super(self, RootModel):init(txn, '/') end + +function register(name, path, field) + local field = to_field(field) + function field:compute(txn, pth, addr) + return self:load(txn, '/'..name, path) + end + RootModel[name] = field +end |