libs/web: remove debugging code
[project/luci.git] / libs / web / luasrc / cbi.lua
index 5fa992d..ef45a89 100644 (file)
@@ -50,6 +50,8 @@ AUTO = true
 
 CREATE_PREFIX = "cbi.cts."
 REMOVE_PREFIX = "cbi.rts."
+RESORT_PREFIX = "cbi.sts."
+FEXIST_PREFIX = "cbi.cbe."
 
 -- Loads a CBI map from given file, creating an environment and returns it
 function load(cbimap, ...)
@@ -100,7 +102,7 @@ function load(cbimap, ...)
                                for _, field in ipairs(map.upload_fields) do
                                        uploads[
                                                field.config .. '.' ..
-                                               field.section.sectiontype .. '.' ..
+                                               (field.section.sectiontype or '1') .. '.' ..
                                                field.option
                                        ] = true
                                end
@@ -122,8 +124,8 @@ function load(cbimap, ...)
                                        )()
 
                                        if c and s and o then
-                                               local t = uci:get( c, s )
-                                               if t and uploads[c.."."..t.."."..o] then
+                                               local t = uci:get( c, s ) or s
+                                               if uploads[c.."."..t.."."..o] then
                                                        local path = upldir .. field.name
                                                        fd = io.open(path, "w")
                                                        if fd then
@@ -150,6 +152,78 @@ function load(cbimap, ...)
        return maps
 end
 
