* new project: ff-luci - Freifunk Lua Configuration Interface
authorJo-Philipp Wich <jow@openwrt.org>
Sun, 2 Mar 2008 21:52:58 +0000 (21:52 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Sun, 2 Mar 2008 21:52:58 +0000 (21:52 +0000)
33 files changed:
.buildpath [new file with mode: 0644]
.project [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
contrib/ffluci [new file with mode: 0755]
contrib/index.cgi [new file with mode: 0755]
contrib/package/ffluci/Makefile [new file with mode: 0644]
contrib/package/ffluci/ipkg/ffluci.postinst [new file with mode: 0755]
contrib/package/ffluci/src [new symlink]
contrib/package/haserl-devel/Makefile [new file with mode: 0644]
contrib/package/luafilesystem/Makefile [new file with mode: 0644]
src/ffluci/config.lua [new file with mode: 0644]
src/ffluci/controller/admin/index.lua [new file with mode: 0644]
src/ffluci/controller/public/example-action.lua [new file with mode: 0644]
src/ffluci/controller/public/example-simpleview.lua [new file with mode: 0644]
src/ffluci/controller/public/index.lua [new file with mode: 0644]
src/ffluci/dispatcher.lua [new file with mode: 0644]
src/ffluci/fs.lua [new file with mode: 0644]
src/ffluci/http.lua [new file with mode: 0644]
src/ffluci/i18n.lua [new file with mode: 0644]
src/ffluci/i18n/example-simpleview.de [new file with mode: 0644]
src/ffluci/init.lua [new file with mode: 0644]
src/ffluci/menu.lua [new file with mode: 0644]
src/ffluci/model/uci.lua [new file with mode: 0644]
src/ffluci/template.lua [new file with mode: 0644]
src/ffluci/util.lua [new file with mode: 0644]
src/ffluci/view/example-simpleview/foo.htm [new file with mode: 0755]
src/ffluci/view/example-simpleview/index.htm [new file with mode: 0755]
src/ffluci/view/footer.htm [new file with mode: 0644]
src/ffluci/view/header.htm [new file with mode: 0644]
src/ffluci/view/hello.htm [new file with mode: 0644]
src/ffluci/view/menu.htm [new file with mode: 0644]

diff --git a/.buildpath b/.buildpath
new file mode 100644 (file)
index 0000000..81fa646
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<buildpath>
+       <buildpathentry kind="src" path="src"/>
+       <buildpathentry kind="con" path="org.eclipse.dltk.launching.INTERPRETER_CONTAINER"/>
+</buildpath>
diff --git a/.project b/.project
new file mode 100644 (file)
index 0000000..1b9dc67
--- /dev/null
+++ b/.project
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>ffluci</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.dltk.lua.core.nature</nature>
+       </natures>
+</projectDescription>
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..f49a4e1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..38b7981
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,40 @@
+LUAC = luac
+LUAC_OPTIONS = -s
+
+FILES = ffluci/config.lua
+
+CFILES = ffluci/util.lua ffluci/http.lua \
+ffluci/fs.lua ffluci/i18n.lua ffluci/model/uci.lua \
+ffluci/template.lua ffluci/dispatcher.lua ffluci/menu.lua ffluci/init.lua
+
+DIRECTORIES = dist/ffluci/model dist/ffluci/controller/public dist/ffluci/controller/admin dist/ffluci/i18n dist/ffluci/view
+
+INFILES = $(CFILES:%=src/%)
+OUTFILE = ffluci/init.lua
+
+all: compile
+
+dist-compile: compile examples
+dist-source: source examples
+
+examples:
+       cp src/ffluci/controller/public/* dist/ffluci/controller/public/
+       cp src/ffluci/controller/admin/* dist/ffluci/controller/admin/
+       cp src/ffluci/i18n/* dist/ffluci/i18n/
+       cp src/ffluci/view/* dist/ffluci/view/ -R
+
+compile:
+       mkdir -p $(DIRECTORIES)
+       $(LUAC) $(LUAC_OPTIONS) -o dist/$(OUTFILE) $(INFILES)
+       for i in $(CFILES); do [ -f dist/$$i ] || ln -s `dirname $$i | cut -s -d / -f 2- | sed -e 's/[^/]*\/*/..\//g'``basename $(OUTFILE)` dist/$$i; done
+       for i in $(FILES); do cp src/$$i dist/$$i; done
+
+source:
+       mkdir -p $(DIRECTORIES)
+       for i in $(CFILES); do cp src/$$i dist/$$i; done
+       for i in $(FILES); do cp src/$$i dist/$$i; done
+       
+
+.PHONY: clean
+clean:
+       rm dist -rf
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..05c4eb8
--- /dev/null
+++ b/README
@@ -0,0 +1,56 @@
+FFLuCI - Freifunk Lua Configuration Interface
+
+This is a leightweight MVC-Webframework for small embedded device.
+It uses the the Lua programming language and relies on Haserl.
+
+It consists of several parts:
+
+MVC Dispatcher
+       Simple PATH_INFO based dispatching mechanism using Lua modules
+       
+       > See src/ffluci/dispatcher.lua for a detailed description
+       > See src/ffluci/controller for example controllers
+
+
+Template engine
+       Support for plain and compiled templates, on-demand compiling support
+       Short markups:
+               <% Lua-Code %> 
+               <%= Lua-Code with return value %>
+               <%:i18nkey default translation%>
+               <%+template-to-be-included%>
+               <%~uci.short.cut%>
+
+       Predefined variables for controller dir and media dir
+
+       > See src/ffluci/template.lua for details
+       > See src/view/ for examples
+
+
+i18n Translation support
+       Simple multi-language per-module internationalization support
+       
+       > See src/ffluci/i18n.lua for details
+       > See src/i18n/ for examples
+
+
+UCI wrapper support
+       Lua UCI-Wrapper adapting the CLI of the uci binary
+
+       > See src/model/uci.lua for details
+
+
+Menu Building support
+       Supports menu building for modules and exported actions
+
+       > See src/ffluci/menu.lua for details
+       > See src/ffluci/view/menu.htm, src/ffluci/controller for examples
+
+
+HTTP-Abstraction and Formvalue validation support
+       HTTP-Redirect, Statuscode, Content-Type abstraction
+       Dynamic formvalue validation support including varaible type and
+       value range validation
+
+       > See src/ffluci/http.lua for details
+       > See src/ffluci/controller/public/example-action.lua for examples
diff --git a/contrib/ffluci b/contrib/ffluci
new file mode 100755 (executable)
index 0000000..e090d56
--- /dev/null
@@ -0,0 +1,5 @@
+#!/usr/bin/haserl --shell=luac
+package.path  = "/usr/lib/lua/?.lua;/usr/lib/lua/?/init.lua;" .. package.path
+package.cpath = "/usr/lib/lua/?.so;" .. package.cpath
+require("ffluci").dispatch()
+
diff --git a/contrib/index.cgi b/contrib/index.cgi
new file mode 100755 (executable)
index 0000000..429b4c3
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/haserl --shell=luac
+print("Status: 302 Found")
+print("Location: /cgi-bin/ffluci\n")
diff --git a/contrib/package/ffluci/Makefile b/contrib/package/ffluci/Makefile
new file mode 100644 (file)
index 0000000..12e811d
--- /dev/null
@@ -0,0 +1,43 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=ffluci
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
+PKG_INSTALL_DIR:=$(PKG_BUILD_DIR)/ipkg-install
+
+MAKE_ACTION:=dist-source
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/ffluci
+  SECTION:=admin
+  CATEGORY:=Administration
+  TITLE:=FFLuCI
+  DEPENDS:=+liblua +luafilesystem +haserl
+  MAINTAINER:=Steven Barth <steven-at-midlink-dot-org>
+endef
+
+define Build/Prepare
+       mkdir -p $(PKG_BUILD_DIR)
+       $(CP) ./src/* $(PKG_BUILD_DIR)/ -R
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+       $(MAKE) -C $(PKG_BUILD_DIR) dist-source
+endef
+
+define Package/ffluci/install          
+       $(INSTALL_DIR) $(1)/usr/lib/lua
+       $(INSTALL_DIR) $(1)/www/cgi-bin
+       $(CP) $(PKG_BUILD_DIR)/dist/* $(1)/usr/lib/lua/ -R
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/contrib/ffluci $(1)/www/cgi-bin
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/contrib/index.cgi $(1)/www/cgi-bin
+       $(CP) -a ./ipkg/ffluci.postinst $(1)/CONTROL/postinst
+endef
+
+$(eval $(call BuildPackage,ffluci))
diff --git a/contrib/package/ffluci/ipkg/ffluci.postinst b/contrib/package/ffluci/ipkg/ffluci.postinst
new file mode 100755 (executable)
index 0000000..0dbac00
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+PATTERN='/cgi-bin/ffluci/admin:root:$p$root'
+grep $PATTERN ${IPKG_INSTROOT}/etc/httpd.conf >/dev/null 2>/dev/null || echo $PATTERN >> ${IPKG_INSTROOT}/etc/httpd.conf 
+[ -z ${IPKG_INSTROOT} ] && /etc/init.d/httpd restart
diff --git a/contrib/package/ffluci/src b/contrib/package/ffluci/src
new file mode 120000 (symlink)
index 0000000..a8a4f8c
--- /dev/null
@@ -0,0 +1 @@
+../../..
\ No newline at end of file
diff --git a/contrib/package/haserl-devel/Makefile b/contrib/package/haserl-devel/Makefile
new file mode 100644 (file)
index 0000000..3621c46
--- /dev/null
@@ -0,0 +1,39 @@
+# 
+# Copyright (C) 2006 OpenWrt.org
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+# $Id$
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=haserl
+PKG_VERSION:=0.9.22
+PKG_RELEASE:=1
+
+PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
+PKG_SOURCE_URL:=@SF/haserl
+PKG_MD5SUM:=b791150f2e5704d03b1fb698f4edc6c0
+
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/haserl
+  SECTION:=utils
+  CATEGORY:=Utilities
+  TITLE:=A CGI wrapper to embed shell scripts in HTML documents
+  URL:=http://haserl.sourceforge.net/
+  DEPENDS:=+liblua
+endef
+
+CONFIGURE_ARGS += \
+        --with-lua
+
+define Package/haserl/install
+       $(INSTALL_DIR) $(1)/usr/bin
+       $(STRIP) $(PKG_BUILD_DIR)/src/haserl
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/haserl $(1)/usr/bin/
+endef
+
+$(eval $(call BuildPackage,haserl))
diff --git a/contrib/package/luafilesystem/Makefile b/contrib/package/luafilesystem/Makefile
new file mode 100644 (file)
index 0000000..6c73cf5
--- /dev/null
@@ -0,0 +1,44 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=luafilesystem
+PKG_VERSION:=1.4.0
+PKG_RELEASE:=1
+
+PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
+PKG_SOURCE_URL:=http://luaforge.net/frs/download.php/3158
+PKG_MD5SUM:=6f3d247f27820b8f045431ad81bcd3ad
+
+PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
+PKG_INSTALL_DIR:=$(PKG_BUILD_DIR)/ipkg-install
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/luafilesystem
+  SECTION:=lib
+  CATEGORY:=Libraries
+  TITLE:=Lua FS library
+  URL:=http://www.keplerproject.org/luafilesystem/
+  DEPENDS:=+liblua
+  MAINTAINER:=Steven Barth <steven-at-midlink-dot-org>
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+       $(MAKE) -C $(PKG_BUILD_DIR) \
+               CC="$(TARGET_CROSS)gcc" \
+               LD="$(TARGET_CROSS)ld" \
+               AR="$(TARGET_CROSS)ar rcu" \
+               RANLIB="$(TARGET_CROSS)ranlib" \
+               INSTALL_ROOT=/usr \
+               LUA_INC=$(STAGING_DIR)/usr/include
+endef
+
+define Package/luafilesystem/install           
+       $(INSTALL_DIR) $(1)/usr/lib/lua
+       $(STRIP) $(PKG_BUILD_DIR)/src/lfs.so
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/lfs.so $(1)/usr/lib/lua
+endef
+
+$(eval $(call BuildPackage,luafilesystem))
diff --git a/src/ffluci/config.lua b/src/ffluci/config.lua
new file mode 100644 (file)
index 0000000..f63dc1f
--- /dev/null
@@ -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 (file)
index 0000000..9aec94c
--- /dev/null
@@ -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 (file)
index 0000000..538f5d9
--- /dev/null
@@ -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 (file)
index 0000000..61f4ad3
--- /dev/null
@@ -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 (file)
index 0000000..4498c77
--- /dev/null
@@ -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 (file)
index 0000000..f43d7f5
--- /dev/null
@@ -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 (file)
index 0000000..5a1cc6b
--- /dev/null
@@ -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 (file)
index 0000000..7aadf8b
--- /dev/null
@@ -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 (file)
index 0000000..2a18c27
--- /dev/null
@@ -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 (file)
index 0000000..db2bee0
--- /dev/null
@@ -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 (file)
index 0000000..4585f51
--- /dev/null
@@ -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 (file)
index 0000000..7b192aa
--- /dev/null
@@ -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 (file)
index 0000000..492367c
--- /dev/null
@@ -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 (file)
index 0000000..8c7f07f
--- /dev/null
@@ -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 (file)
index 0000000..07cbb80
--- /dev/null
@@ -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 (executable)
index 0000000..a0df536
--- /dev/null
@@ -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 (executable)
index 0000000..ffe1ccf
--- /dev/null
@@ -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 (file)
index 0000000..17c7245
--- /dev/null
@@ -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 (file)
index 0000000..f47388a
--- /dev/null
@@ -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 (file)
index 0000000..8231b61
--- /dev/null
@@ -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 (file)
index 0000000..8d5c597
--- /dev/null
@@ -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>