summaryrefslogblamecommitdiffstats
path: root/aconf/model/init.lua
blob: aaf6ad0eab54ca013b5d62c0cbfdbdf4184e7804 (plain) (tree)
1
2
3
4
5
6
7
8
9
    
                                       


                                    
                       
            
 
                                
                           
                               
 
                                        
 
                                                      

                           
 
                                        
                       



                       
 
                                          

                       
                               
 
                                  
 
                                        

                   

          
              
              



                    
          
            
          
          

                            




                                                 
 



                                      
 


                                                    
 
                                  
                                  
                          

 
                                  

 
                          
 
                                 

                                 

                                                                          

       
                           
                          
                                             

   

                                                         
                                                         
             

   
                                       
                                                                   

   

                                     
                                      
 

                                                                        
 
                                                       

                               











                                                                         
                               

                                                                            


               
          


                                                                  
         

                                          

      


             
                                  



                                                                            

   
                                           


                                                                    

   
                                           
                                                     
           
                 

                                                       
                                         

   
                                              
                                                                        

                            
                                                                              
 
                    
                                        
 
                                  
                               
                                                 
                                                            
         
                                         

      
                              
                                                 
 
                                                                      

   
                                           
                                                        
 
                                               






                                                  








                                           


      
 
                   

 




                                                                      

                                                                    
                                  
 
                                         
                                                        
                                         
 
                    
                                        

                           
                   
                         


                              
 
                            

   
                                        
                              
                                                       
                                                      

   
                                            





                                                       
                                                          


   

                                                                     
              

                                                                            

 

                                                                    
             
                           





                                                                     
                                                           


               
                             
 
                             
                                                                        
                        

   
                                                
 
                              
                                          
                                                                               


               

                                                                           
                                            
   

 
                                                                          
                                                

 






































































                                                                      
        
--[[
Copyright (c) 2012-2016 Kaarle Ritvanen
See LICENSE file for license details
--]]

--- @module aconf.model
local M = {}

M.error = require('aconf.error')
local raise = M.error.raise
local relabel = M.error.relabel

M.binary = require('aconf.model.binary')

local combination = require('aconf.model.combination')
M.Union = combination.Union
M.Range = combination.Range

local fld = require('aconf.model.field')
local Field = fld.Field
M.Boolean = fld.Boolean
M.Integer = fld.Integer
M.Number = fld.Number
M.String = fld.String

local model = require('aconf.model.model')
M.Action = model.Action
M.new = model.new
local to_field = model.to_field

M.net = require('aconf.model.net')

local node = require('aconf.model.node')
M.node = {}
for _, m in ipairs{
   'List',
   'Set',
   'TreeNode',
   'contains',
   'has_permission',
   'insert',
   'meta',
   'mmeta',
   'name',
   'parent',
   'path',
   'pairs'
} do M.node[m] = node[m] end

M.permission = require('aconf.model.permission')
M.register = require('aconf.model.root').register
M.service = require('aconf.model.service')
M.node.Set = require('aconf.model.set').Set
M.time = require('aconf.model.time')

M.object = require('aconf.object')
local class = M.object.class
local isinstance = M.object.isinstance
local super = M.object.super

M.path = require('aconf.path')
local store = require('aconf.persistence')
local def_store = require('aconf.persistence.defer')

local util = require('aconf.util')
local setdefault = util.setdefault
local update = util.update


local stringy = require('stringy')


M.Reference = class(Field)

function M.Reference:init(params)
   super(self, M.Reference):init(
      util.setdefaults(
	 params,
	 {on_delete='restrict', scope='/', search='*', widget='reference'}
      )
   )
   self.dtype = 'reference'
   self.dereference = true
   self.filter = fld.conv_filter(self.filter)
end

function M.Reference:topology(context)
   local res = super(self, M.Reference):topology(context)
   update(res[1], {scope=self.scope, search=self.search})
   return res
end

function M.Reference:abs_scope(context)
   return M.path.to_absolute(self.scope, node.path(context.parent))
end

