--[[ Copyright (c) 2012-2013 Kaarle Ritvanen See LICENSE file for license details --]] module(..., package.seeall) local err = require('acf.error') local raise = err.raise local node = require('acf.model.node') local object = require('acf.object') local class = object.class local super = object.super local util = require('acf.util') local function contains(list, value) for k, v in ipairs(list) do if v == value then return true end end return false end Member = class() function Member:init(params) for k, v in pairs(params or {}) do if self[k] == nil then self[k] = v end end end function Member:auto_ui_name(name) if not name then return end return string.gsub( string.upper(string.sub(name, 1, 1))..string.sub(name, 2), '-', ' ' ) end function Member:meta(context) return { name=self.name, description=self.description, ['ui-name']=self.ui_name or self:auto_ui_name(self.name) } end Field = class(Member) function Field:init(params) super(self, Field):init(params) if self.choice and not self['ui-choice'] then self['ui-choice'] = util.map( function(name) return self:auto_ui_name(name) end, self.choice ) end if not self.widget then self.widget = self.choice and 'combobox' or 'field' end end function Field:meta(context) assert(self.dtype) local res = super(self, Field):meta(context) res.type = self.dtype res.required = self.required res.default = self.default res.choice = self.choice res.widget = self.widget res['ui-choice'] = self['ui-choice'] return res end function Field:topology(context) return { {path=context.path, addr=context.addr, type=self.dtype} } end function Field:load(context) if not context.txn then return setmetatable({}, context) end local value = context.txn:get(context.addr) if value == nil then return self.default end return value end function Field:_validate(context, value) if self.required and value == nil then raise(context.path, 'Required value not set') end if self.choice and value ~= nil and not contains(self.choice, value) then raise(context.path, 'Invalid value') end if value ~= nil then self:validate(context, value) end return value end function Field:validate(context, value) end function Field:save(context, value) context.txn:set(context.addr, self:_validate(context, value)) end function Field:validate_saved(context) self:save(context, self:load(context)) end local Primitive = class(Field) function Primitive:validate(context, value) local t = self.dtype if type(value) ~= t then raise(context.path, 'Not a '..t) end end String = class(Primitive) function String:init(params) super(self, String):init(params) self.dtype = 'string' end function String:validate(context, value) super(self, String):validate(context, value) if self['max-length'] and string.len(value) > self['max-length'] then raise(context.path, 'Maximum length exceeded') end end function String:meta(context) local res = super(self, String):meta(context) 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(context, value) return super(self, Number):_validate( context, value and tonumber(value) or value ) end Integer = class(Number) function Integer:validate(context, value) super(self, Integer):validate(context, value) if math.floor(value) ~= value then raise(context.path, 'Not an integer') end end Boolean = class(Primitive) function Boolean:init(params) super(self, Boolean):init(params) self.dtype = 'boolean' self.widget = self.dtype end TreeNode = class(Field) function TreeNode:topology(context) local res = super(self, TreeNode):topology(context) res[1].type = 'table' util.extend(res, node.topology(self:load(context, true))) return res end function TreeNode:load(context, create) if context.txn and not ( create or self.create or context.txn:get(context.addr) ) then return end return self.itype(context, self.iparams) end function TreeNode:save(context, value) local path = context.path if value == path then return end if type(value) == 'string' then value = context.txn:search(value) end if object.isinstance(value, node.TreeNode) and node.path(value) == path then return end context.txn:set(context.addr) if value then if type(value) ~= 'table' then raise(path, 'Cannot assign primitive value') end context.txn:set(context.addr, {}) local new = self:load(context, true) local errors = err.ErrorDict() for k, v in node.pairs(value) do errors:collect(self.save_member, new, k, v) end errors:raise() end end function TreeNode.save_member(node, k, v) node[k] = v end Model = class(TreeNode) function Model:init(params) super(self, Model):init(params) assert(self.model) self.itype = self.model self.dtype = 'model' self.widget = self.dtype end