From: Jo-Philipp Wich Date: Sun, 2 Mar 2008 21:52:58 +0000 (+0000) Subject: * new project: ff-luci - Freifunk Lua Configuration Interface X-Git-Tag: 0.8.0~1222 X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fluci.git;a=commitdiff_plain;h=3f5de3273c9e103b4909802e339db06fe0b53312 * new project: ff-luci - Freifunk Lua Configuration Interface --- 3f5de3273c9e103b4909802e339db06fe0b53312 diff --git a/.buildpath b/.buildpath new file mode 100644 index 000000000..81fa6469e --- /dev/null +++ b/.buildpath @@ -0,0 +1,5 @@ + + + + + diff --git a/.project b/.project new file mode 100644 index 000000000..1b9dc67a9 --- /dev/null +++ b/.project @@ -0,0 +1,12 @@ + + + ffluci + + + + + + + org.eclipse.dltk.lua.core.nature + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..f49a4e16e --- /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 index 000000000..38b7981ec --- /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 index 000000000..05c4eb82b --- /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 index 000000000..e090d560c --- /dev/null +++ b/contrib/ffluci @@ -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 index 000000000..429b4c3b0 --- /dev/null +++ b/contrib/index.cgi @@ -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 index 000000000..12e811d2c --- /dev/null +++ b/contrib/package/ffluci/Makefile @@ -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 +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 index 000000000..0dbac00eb --- /dev/null +++ b/contrib/package/ffluci/ipkg/ffluci.postinst @@ -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 index 000000000..a8a4f8c21 --- /dev/null +++ b/contrib/package/ffluci/src @@ -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 index 000000000..3621c4691b --- /dev/null +++ b/contrib/package/haserl-devel/Makefile @@ -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 index 000000000..6c73cf501 --- /dev/null +++ b/contrib/package/luafilesystem/Makefile @@ -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 +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 index 000000000..f63dc1fad --- /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 + +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 000000000..9aec94c86 --- /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 000000000..538f5d9d0 --- /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 000000000..61f4ad32c --- /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 000000000..4498c77ed --- /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("

Hello World!

") + for k,v in pairs(request) do + print("
" .. k .. ": " .. v .. "
") + 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 000000000..f43d7f5a9 --- /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 + +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 000000000..5a1cc6b35 --- /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 + +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 000000000..7aadf8b33 --- /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 + +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 000000000..2a18c27d5 --- /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 + +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 000000000..db2bee0cf --- /dev/null +++ b/src/ffluci/i18n/example-simpleview.de @@ -0,0 +1,6 @@ +descr = [[Dies ist das Simple View-Beispiel.
+Dieses Template ist: ffluci/view/example-simpleview/index.htm und gehoert +zur Aktion "index".
+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 000000000..4585f51fb --- /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 + +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 000000000..7b192aaea --- /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 + +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 000000000..492367ce2 --- /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 + +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 000000000..8c7f07f94 --- /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 + +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 000000000..07cbb8000 --- /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 + +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 000000000..a0df536f1 --- /dev/null +++ b/src/ffluci/view/example-simpleview/foo.htm @@ -0,0 +1,3 @@ +<%+header%> +

bar

+<%+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 000000000..ffe1ccf71 --- /dev/null +++ b/src/ffluci/view/example-simpleview/index.htm @@ -0,0 +1,6 @@ +<%+header%> +

<%:descr This is the Simple View-Example.
+This template is ffluci/view/example-simpleview/index.htm and belongs +to the index-Action.%>

+

<%:lan The router's LAN IP-Address is:%> <%~network.lan.ipaddr%>

+<%+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 000000000..17c7245b6 --- /dev/null +++ b/src/ffluci/view/footer.htm @@ -0,0 +1,3 @@ + + + \ 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 000000000..f47388a42 --- /dev/null +++ b/src/ffluci/view/header.htm @@ -0,0 +1,9 @@ +<% require("ffluci.http").htmlheader() %> + + +FFLuCI Examples + + +

FFLuCI

+<%+menu%> +
\ 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 000000000..8231b61f9 --- /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 000000000..8d5c597cf --- /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") +%> +