function M.Reference:_choice(context)
   local res = {}
   local onelevel = self.search == '*'

   local obj = relabel('system', node.fetch, context.parent, self.scope)
   assert(isinstance(obj, node.TreeNode))

   for _, v in ipairs(node.search(obj, self.search)) do
      local ch = {enabled=true}

      if isinstance(v.value, node.TreeNode) then
	 ch.ref = node.path(v.value)
	 if M.path.is_subordinate(context.path, ch.ref) then ch = nil
	 else
	    update(
	       ch,
	       {
		  be_value=v.path,
		  value=self.dereference and ch.ref or v.path,
		  ['ui-value']=onelevel and M.path.name(v.path) or v.path
	       }
	    )
	    if self.filter then
	       assert(isinstance(v.value, model.Model))
	       if not v.value:match(self.filter) then ch.enabled = false end
	    end
	 end

      else
	 assert(onelevel)
	 local ep = M.path.escape(v.value)
	 update(ch, {be_value=ep, value=ep, ['ui-value']=v.value})
      end

      if ch then table.insert(res, ch) end
   end

   return res
end

function M.Reference:meta(context)
   return update(
      super(self, M.Reference):meta(context),
      {scope=self:abs_scope(context), dynamic=self.filter and true or false}
   )
end

function M.Reference:follow(context, value)
   return node.fetch(
      context.parent, M.path.rawjoin(self:abs_scope(context), value)
   )
end

function M.Reference:load(context, options)
   local ref = super(self, M.Reference):load(context)
   return (
      setdefault(
	 options or {}, 'dereference', self.dereference
      ) and context.txn and ref
   ) and self:follow(context, ref) or ref
end

function M.Reference:normalize(context, value)
   if isinstance(value, node.TreeNode) then value = node.path(value) end

   local path = context.path
   if type(value) ~= 'string' then raise(path, 'Path name must be string') end

   local rel = value
   local scope = self:abs_scope(context)

   if M.path.is_absolute(rel) then
      local prefix = scope..'/'
      if not stringy.startswith(rel, prefix) then
	 raise(path, 'Reference out of scope ('..scope..')')
      end
      rel = rel:sub(prefix:len() + 1, -1)
   end

   -- TODO check instance type
   relabel(path, self.follow, self, context, rel)

   return self.dereference and M.path.to_absolute(value, scope) or rel
end

function M.Reference:deleted(context, addr)
   local target = self:load(context, {dereference=true})

   if target and node.addr(target) == addr then
      local policy = self.on_delete

      if policy == 'restrict' then
	 -- TODO raise error for the target object
	 raise(context.path, 'Refers to '..addr)
      end

      local parent = context.parent
      local path = context.path

      if policy == 'cascade' then
	 path = node.path(parent)
	 parent = node.parent(parent)
      else assert(policy == 'set-null') end

      node.save(parent, M.path.name(path))
   end
end


M.Model = fld.Model


