summaryrefslogtreecommitdiffhomepage
path: root/src/ffluci
diff options
context:
space:
mode:
authorJo-Philipp Wich <jow@openwrt.org>2008-03-02 21:52:58 +0000
committerJo-Philipp Wich <jow@openwrt.org>2008-03-02 21:52:58 +0000
commit3f5de3273c9e103b4909802e339db06fe0b53312 (patch)
tree793ef66c9456665f7b472e214d79b1078fccebe8 /src/ffluci
* new project: ff-luci - Freifunk Lua Configuration Interface
Diffstat (limited to 'src/ffluci')
-rw-r--r--src/ffluci/config.lua37
-rw-r--r--src/ffluci/controller/admin/index.lua15
-rw-r--r--src/ffluci/controller/public/example-action.lua49
-rw-r--r--src/ffluci/controller/public/example-simpleview.lua27
-rw-r--r--src/ffluci/controller/public/index.lua32
-rw-r--r--src/ffluci/dispatcher.lua175
-rw-r--r--src/ffluci/fs.lua71
-rw-r--r--src/ffluci/http.lua118
-rw-r--r--src/ffluci/i18n.lua61
-rw-r--r--src/ffluci/i18n/example-simpleview.de6
-rw-r--r--src/ffluci/init.lua33
-rw-r--r--src/ffluci/menu.lua124
-rw-r--r--src/ffluci/model/uci.lua139
-rw-r--r--src/ffluci/template.lua189
-rw-r--r--src/ffluci/util.lua102
-rwxr-xr-xsrc/ffluci/view/example-simpleview/foo.htm3
-rwxr-xr-xsrc/ffluci/view/example-simpleview/index.htm6
-rw-r--r--src/ffluci/view/footer.htm3
-rw-r--r--src/ffluci/view/header.htm9
-rw-r--r--src/ffluci/view/hello.htm1
-rw-r--r--src/ffluci/view/menu.htm25
21 files changed, 1225 insertions, 0 deletions
diff --git a/src/ffluci/config.lua b/src/ffluci/config.lua
new file mode 100644
index 0000000000..f63dc1fad5
--- /dev/null
+++ b/src/ffluci/config.lua
@@ -0,0 +1,37 @@
+--[[
+FFLuCI - Configuration
+
+Description:
+Some FFLuCI configuration values
+
+ToDo:
+Port over to UCI
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+
+module("ffluci.config", package.seeall)
+
+-- This is where stylesheets and images go
+mediaurlbase = "/ffluci/media"
+
+-- Does anybody think about browser autodetect here?
+-- Too bad busybox doesn't populate HTTP_ACCEPT_LANGUAGE
+lang = "de" \ No newline at end of file
diff --git a/src/ffluci/controller/admin/index.lua b/src/ffluci/controller/admin/index.lua
new file mode 100644
index 0000000000..9aec94c867
--- /dev/null
+++ b/src/ffluci/controller/admin/index.lua
@@ -0,0 +1,15 @@
+module(..., package.seeall)
+
+function dispatcher(request)
+ require("ffluci.template").render("header")
+ print("Hello there, Mr. Administrator")
+ require("ffluci.template").render("footer")
+end
+
+menu = {
+ descr = "Administrative",
+ order = 10,
+ entries = {
+ {action = "index", descr = "Hello"}
+ }
+} \ No newline at end of file
diff --git a/src/ffluci/controller/public/example-action.lua b/src/ffluci/controller/public/example-action.lua
new file mode 100644
index 0000000000..538f5d9d05
--- /dev/null
+++ b/src/ffluci/controller/public/example-action.lua
@@ -0,0 +1,49 @@
+-- This example demonstrates the action dispatcher which invokes
+-- an appropriate action function named action_"action"
+
+-- This example consists of:
+-- ffluci/controller/index/example-action.lua (this file)
+
+-- Try the following address(es) in your browser:
+-- ffluci/index/example-action
+-- ffluci/index/example-action/sp
+-- ffluci/index/example-action/redir
+
+module(..., package.seeall)
+
+dispatcher = require("ffluci.dispatcher").action
+
+menu = {
+ descr = "Example Action",
+ order = 30,
+ entries = {
+ {action = "index", descr = "Action-Dispatcher Example"},
+ {action = "sp", descr = "Simple View Template Stealing"},
+ {action = "redir", descr = "Hello World Redirector"}
+ }
+}
+
+function action_index()
+ require("ffluci.template").render("header")
+ local formvalue = require("ffluci.http").formvalue
+
+ local x = formvalue("x", nil, true)
+
+ print(x and "x*x: "..tostring(x*x) or "Set ?x= any number")
+ require("ffluci.template").render("footer")
+end
+
+function action_sp()
+ require("ffluci.http")
+ require("ffluci.i18n")
+ require("ffluci.config")
+ require("ffluci.template")
+
+ -- Try uncommenting the next line
+ -- ffluci.i18n.loadc("example-simpleview")
+ ffluci.template.render("example-simpleview/index")
+end
+
+function action_redir()
+ require("ffluci.http").request_redirect("public", "index", "foobar")
+end \ No newline at end of file
diff --git a/src/ffluci/controller/public/example-simpleview.lua b/src/ffluci/controller/public/example-simpleview.lua
new file mode 100644
index 0000000000..61f4ad32cd
--- /dev/null
+++ b/src/ffluci/controller/public/example-simpleview.lua
@@ -0,0 +1,27 @@
+-- This example demonstrates the simple view dispatcher which is the
+-- most simple way to provide content as it directly renders the
+-- associated template
+
+-- This example consists of:
+-- ffluci/controller/index/example-simpleview.lua (this file)
+-- ffluci/view/example-simpleview/index.htm (the template for action "index")
+-- ffluci/view/example-simpleview/foo.htm (the template for action "foo")
+-- ffluci/i18n/example-simpleview.de (the german language file for this module)
+
+-- Try the following address(es) in your browser:
+-- ffluci/index/example-simpleview
+-- ffluci/index/example-simpleview/index
+-- ffluci/index/example-simpleview/foo
+
+module(..., package.seeall)
+
+dispatcher = require("ffluci.dispatcher").simpleview
+
+menu = {
+ descr = "Example Simpleview",
+ order = 20,
+ entries = {
+ {action = "index", descr = "Simpleview Index"},
+ {action = "foo", descr = "Simpleview Foo"}
+ }
+} \ No newline at end of file
diff --git a/src/ffluci/controller/public/index.lua b/src/ffluci/controller/public/index.lua
new file mode 100644
index 0000000000..4498c77edf
--- /dev/null
+++ b/src/ffluci/controller/public/index.lua
@@ -0,0 +1,32 @@
+-- This is a very simple example Hello World FFLuCI controller
+-- See the other examples for more automated controllers
+
+-- Initialise Lua module system
+module(..., package.seeall)
+
+-- This is the module dispatcher. It implements the last step of the
+-- dispatching process.
+function dispatcher(request)
+ require("ffluci.template").render("header")
+ print("<h2>Hello World!</h2>")
+ for k,v in pairs(request) do
+ print("<div>" .. k .. ": " .. v .. "</div>")
+ end
+ require("ffluci.template").render("footer")
+end
+
+-- The following part is optional it could be useful for menu generators
+-- An example menu generator is implemented in the template "menu"
+
+menu = {
+ -- This is the menu item description
+ descr = "Hello World",
+
+ -- This is the order level of the menu entry (lowest goes first)
+ order = 10,
+
+ -- A list of menu entries in the form action => "description"
+ entries = {
+ {action = "index", descr = "Hello World"},
+ }
+} \ No newline at end of file
diff --git a/src/ffluci/dispatcher.lua b/src/ffluci/dispatcher.lua
new file mode 100644
index 0000000000..f43d7f5a9d
--- /dev/null
+++ b/src/ffluci/dispatcher.lua
@@ -0,0 +1,175 @@
+--[[
+FFLuCI - Dispatcher
+
+Description:
+The request dispatcher and module dispatcher generators
+
+
+The dispatching process:
+ For a detailed explanation of the dispatching process we assume:
+ You have installed the FFLuCI CGI-Dispatcher in /cgi-bin/ffluci
+
+ To enforce a higher level of security only the CGI-Dispatcher
+ resides inside the web server's document root, everything else
+ stays inside an external directory, we assume this is /lua/ffluci
+ for this explanation.
+
+ All controllers and action are reachable as sub-objects of /cgi-bin/ffluci
+ as if they were virtual folders and files
+ e.g.: /cgi-bin/ffluci/public/info/about
+ /cgi-bin/ffluci/admin/network/interfaces
+ and so on.
+
+ The PATH_INFO variable holds the dispatch path and
+ will be split into three parts: /category/module/action
+
+ Category: This is the category in which modules are stored in
+ By default there are two categories:
+ "public" - which is the default public category
+ "admin" - which is the default protected category
+
+ As FFLuCI itself does not implement authentication
+ you should make sure that "admin" and other sensitive
+ categories are protected by the webserver.
+
+ E.g. for busybox add a line like:
+ /cgi-bin/ffluci/admin:root:$p$root
+ to /etc/httpd.conf to protect the "admin" category
+
+
+ Module: This is the controller which will handle the request further
+ It is always a submodule of ffluci.controller, so a module
+ called "helloworld" will be stored in
+ /lua/ffluci/controller/helloworld.lua
+ You are free to submodule your controllers any further.
+
+ Action: This is action that will be invoked after loading the module.
+ The kind of how the action will be dispatched depends on
+ the module dispatcher that is defined in the controller.
+ See the description of the default module dispatcher down
+ on this page for some examples.
+
+
+ The main dispatcher at first searches for the module by trying to
+ include ffluci.controller.category.module
+ (where "category" is the category name and "module" is the module name)
+ If this fails a 404 status code will be send to the client and FFLuCI exits
+
+ Then the main dispatcher calls the module dispatcher
+ ffluci.controller.category.module.dispatcher with the request object
+ as the only argument. The module dispatcher is then responsible
+ for the further dispatching process.
+
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+
+module("ffluci.dispatcher", package.seeall)
+require("ffluci.http")
+require("ffluci.template")
+
+
+-- Dispatches the "request"
+function dispatch(req)
+ request = req
+ local m = "ffluci.controller." .. request.category .. "." .. request.module
+ local stat, module = pcall(require, m)
+ if not stat then
+ return error404()
+ else
+ module.request = request
+ setfenv(module.dispatcher, module)
+ return module.dispatcher(request)
+ end
+end
+
+
+-- Sends a 404 error code and renders the "error404" template if available
+function error404(message)
+ message = message or "Not Found"
+
+ ffluci.http.status(404, "Not Found")
+
+ if not pcall(ffluci.template.render, "error404") then
+ ffluci.http.textheader()
+ print(message)
+ end
+ return false
+end
+
+-- Sends a 500 error code and renders the "error500" template if available
+function error500(message)
+ ffluci.http.status(500, "Internal Server Error")
+
+ if not pcall(ffluci.template.render, "error500") then
+ ffluci.http.textheader()
+ print(message)
+ end
+ return false
+end
+
+
+-- Dispatches a request depending on the PATH_INFO variable
+function httpdispatch()
+ local pathinfo = os.getenv("PATH_INFO") or ""
+ local parts = pathinfo:gmatch("/[%w-]+")
+
+ local sanitize = function(s, default)
+ return s and s:sub(2) or default
+ end
+
+ local cat = sanitize(parts(), "public")
+ local mod = sanitize(parts(), "index")
+ local act = sanitize(parts(), "index")
+
+ dispatch({category=cat, module=mod, action=act})
+end
+
+-- The Simple View Dispatcher directly renders the template
+-- which is placed in ffluci/views/"request.module"/"request.action"
+function simpleview(request)
+ local i18n = require("ffluci.i18n")
+ local tmpl = require("ffluci.template")
+ local conf = require("ffluci.config")
+ local disp = require("ffluci.dispatcher")
+
+ pcall(i18n.load, request.module .. "." .. conf.lang)
+ if not pcall(tmpl.get, request.module .. "/" .. request.action) then
+ disp.error404()
+ else
+ tmpl.render(request.module .. "/" .. request.action)
+ end
+end
+
+-- The Action Dispatcher searches the module for any function called
+-- action_"request.action" and calls it
+function action(request)
+ local i18n = require("ffluci.i18n")
+ local conf = require("ffluci.config")
+ local disp = require("ffluci.dispatcher")
+
+ pcall(i18n.load, request.module .. "." .. conf.lang)
+ local action = getfenv()["action_" .. request.action:gsub("-", "_")]
+ if action then
+ action()
+ else
+ disp.error404()
+ end
+end \ No newline at end of file
diff --git a/src/ffluci/fs.lua b/src/ffluci/fs.lua
new file mode 100644
index 0000000000..5a1cc6b351
--- /dev/null
+++ b/src/ffluci/fs.lua
@@ -0,0 +1,71 @@
+--[[
+FFLuCI - Filesystem tools
+
+Description:
+A module offering often needed filesystem manipulation functions
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+
+module("ffluci.fs", package.seeall)
+
+require("lfs")
+
+-- Returns the content of file
+function readfile(filename)
+ local fp = io.open(filename)
+ if fp == nil then
+ error("Unable to open file for reading: " .. filename)
+ end
+ local data = fp:read("*a")
+ fp:close()
+ return data
+end
+
+-- Writes given data to a file
+function writefile(filename, data)
+ local fp = io.open(filename, "w")
+ if fp == nil then
+ error("Unable to open file for writing: " .. filename)
+ end
+ fp:write(data)
+ fp:close()
+end
+
+-- Returns the file modification date/time of "path"
+function mtime(path)
+ return lfs.attributes(path, "modification")
+end
+
+-- Simplified dirname function
+function dirname(file)
+ return string.gsub(file, "[^/]+$", "")
+end
+
+-- Diriterator - alias for lfs.dir - filter . and ..
+function dir(path)
+ local e = {}
+ for entry in lfs.dir(path) do
+ if not(entry == "." or entry == "..") then
+ table.insert(e, entry)
+ end
+ end
+ return e
+end \ No newline at end of file
diff --git a/src/ffluci/http.lua b/src/ffluci/http.lua
new file mode 100644
index 0000000000..7aadf8b33d
--- /dev/null
+++ b/src/ffluci/http.lua
@@ -0,0 +1,118 @@
+--[[
+FFLuCI - HTTP-Interaction
+
+Description:
+HTTP-Header manipulator and form variable preprocessor
+
+FileId:
+$Id$
+
+ToDo:
+- Cookie handling
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+
+module("ffluci.http", package.seeall)
+
+require("ffluci.util")
+
+-- Sets HTTP-Status-Header
+function status(code, message)
+ print("Status: " .. tostring(code) .. " " .. message)
+end
+
+
+-- Asks the browser to redirect to "url"
+function redirect(url)
+ status(302, "Found")
+ print("Location: " .. url .. "\n")
+end
+
+
+-- Same as redirect but accepts category, module and action for internal use
+function request_redirect(category, module, action)
+ category = category or "public"
+ module = module or "index"
+ action = action or "index"
+
+ local pattern = os.getenv("SCRIPT_NAME") .. "/%s/%s/%s"
+ redirect(pattern:format(category, module, action))
+end
+
+-- Form validation function:
+-- Gets a form variable "key".
+-- If it does not exist: return "default"
+-- If cast_number is true and "key" is not a number: return "default"
+-- If valid is a table and "key" is not in it: return "default"
+-- If valid is a function and returns nil: return "default"
+-- Else return the value of "key"
+--
+-- Examples:
+-- Get a form variable "foo" and return "bar" if it is not set
+-- = formvalue("foo", "bar")
+--
+-- Get "foo" and make sure it is either "bar" or "baz"
+-- = formvalue("foo", nil, nil, {"bar", "baz"})
+--
+-- Get "foo", make sure its a number and below 10 else return 5
+-- = formvalue("foo", 5, true, function(a) return a < 10 and a or nil end)
+function formvalue(key, default, cast_number, valid, table)
+ table = table or formvalues()
+
+ if table[key] == nil then
+ return default
+ else
+ local value = table[key]
+
+ value = cast_number and tonumber(value) or not cast_number and nil
+
+ if type(valid) == "function" then
+ value = valid(value)
+ elseif type(valid) == "table" then
+ if not ffluci.util.contains(valid, value) then
+ value = nil
+ end
+ end
+
+ return value or default
+ end
+end
+
+
+-- Returns a table of all COOKIE, GET and POST Parameters
+function formvalues()
+ return FORM
+end
+
+
+-- Prints plaintext content-type header
+function textheader()
+ print("Content-Type: text/plain\n")
+end
+
+
+-- Prints html content-type header
+function htmlheader()
+ print("Content-Type: text/html\n")
+end
+
+
+-- Prints xml content-type header
+function xmlheader()
+ print("Content-Type: text/xml\n")
+end
diff --git a/src/ffluci/i18n.lua b/src/ffluci/i18n.lua
new file mode 100644
index 0000000000..2a18c27d59
--- /dev/null
+++ b/src/ffluci/i18n.lua
@@ -0,0 +1,61 @@
+--[[
+FFLuCI - Internationalisation
+
+Description:
+A very minimalistic but yet effective internationalisation module
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+
+module("ffluci.i18n", package.seeall)
+
+require("ffluci.fs")
+require("ffluci.util")
+require("ffluci.config")
+
+table = {}
+i18ndir = ffluci.fs.dirname(ffluci.util.__file__()) .. "i18n/"
+
+-- Clears the translation table
+function clear()
+ table = {}
+end
+
+-- Loads a translation and copies its data into the global translation table
+function load(file)
+ local f = loadfile(i18ndir .. file)
+ if f then
+ setfenv(f, table)
+ f()
+ return true
+ else
+ return false
+ end
+end
+
+-- Same as load but autocompletes the filename with .LANG from config.lang
+function loadc(file)
+ return load(file .. "." .. ffluci.config.lang)
+end
+
+-- Returns the i18n-value defined by "key" or if there is no such: "default"
+function translate(key, default)
+ return table[key] or default
+end \ No newline at end of file
diff --git a/src/ffluci/i18n/example-simpleview.de b/src/ffluci/i18n/example-simpleview.de
new file mode 100644
index 0000000000..db2bee0cf7
--- /dev/null
+++ b/src/ffluci/i18n/example-simpleview.de
@@ -0,0 +1,6 @@
+descr = [[Dies ist das Simple View-Beispiel.<br />
+Dieses Template ist: ffluci/view/example-simpleview/index.htm und gehoert
+zur Aktion "index".<br />
+Diese Uebersetzung ist: ffluci/i18n/example-simpleview.de]]
+
+lan = "Die LAN IP-Adresse des Routers lautet:" \ No newline at end of file
diff --git a/src/ffluci/init.lua b/src/ffluci/init.lua
new file mode 100644
index 0000000000..4585f51fb0
--- /dev/null
+++ b/src/ffluci/init.lua
@@ -0,0 +1,33 @@
+--[[
+FFLuCI - Freifunk Lua Configuration Interface
+
+Description:
+This is the init file
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+module("ffluci", package.seeall)
+
+__version__ = "0.1"
+__appname__ = "FFLuCI"
+
+dispatch = require("ffluci.dispatcher").httpdispatch
+env = ENV
+form = FORM
diff --git a/src/ffluci/menu.lua b/src/ffluci/menu.lua
new file mode 100644
index 0000000000..7b192aaea9
--- /dev/null
+++ b/src/ffluci/menu.lua
@@ -0,0 +1,124 @@
+--[[
+FFLuCI - Menu Builder
+
+Description:
+Collects menu building information from controllers
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+module("ffluci.menu", package.seeall)
+
+require("ffluci.fs")
+require("ffluci.util")
+require("ffluci.template")
+
+ctrldir = ffluci.fs.dirname(ffluci.util.__file__()) .. "controller/"
+modelpath = ffluci.fs.dirname(ffluci.util.__file__()) .. "model/menudata.lua"
+
+-- Cache menudata into a Luafile instead of recollecting it at every pageload
+-- Warning: Make sure the menudata cache gets deleted everytime you update
+-- the menu information of any module or add or remove a module
+builder_enable_cache = false
+
+
+-- Builds the menudata file
+function build()
+ local data = collect()
+ ffluci.fs.writefile(modelpath, dump(data, "m"))
+ return data
+end
+
+
+-- Collect all menu information provided in the controller modules
+function collect()
+ local m = {}
+ for k,cat in pairs(ffluci.fs.dir(ctrldir)) do
+ m[cat] = {}
+ for k,con in pairs(ffluci.fs.dir(ctrldir .. "/" .. cat)) do
+ if con:sub(-4) == ".lua" then
+ con = con:sub(1, con:len()-4)
+ local mod = require("ffluci.controller." .. cat .. "." .. con)
+ if mod.menu and mod.menu.descr
+ and mod.menu.entries and mod.menu.order then
+ local entry = {}
+ entry[".descr"] = mod.menu.descr
+ entry[".order"] = mod.menu.order
+ entry[".contr"] = con
+ for k,v in pairs(mod.menu.entries) do
+ entry[k] = v
+ end
+ local i = 0
+ for k,v in ipairs(m[cat]) do
+ if v[".order"] > entry[".order"] then
+ break
+ end
+ i = k
+ end
+ table.insert(m[cat], i+1, entry)
+ end
+ end
+ end
+ end
+ return m
+end
+
+
+-- Dumps a table into a string of Lua code
+function dump(tbl, name)
+ local src = name .. "={}\n"
+ for k,v in pairs(tbl) do
+ if type(k) == "string" then
+ k = ffluci.util.escape(k)
+ k = "'" .. ffluci.util.escape(k, "'") .. "'"
+ end
+ if type(v) == "string" then
+ v = ffluci.util.escape(v)
+ v = ffluci.util.escape(v, "'")
+ src = src .. name .. "[" .. k .. "]='" .. v .. "'\n"
+ elseif type(v) == "number" then
+ src = src .. name .. "[" .. k .. "]=" .. v .. "\n"
+ elseif type(v) == "table" then
+ src = src .. dump(v, name .. "[" .. k .. "]")
+ end
+ end
+ return src
+end
+
+-- Returns the menu information
+function get()
+ if builder_enable_cache then
+ local cachemt = ffluci.fs.mtime(modelpath)
+ local data = nil
+
+ if cachemt == nil then
+ data = build()
+ else
+ local fenv = {}
+ local f = loadfile(modelpath)
+ setfenv(f, fenv)
+ f()
+ data = fenv.m
+ end
+
+ return data
+ else
+ return collect()
+ end
+end \ No newline at end of file
diff --git a/src/ffluci/model/uci.lua b/src/ffluci/model/uci.lua
new file mode 100644
index 0000000000..492367ce28
--- /dev/null
+++ b/src/ffluci/model/uci.lua
@@ -0,0 +1,139 @@
+--[[
+FFLuCI - UCI wrapper library
+
+Description:
+Wrapper for the /sbin/uci application, syntax of implemented functions
+is comparable to the syntax of the uci application
+
+Any return value of false or nil can be interpreted as an error
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+module("ffluci.model.uci", package.seeall)
+require("ffluci.util")
+
+ucicmd = "uci"
+
+-- Wrapper for "uci add"
+function add(config, section_type)
+ return _uci("add " .. _path(config) .. " " .. _path(section_type))
+end
+
+
+-- Wrapper for "uci changes"
+function changes(config)
+ return _uci3("changes " .. _path(config))
+end
+
+
+-- Wrapper for "uci commit"
+function commit(config)
+ return _uci2("commit " .. _path(config))
+end
+
+
+-- Wrapper for "uci get"
+function get(config, section, option)
+ return _uci("get " .. _path(config, section, option))
+end
+
+
+-- Wrapper for "uci revert"
+function revert(config)
+ return _uci2("revert " .. _path(config))
+end
+
+
+-- Wrapper for "uci show"
+function show(config)
+ return _uci3("show " .. _path(config))
+end
+
+
+-- Wrapper for "uci set"
+function set(config, section, option, value)
+ return _uci2("set " .. _path(config, section, option, value))
+end
+
+
+-- Internal functions --
+
+function _uci(cmd)
+ local res = ffluci.util.exec(ucicmd .. " 2>/dev/null " .. cmd)
+
+ if res:len() == 0 then
+ return nil
+ else
+ return res:sub(1, res:len()-1)
+ end
+end
+
+function _uci2(cmd)
+ local res = ffluci.util.exec(ucicmd .. " 2>&1 " .. cmd)
+
+ if res:len() > 0 then
+ return false, res
+ else
+ return true
+ end
+end
+
+function _uci3(cmd)
+ local res = ffluci.util.exec(ucicmd .. " 2>&1 " .. cmd, true)
+ if res[1]:sub(1, ucicmd:len() + 1) == ucicmd .. ":" then
+ return nil, res[1]
+ end
+
+ table = {}
+
+ for k,line in pairs(res) do
+ c, s, t = line:match("^([^.]-)%.([^.]-)=(.-)$")
+ if c then
+ table[c] = table[c] or {}
+ table[c][s] = {}
+ table[c][s][".type"] = t
+ end
+
+ c, s, o, v = line:match("^([^.]-)%.([^.]-)%.([^.]-)=(.-)$")
+ if c then
+ table[c][s][o] = v
+ end
+ end
+
+ return table
+end
+
+-- Build path (config.section.option=value) and prevent command injection
+function _path(...)
+ local result = ""
+
+ -- Not using ipairs because it is not reliable in case of nil arguments
+ arg.n = nil
+ for k,v in pairs(arg) do
+ if k == 1 then
+ result = "'" .. v:gsub("['.]", "") .. "'"
+ elseif k < 4 then
+ result = result .. ".'" .. v:gsub("['.]", "") .. "'"
+ elseif k == 4 then
+ result = result .. "='" .. v:gsub("'", "") .. "'"
+ end
+ end
+ return result
+end \ No newline at end of file
diff --git a/src/ffluci/template.lua b/src/ffluci/template.lua
new file mode 100644
index 0000000000..8c7f07f94a
--- /dev/null
+++ b/src/ffluci/template.lua
@@ -0,0 +1,189 @@
+--[[
+FFLuCI - Template Parser
+
+Description:
+A template parser supporting includes, translations, Lua code blocks
+and more. It can be used either as a compiler or as an interpreter.
+
+FileId: $Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+module("ffluci.template", package.seeall)
+
+require("ffluci.config")
+require("ffluci.util")
+require("ffluci.fs")
+require("ffluci.i18n")
+require("ffluci.model.uci")
+
+viewdir = ffluci.fs.dirname(ffluci.util.__file__()) .. "view/"
+
+
+-- Compile modes:
+-- none: Never compile, only render precompiled
+-- memory: Always compile, do not save compiled files, ignore precompiled
+-- always: Same as "memory" but also saves compiled files
+-- smart: Compile on demand, save compiled files, update precompiled
+compiler_mode = "smart"
+
+
+-- This applies to compiler modes "always" and "smart"
+--
+-- Produce compiled lua code rather than lua sourcecode
+-- WARNING: Increases template size heavily!!!
+-- This produces the same bytecode as luac but does not have a strip option
+compiler_enable_bytecode = false
+
+
+-- Define the namespace for template modules
+viewns = {
+ translate = ffluci.i18n.translate,
+ config = ffluci.model.uci.get,
+ controller = os.getenv("SCRIPT_NAME"),
+ media = ffluci.config.mediaurlbase,
+ include = function(name) return render(name, getfenv(2)) end,
+ write = io.write
+}
+
+
+-- Compiles and builds a given template
+function build(template, compiled)
+ local template = compile(ffluci.fs.readfile(template))
+
+ if compiled then
+ ffluci.fs.writefile(compiled, template)
+ end
+
+ return template
+end
+
+
+-- Compiles a given template into an executable Lua module
+function compile(template)
+ -- Search all <% %> expressions (remember: Lua table indexes begin with #1)
+ local function expr_add(command)
+ table.insert(expr, command)
+ return "<%" .. tostring(#expr) .. "%>"
+ end
+
+ -- As "expr" should be local, we have to assign it to the "expr_add" scope
+ local expr = {}
+ ffluci.util.extfenv(expr_add, "expr", expr)
+
+ -- Save all expressiosn to table "expr"
+ template = template:gsub("<%%(.-)%%>", expr_add)
+
+ local function sanitize(s)
+ s = ffluci.util.escape(s)
+ s = ffluci.util.escape(s, "'")
+ s = ffluci.util.escape(s, "\n")
+ return s
+ end
+
+ -- Escape and sanitize all the template (all non-expressions)
+ template = sanitize(template)
+
+ -- Template module header/footer declaration
+ local header = "write('"
+ local footer = "')"
+
+ template = header .. template .. footer
+
+ -- Replacements
+ local r_include = "')\ninclude('%s')\nwrite('"
+ local r_i18n = "'..translate('%1','%2')..'"
+ local r_uci = "'..config('%1','%2','%3')..'"
+ local r_pexec = "'..%s..'"
+ local r_exec = "')\n%s\nwrite('"
+
+ -- Parse the expressions
+ for k,v in pairs(expr) do
+ local p = v:sub(1, 1)
+ local re = nil
+ if p == "+" then
+ re = r_include:format(sanitize(string.sub(v, 2)))
+ elseif p == ":" then
+ re = sanitize(v):gsub(":(.-) (.+)", r_i18n)
+ elseif p == "~" then
+ re = sanitize(v):gsub("~(.-)%.(.-)%.(.+)", r_uci)
+ elseif p == "=" then
+ re = r_pexec:format(string.sub(v, 2))
+ else
+ re = r_exec:format(v)
+ end
+ template = template:gsub("<%%"..tostring(k).."%%>", re)
+ end
+
+ if compiler_enable_bytecode then
+ tf = loadstring(template)
+ template = string.dump(tf)
+ end
+
+ return template
+end
+
+
+-- Returns and builds the template for "name" depending on the compiler mode
+function get(name)
+ local templatefile = viewdir .. name .. ".htm"
+ local compiledfile = viewdir .. name .. ".lua"
+ local template = nil
+
+ if compiler_mode == "smart" then
+ local tplmt = ffluci.fs.mtime(templatefile)
+ local commt = ffluci.fs.mtime(compiledfile)
+
+ -- Build if there is no compiled file or if compiled file is outdated
+ if ((commt == nil) and not (tplmt == nil))
+ or (not (commt == nil) and not (tplmt == nil) and commt < tplmt) then
+ template = loadstring(build(templatefile, compiledfile))
+ else
+ template = loadfile(compiledfile)
+ end
+
+ elseif compiler_mode == "none" then
+ template = loadfile(compiledfile)
+
+ elseif compiler_mode == "memory" then
+ template = loadstring(build(templatefile))
+
+ elseif compiler_mode == "always" then
+ template = loadstring(build(templatefile, compiledfile))
+
+ else
+ error("Invalid compiler mode: " .. compiler_mode)
+
+ end
+
+ return template or error("Unable to load template: " .. name)
+end
+
+-- Renders a template
+function render(name, scope)
+ scope = scope or getfenv(2)
+
+ -- Our template module
+ local view = get(name)
+
+ -- Put our predefined objects in the scope of the template
+ ffluci.util.updfenv(view, scope)
+ ffluci.util.updfenv(view, viewns)
+
+ -- Now finally render the thing
+ return view()
+end \ No newline at end of file
diff --git a/src/ffluci/util.lua b/src/ffluci/util.lua
new file mode 100644
index 0000000000..07cbb8000c
--- /dev/null
+++ b/src/ffluci/util.lua
@@ -0,0 +1,102 @@
+--[[
+FFLuCI - Utility library
+
+Description:
+Several common useful Lua functions
+
+FileId:
+$Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+]]--
+
+module("ffluci.util", package.seeall)
+
+-- Checks whether a table has an object "value" in it
+function contains(table, value)
+ for k,v in pairs(table) do
+ if value == v then
+ return true
+ end
+ end
+ return false
+end
+
+
+-- Dumps a table to stdout (useful for testing and debugging)
+function dumptable(t, i)
+ i = i or 0
+ for k,v in pairs(t) do
+ print(string.rep("\t", i) .. k, v)
+ if type(v) == "table" then
+ dumptable(v, i+1)
+ end
+ end
+end
+
+
+-- Escapes all occurences of c in s
+function escape(s, c)
+ c = c or "\\"
+ return s:gsub(c, "\\" .. c)
+end
+
+
+-- Runs "command" and returns its output
+function exec(command, return_array)
+ local pp = io.popen(command)
+ local data = nil
+
+ if return_array then
+ local line = ""
+ data = {}
+
+ while true do
+ line = pp:read()
+ if (line == nil) then break end
+ table.insert(data, line)
+ end
+ pp:close()
+ else
+ data = pp:read("*a")
+ pp:close()
+ end
+
+ return data
+end
+
+-- Populate obj in the scope of f as key
+function extfenv(f, key, obj)
+ local scope = getfenv(f)
+ scope[key] = obj
+ setfenv(f, scope)
+end
+
+
+-- Updates the scope of f with "extscope"
+function updfenv(f, extscope)
+ local scope = getfenv(f)
+ for k, v in pairs(extscope) do
+ scope[k] = v
+ end
+ setfenv(f, scope)
+end
+
+-- Returns the filename of the calling script
+function __file__()
+ return debug.getinfo(2, 'S').source:sub(2)
+end \ No newline at end of file
diff --git a/src/ffluci/view/example-simpleview/foo.htm b/src/ffluci/view/example-simpleview/foo.htm
new file mode 100755
index 0000000000..a0df536f16
--- /dev/null
+++ b/src/ffluci/view/example-simpleview/foo.htm
@@ -0,0 +1,3 @@
+<%+header%>
+<h1>bar</h1>
+<%+footer%> \ No newline at end of file
diff --git a/src/ffluci/view/example-simpleview/index.htm b/src/ffluci/view/example-simpleview/index.htm
new file mode 100755
index 0000000000..ffe1ccf71d
--- /dev/null
+++ b/src/ffluci/view/example-simpleview/index.htm
@@ -0,0 +1,6 @@
+<%+header%>
+<p><%:descr This is the Simple View-Example.<br />
+This template is ffluci/view/example-simpleview/index.htm and belongs
+to the index-Action.%></p>
+<p><%:lan The router's LAN IP-Address is:%> <%~network.lan.ipaddr%></p>
+<%+footer%> \ No newline at end of file
diff --git a/src/ffluci/view/footer.htm b/src/ffluci/view/footer.htm
new file mode 100644
index 0000000000..17c7245b64
--- /dev/null
+++ b/src/ffluci/view/footer.htm
@@ -0,0 +1,3 @@
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/src/ffluci/view/header.htm b/src/ffluci/view/header.htm
new file mode 100644
index 0000000000..f47388a424
--- /dev/null
+++ b/src/ffluci/view/header.htm
@@ -0,0 +1,9 @@
+<% require("ffluci.http").htmlheader() %>
+<html>
+<head>
+<title>FFLuCI Examples</title>
+</head>
+<body>
+<h1>FFLuCI</h1>
+<%+menu%>
+<div id="content"> \ No newline at end of file
diff --git a/src/ffluci/view/hello.htm b/src/ffluci/view/hello.htm
new file mode 100644
index 0000000000..8231b61f96
--- /dev/null
+++ b/src/ffluci/view/hello.htm
@@ -0,0 +1 @@
+A very little Hello <%=muh%>
diff --git a/src/ffluci/view/menu.htm b/src/ffluci/view/menu.htm
new file mode 100644
index 0000000000..8d5c597cf7
--- /dev/null
+++ b/src/ffluci/view/menu.htm
@@ -0,0 +1,25 @@
+<%
+local req = require("ffluci.dispatcher").request
+local menu = require("ffluci.menu").get()[req.category]
+local menu_module = nil
+require("ffluci.i18n").loadc("default")
+%>
+<div id="menu" style="font-size: 0.8em; padding-bottom: 20px">
+ <div id="menu_categories">
+ <span style="<% if "public" == req.category then write("font-weight: bold") end %>"><a href="<%=controller%>/public"><%:public Public%></a></span>
+ <span style="<% if "admin" == req.category then write("font-weight: bold") end %>"><a href="<%=controller%>/admin"><%:admin Admin%></a></span>
+ </div>
+ <div id="menu_modules">
+<% for k,v in pairs(menu) do
+if v[".contr"] == req.module then menu_module = v end %>
+ <span style="<% if v[".contr"] == req.module then write("font-weight: bold") end %>"><a href="<%=controller.."/"..req.category.."/"..v[".contr"]%>"><%=translate(v[".contr"], v[".descr"])%></a></span>
+<% end %>
+ </div>
+<% if menu_module then %>
+ <div id="menu_actions">
+<% for k,v in ipairs(menu_module) do %>
+ <span style="<% if v.action == req.action then write("font-weight: bold") end %>"><a href="<%=controller.."/"..req.category.."/"..req.module.."/"..v.action%>"><%=translate(v.action, v.descr)%></a></span>
+<% end %>
+ </div>
+<% end %>
+</div>