+--
+-- Compile a datatype specification into a parse tree for evaluation later on
+--
+local cdt_cache = { }
+
+function compile_datatype(code)
+       local i
+       local pos = 0
+       local esc = false
+       local depth = 0
+       local stack = { }
+
+       for i = 1, #code+1 do
+               local byte = code:byte(i) or 44
+               if esc then
+                       esc = false
+               elseif byte == 92 then
+                       esc = true
+               elseif byte == 40 or byte == 44 then
+                       if depth <= 0 then
+                               if pos < i then
+                                       local label = code:sub(pos, i-1)
+                                               :gsub("\\(.)", "%1")
+                                               :gsub("^%s+", "")
+                                               :gsub("%s+$", "")
+
+                                       if #label > 0 and tonumber(label) then
+                                               stack[#stack+1] = tonumber(label)
+                                       elseif label:match("^'.*'$") or label:match('^".*"$') then
+                                               stack[#stack+1] = label:gsub("[\"'](.*)[\"']", "%1")
+                                       elseif type(datatypes[label]) == "function" then
+                                               stack[#stack+1] = datatypes[label]
+                                               stack[#stack+1] = { }
+                                       else
+                                               error("Datatype error, bad token %q" % label)
+                                       end
+                               end
+                               pos = i + 1
+                       end
+                       depth = depth + (byte == 40 and 1 or 0)
+               elseif byte == 41 then
+                       depth = depth - 1
+                       if depth <= 0 then
+                               if type(stack[#stack-1]) ~= "function" then
+                                       error("Datatype error, argument list follows non-function")
+                               end
+                               stack[#stack] = compile_datatype(code:sub(pos, i-1))
+                               pos = i + 1
+                       end
+               end
+       end
+
+       return stack
+end
+
+function verify_datatype(dt, value)
+       if dt and #dt > 0 then
+               if not cdt_cache[dt] then
+                       local c = compile_datatype(dt)
+                       if c and type(c[1]) == "function" then
+                               cdt_cache[dt] = c
+                       else
+                               error("Datatype error, not a function expression")
+                       end
+               end
+               if cdt_cache[dt] then
+                       return cdt_cache[dt][1](value, unpack(cdt_cache[dt][2]))
+               end
+       end
+       return true
+end
+
 
 -- Node pseudo abstract class
 Node = class()
@@ -209,7 +283,9 @@ end
 
 -- Render the children
 function Node.render_children(self, ...)
+       local k, node
        for k, node in ipairs(self.children) do
+               node.last_child = (k == #self.children)
                node:render(...)
        end
 end
@@ -322,10 +398,10 @@ function Map.parse(self, readinput, ...)
                                self.uci:apply(self.parsechain)
                                self:_run_hooks("on_apply", "on_after_apply")
                        else
-                               self._apply = function()
-                                       local cmd = self.uci:apply(self.parsechain, true)
-                                       return io.popen(cmd)
-                               end
+                               -- This is evaluated by the dispatcher and delegated to the
+                               -- template which in turn fires XHR to perform the actual
+                               -- apply actions.
+                               self.apply_needed = true
                        end
 
                        -- Reparse sections
@@ -358,12 +434,6 @@ end
 function Map.render(self, ...)
        self:_run_hooks("on_init")
        Node.render(self, ...)
-       if false and self._apply then
-               local fp = self._apply()
-               fp:read("*a")
-               fp:close()
-               self:_run_hooks("on_apply")
-       end
 end
 
 -- Creates a child section
@@ -384,10 +454,14 @@ end
 
 -- UCI set
 function Map.set(self, section, option, value)
-       if option then
-               return self.uci:set(self.config, section, option, value)
+       if type(value) ~= "table" or #value > 0 then
+               if option then
+                       return self.uci:set(self.config, section, option, value)
+               else
+                       return self.uci:set(self.config, section, value)
+               end
        else
-               return self.uci:set(self.config, section, value)
+               return Map.del(self, section, option)
        end
 end
 
@@ -805,7 +879,9 @@ function AbstractSection.render_tab(self, tab, ...)
        assert(tab and self.tabs and self.tabs[tab],
                "Cannot render not existing tab %q" % tostring(tab))
 
-       for _, node in ipairs(self.tabs[tab].childs) do
+       local k, node
+       for k, node in ipairs(self.tabs[tab].childs) do
+               node.last_child = (k == #self.tabs[tab].childs)
                node:render(...)
        end
 end
@@ -1091,10 +1167,10 @@ function TypedSection.parse(self, novld)
                -- Create
                local created
                local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
-               local name  = self.map:formvalue(crval)
+               local origin, name = next(self.map:formvaluetable(crval))
                if self.anonymous then
                        if name then
-                               created = self:create()
+                               created = self:create(nil, origin)
                        end
                else
                        if name then
@@ -1110,7 +1186,7 @@ function TypedSection.parse(self, novld)
                                end
 
                                if name and #name > 0 then
-                                       created = self:create(name) and name
+                                       created = self:create(name, origin) and name
                                        if not created then
                                                self.invalid_cts = true
                                        end
@@ -1123,6 +1199,20 @@ function TypedSection.parse(self, novld)
                end
        end
 
+       if self.sortable then
+               local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
+               local order = self.map:formvalue(stval)
+               if order and #order > 0 then
+                       local sid
+                       local num = 0
+                       for sid in util.imatch(order) do
+                               self.map.uci:reorder(self.config, sid, num)
+                               num = num + 1
+                       end
+                       self.changed = (num > 0)
+               end
+       end
+
        if created or self.changed then
                self:push_events()
        end
@@ -1281,7 +1371,7 @@ function AbstractValue.parse(self, section, novld)
                        self:add_error(section, "invalid", val_err)
                end
 
-               if fvalue and not (fvalue == cvalue) then
+               if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
                        if self:write(section, fvalue) then
                                -- Push events
                                self.section.changed = true
@@ -1307,29 +1397,8 @@ end
 function AbstractValue.render(self, s, scope)
        if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
                scope = scope or {}
-               scope.section   = s
-               scope.cbid      = self:cbid(s)
-               scope.striptags = luci.util.striptags
-               scope.pcdata    = luci.util.pcdata
-
-               scope.ifattr = function(cond,key,val)
-                       if cond then
-                               return string.format(
-                                       ' %s="%s"', tostring(key),
-                                       luci.util.pcdata(tostring( val
-                                        or scope[key]
-                                        or (type(self[key]) ~= "function" and self[key])
-                                        or "" ))
-                               )
-                       else
-                               return ''
-                       end
-               end
-
-               scope.attr = function(...)
-                       return scope.ifattr( true, ... )
-               end
-
+               scope.section = s
+               scope.cbid    = self:cbid(s)
                Node.render(self, scope)
        end
 end
@@ -1352,26 +1421,27 @@ function AbstractValue.cfgvalue(self, section)
                        return value[1]
                end
        elseif self.cast == "table" then
-               return luci.util.split(value, "%s+", nil, true)
+               return { value }
        end
 end
 
 -- Validate the form value
 function AbstractValue.validate(self, value)
-       if self.datatype and value and datatypes[self.datatype] then
+       if self.datatype and value then
                if type(value) == "table" then
                        local v
                        for _, v in ipairs(value) do
-                               if v and #v > 0 and not datatypes[self.datatype](v) then
+                               if v and #v > 0 and not verify_datatype(self.datatype, v) then
                                        return nil
                                end
                        end
                else
-                       if not datatypes[self.datatype](value) then
+                       if not verify_datatype(self.datatype, value) then
                                return nil
                        end
                end
        end
+
        return value
 end
 
@@ -1453,29 +1523,31 @@ function Flag.__init__(self, ...)
        AbstractValue.__init__(self, ...)
        self.template  = "cbi/fvalue"
 
-       self.enabled = "1"
+       self.enabled  = "1"
        self.disabled = "0"
+       self.default  = self.disabled
 end
 
 -- A flag can only have two states: set or unset
 function Flag.parse(self, section)
-       local fvalue = self:formvalue(section)
-
-       if fvalue then
-               fvalue = self.enabled
-       else
-               fvalue = self.disabled
-       end
+       local fexists = self.map:formvalue(
+               FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
 
-       if fvalue == self.enabled or (not self.optional and not self.rmempty) then
-               if not(fvalue == self:cfgvalue(section)) then
+       if fexists then
+               local fvalue = self:formvalue(section) and self.enabled or self.disabled
+               if fvalue ~= self.default or (not self.optional and not self.rmempty) then
                        self:write(section, fvalue)
+               else
+                       self:remove(section)
                end
        else
                self:remove(section)
        end
 end
 
+function Flag.cfgvalue(self, section)
+       return AbstractValue.cfgvalue(self, section) or self.default
+end
 
 
 --[[
@@ -1641,25 +1713,63 @@ function DynamicList.value(self, key, val)
        table.insert(self.vallist, tostring(val))
 end
 
-function DynamicList.write(self, ...)
-       self.map.proceed = true
-       return AbstractValue.write(self, ...)
+function DynamicList.write(self, section, value)
+       local t = { }
+
+       if type(value) == "table" then
+               local x
+               for _, x in ipairs(value) do
+                       if x and #x > 0 then
+                               t[#t+1] = x
+                       end
+               end
+       else
+               t = { value }
+       end
+
+       if self.cast == "string" then
+               value = table.concat(t, " ")
+       else
+               value = t
+       end
+
+       return AbstractValue.write(self, section, value)
+end
+
+function DynamicList.cfgvalue(self, section)
+       local value = AbstractValue.cfgvalue(self, section)
+
+       if type(value) == "string" then
+               local x
+               local t = { }
+               for x in value:gmatch("%S+") do
+                       if #x > 0 then
+                               t[#t+1] = x
+                       end
+               end
+               value = t
+       end
+
+       return value
 end
 
 function DynamicList.formvalue(self, section)
        local value = AbstractValue.formvalue(self, section)
-       value = (type(value) == "table") and value or {value}
 
-       local valid = {}
-       for i, v in ipairs(value) do
-               if v and #v > 0
-                and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
-                and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
-                       table.insert(valid, v)
+       if type(value) == "string" then
+               if self.cast == "string" then
+                       local x
+                       local t = { }
+                       for x in value:gmatch("%S+") do
+                               t[#t+1] = x
+                       end
+                       value = t
+               else
+                       value = { value }
                end
        end
 
-       return valid
+       return value
 end