--- collection field.
-- @fcons Collection
-- @param type (<i>[&lt;Field&gt;](#Field_constructors)</i> or
-- <i>[&lt;Model&gt;](#new)</i>) field constructor, field instance, or
-- model specifying the type of the members.
-- @tparam ?string ui_member user-friendly noun for a member of this
-- collection
M.Collection = class(fld.TreeNode)

function M.Collection:init(params, itype)
   if params.create == nil then params.create = true end
   super(self, M.Collection):init(params)

   assert(self.type)
   self.itype = itype or node.Collection
   self.iparams = {
      destroy=self.destroy,
      key=self.key,
      layout=self.layout,
      required=self.required,
      ui_member=self.ui_member
   }

   self.dtype = 'collection'
end

function M.Collection:auto_ui_name(name)
   if not name then return end
   if name:sub(-1, -1) ~= 's' then name = name..'s' end
   return super(self, M.Collection):auto_ui_name(name)
end

function M.Collection:load(context, options)
   if not self.iparams.field then
      self.iparams.field = to_field(self.type)
      if isinstance(self.iparams.field, fld.Model) then
	 setdefault(self.iparams, 'layout', 'tabular')
      end
   end
   return super(self, M.Collection):load(context, options)
end


--- list field, inherits @{Collection}. The value of this field is an
-- instance of @{node.List}.
-- @fcons List
M.List = class(M.Collection)
function M.List:init(params) super(self, M.List):init(params, node.List) end


--- set field, inherits @{Collection}. The value of this field is an
-- instance of @{node.Set}.
-- @fcons Set
M.Set = class(M.Collection)
function M.Set:init(params)
   if not params.widget and isinstance(params.type, M.Reference) then
      params.widget = 'checkboxes'
   end
   super(self, M.Set):init(params, M.node.Set)
end
function M.Set.save_member(tn, k, v) node.insert(tn, v) end


-- experimental
M.Mixed = class(M.Collection)

function M.Mixed:init(params)
   super(self, M.Mixed):init(update(params, {type=M.Mixed}), node.Mixed)
   self.pfield = Field()
end

function M.Mixed:topology(context) return {} end

function M.Mixed:load(context)
   local value = self.pfield:load(context)
   if type(value) == 'table' then return super(self, M.Mixed):load(context) end
   return value
end

function M.Mixed:save(context, value)
   if type(value) == 'table' then super(self, M.Mixed):save(context, value)
   else self.pfield:save(context, value) end
end


function M.trigger(phase, addr, func) store:trigger(phase, addr, func) end
function M.defer(addr) def_store:defer(addr) end


--- Field constructor parameters. All field constructors accept one
-- table argument containing field parameters as key&ndash;value
-- pairs. The parameters listed below are valid for all
-- subclasses. Subclasses may define additional parameters.
-- @section Field

--- back-end address for the field. This can be an
-- absolute address or relative to the parent's address. The top-level
-- component of a back-end address specifies the back-end. The
-- interpretation of the remaining components is specific to the
-- back-end. If not specified, the address is formed by appending the
-- field's name to the address of the parent.
-- @tfield ?string addr

--- back-end mode. Controls how the Augeas back-end will map addresses
-- to Augeas paths. By default, each component of a back-end address
-- is directly mapped to an Augeas path component. This parameter is
-- an exception table applicable to the subtree defined by the field's
-- address. Each key is a relative address pattern, and the
-- corresponding value is a directive applied to matching address
-- components. The *enumerate* directive indicates there can be
-- several Augeas nodes matching the path and the next component is to
-- be interpreted as an index for such nodes. The *parent-value*
-- directive is applicable only to primitive fields and instructs the
-- back-end not to append the last address component at all, causing
-- the parent node's value to be accessed. If the *be\_mode* parameter
-- is defined as a string, it is assumed to be a directive applicable
-- to the field's own address.
-- @field be_mode (optional <i>**string**</i> or
-- <i>**{[string]=string,...}**</i>)

--- array of allowed values. Each value may be a primitive value or a
-- tuple specifying the value used by the data model and a
-- user-friendly value.
-- @field choice (optional <i>**{primitive**</i> or
-- <i>**{primitive,string},...}**</i>)

--- function for computing the value of the field when not provided by
-- the back-end. The function gets a reference to the field's parent
-- as an argument. If defined as a string, a method with the given
-- name is invoked.
-- @field compute (optional <i><b>function(@{node.TreeNode})</b></i>
-- or <i>**string**</i>)

--- default value for the field.
-- @tfield ?primitive default

--- required field must be assigned a value if set. Defaults to false.
-- @tfield ?boolean required

--- function for storing the value of the field. If this parameter is
-- defined, the value of the field is not stored according to the
-- field's back-end address. Rather, the provided function is invoked
-- with a reference to the parent and the field value. If defined as a
-- string, a method with the given name is invoked.
-- @field store (optional <i><b>function(@{node.TreeNode},
-- primitive)</b></i> or <i>**string**</i>)

--- user-friendly name for the field.
-- @tfield ?string ui_name

--- visibility of the field in the user interface. Defaults to true.
-- @tfield ?boolean visible

--- widget for rendering the field in the user interface. The default
-- widget for non-leaf objects is *link*, which is a hyperlink to a
-- detailed view to the object. The *inline* widget renders a non-leaf
-- object embedded in the parent's view.
-- @tfield ?string widget


return M