summaryrefslogtreecommitdiffstats
path: root/acf2
diff options
context:
space:
mode:
Diffstat (limited to 'acf2')
-rw-r--r--acf2/model/field.lua45
-rw-r--r--acf2/model/init.lua56
-rw-r--r--acf2/model/model.lua16
-rw-r--r--acf2/model/node.lua1
-rw-r--r--acf2/transaction/init.lua4
-rw-r--r--acf2/util.lua10
6 files changed, 89 insertions, 43 deletions
diff --git a/acf2/model/field.lua b/acf2/model/field.lua
index d2470ca..e633a63 100644
--- a/acf2/model/field.lua
+++ b/acf2/model/field.lua
@@ -41,6 +41,16 @@ function M.Member:meta(context)
end
+function M.conv_filter(filter)
+ return filter and map(
+ function(values)
+ return type(values) == 'table' and values or {values}
+ end,
+ filter
+ ) or nil
+end
+
+
M.Field = class(M.Member)
function M.Field:init(params)
@@ -49,14 +59,7 @@ function M.Field:init(params)
if self.compute then self.addr = node.null_addr end
if self.editable == nil then self.editable = not self.compute end
- if self.condition then
- self.condition = map(
- function(values)
- return type(values) == 'table' and values or {values}
- end,
- self.condition
- )
- end
+ self.condition = M.conv_filter(self.condition)
if self.choice then
self.choice = map(
@@ -70,7 +73,8 @@ function M.Field:init(params)
end
end
return util.setdefaults(
- choice, {['ui-value']=self:auto_ui_name(choice.value)}
+ choice,
+ {enabled=true, ['ui-value']=self:auto_ui_name(choice.value)}
)
end,
self.choice
@@ -82,6 +86,8 @@ function M.Field:init(params)
end
end
+function M.Field:_choice(context) return self.choice end
+
function M.Field:meta(context)
assert(self.dtype)
local res = super(self, M.Field):meta(context)
@@ -91,7 +97,7 @@ function M.Field:meta(context)
res.condition = self.condition
res.required = self.required
res.default = self.default
- res.choice = self.choice
+ res.choice = self:_choice(context)
res.widget = self.widget
return res
@@ -118,14 +124,25 @@ function M.Field:_validate(context, value)
return
end
- value = self:normalize(context, value)
- if self.choice and not util.contains(
- map(function(ch) return ch.value end, self.choice), value
+ local save
+ value, save = self:normalize(context, value)
+
+ local committing = context.txn:committing()
+ local choice = self:_choice(context)
+ if choice and not util.contains(
+ map(
+ function(ch) return ch.value end,
+ util.filter(function(ch) return committing or ch.enabled end, choice)
+ ),
+ value
) then
raise(context.path, 'Invalid value')
end
+
self:validate(context, value)
- return value
+
+ if save == nil then save = value end
+ return save
end
function M.Field:normalize(context, value) return value end
diff --git a/acf2/model/init.lua b/acf2/model/init.lua
index 62f114b..711626b 100644
--- a/acf2/model/init.lua
+++ b/acf2/model/init.lua
@@ -69,6 +69,7 @@ function M.Reference:init(params)
self.dtype = 'reference'
self.dereference = true
if not self.scope then self.scope = '/' end
+ self.filter = fld.conv_filter(self.filter)
end
function M.Reference:topology(context)
@@ -81,30 +82,43 @@ function M.Reference:abs_scope(context)
return pth.to_absolute(self.scope, node.path(context.parent))
end
-function M.Reference:meta(context)
- local res = super(self, M.Reference):meta(context)
- res.scope = self:abs_scope(context)
+-- assume one-level refs for now
+function M.Reference:_choice(context)
+ local res = {}
local txn = context.txn
- local obj = relabel('system', txn.fetch, txn, res.scope)
+ local obj = relabel('system', txn.fetch, txn, self:abs_scope(context))
assert(isinstance(obj, node.Collection))
- res.choice = {}
+
for k, v in node.pairs(obj) do
+ local ch = {enabled=true}
+
if isinstance(v, node.TreeNode) then
- v = node.path(v)
- local name = pth.name(v)
- v = not pth.is_subordinate(context.path, v) and {
- value=self.dereference and v or pth.escape(name),
- ['ui-value']=name,
- ref=v
- } or nil
- else v = {value=pth.escape(v), ['ui-value']=v} end
- if v then table.insert(res.choice, v) end
+ ch.ref = node.path(v)
+ if pth.is_subordinate(context.path, ch.ref) then ch = nil end
+ if ch then
+ ch['ui-value'] = pth.name(ch.ref)
+ ch.value = self.dereference and ch.ref or pth.escape(ch['ui-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 update(ch, {value=pth.escape(v), ['ui-value']=v}) end
+
+ if ch then table.insert(res, ch) end
end
return res
end
+function M.Reference:meta(context)
+ local res = super(self, M.Reference):meta(context)
+ res.scope = self:abs_scope(context)
+ return res
+end
+
function M.Reference:follow(context, value)
return context.txn:fetch(pth.rawjoin(self:abs_scope(context), value))
end
@@ -124,24 +138,26 @@ function M.Reference:normalize(context, value)
local path = context.path
if type(value) ~= 'string' then raise(path, 'Path name must be string') end
- if pth.is_absolute(value) then
+ local rel = value
+
+ if pth.is_absolute(rel) then
local scope = self:abs_scope(context)
local prefix = scope..'/'
- if not stringy.startswith(value, prefix) then
+ if not stringy.startswith(rel, prefix) then
raise(path, 'Reference out of scope ('..scope..')')
end
- value = value:sub(prefix:len() + 1, -1)
+ rel = rel:sub(prefix:len() + 1, -1)
end
-- assume one-level ref for now
- if #pth.split(value) > 1 then
+ if #pth.split(rel) > 1 then
raise(path, 'Subtree references not yet supported')
end
-- TODO check instance type
- relabel(path, self.follow, self, context, value)
+ relabel(path, self.follow, self, context, rel)
- return value
+ return self.dereference and value or rel, rel
end
function M.Reference:deleted(context, addr)
diff --git a/acf2/model/model.lua b/acf2/model/model.lua
index b4f4603..c1c222f 100644
--- a/acf2/model/model.lua
+++ b/acf2/model/model.lua
@@ -196,17 +196,17 @@ function M.Model:init(context)
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 f.editable then
- local relevant = true
- for k, v in pairs(f.condition or {}) do
- if not util.contains(v, mt.load(k)) then
- relevant = false
- break
- end
- end
- if relevant then f:validate_saved()
+ if mt.match(f.condition or {}) then f:validate_saved()
else f:_save() end
end
end
diff --git a/acf2/model/node.lua b/acf2/model/node.lua
index 5096787..37a478e 100644
--- a/acf2/model/node.lua
+++ b/acf2/model/node.lua
@@ -263,6 +263,7 @@ for _, mf in ipairs{
'contains',
'has_permission',
'insert',
+ 'match',
'meta',
'mmeta',
'parent',
diff --git a/acf2/transaction/init.lua b/acf2/transaction/init.lua
index 395059a..627703c 100644
--- a/acf2/transaction/init.lua
+++ b/acf2/transaction/init.lua
@@ -44,11 +44,12 @@ function Transaction:init(backend, validate)
self.validate = validate
self.validable = {}
- self.commit_val = {}
self.root = root.RootModel(self)
end
+function Transaction:committing() return self.commit_val and true or false end
+
function Transaction:check()
if not self.backend then error('Transaction already committed') end
end
@@ -169,6 +170,7 @@ function Transaction:commit()
end
while next(self.commit_val) do validate(next(self.commit_val)) end
+ self.commit_val = nil
errors:raise()
end
diff --git a/acf2/util.lua b/acf2/util.lua
index a40f7fd..f4c47d6 100644
--- a/acf2/util.lua
+++ b/acf2/util.lua
@@ -90,4 +90,14 @@ function M.map(func, tbl)
return res
end
+--- select array values satisfying a filter.
+-- @param func a function with one argument
+-- @param list the array
+-- @return the filtered array
+function M.filter(func, list)
+ local res = {}
+ for _, v in ipairs(list) do if func(v) then table.insert(res, v) end end
+ return res
+end
+
return M