webmcp

changeset 0:9fdfb27f8e67 v1.0.0

Version 1.0.0
author jbe/bsw
date Sun Oct 25 12:00:00 2009 +0100 (2009-10-25)
parents
children 985024b16520
files LICENSE Makefile Makefile.options demo-app/app/main/_filter/20_session.lua demo-app/app/main/_filter/21_auth.lua demo-app/app/main/_filter_action/23_write_priv.lua demo-app/app/main/_filter_view/30_topnav.lua demo-app/app/main/_layout/default.html demo-app/app/main/_layout/system_error.html demo-app/app/main/genre/_action/update.lua demo-app/app/main/genre/index.lua demo-app/app/main/genre/show.lua demo-app/app/main/index/_action/login.lua demo-app/app/main/index/_action/logout.lua demo-app/app/main/index/_action/set_lang.lua demo-app/app/main/index/index.lua demo-app/app/main/index/login.lua demo-app/app/main/media_type/_action/update.lua demo-app/app/main/media_type/index.lua demo-app/app/main/media_type/show.lua demo-app/app/main/medium/_action/update.lua demo-app/app/main/medium/index.lua demo-app/app/main/medium/show.lua demo-app/app/main/user/_action/update.lua demo-app/app/main/user/_filter/25_require_admin.lua demo-app/app/main/user/index.lua demo-app/app/main/user/show.lua demo-app/config/demo.lua demo-app/db/schema.sql demo-app/locale/translations.de.lua demo-app/locale/translations.en.lua demo-app/locale/translations.es.lua demo-app/model/classification.lua demo-app/model/genre.lua demo-app/model/media_type.lua demo-app/model/medium.lua demo-app/model/session.lua demo-app/model/tempstore.lua demo-app/model/track.lua demo-app/model/user.lua demo-app/static/lang/de.png demo-app/static/lang/en.png demo-app/static/lang/es.png demo-app/static/logo.png demo-app/static/style.css demo-app/static/trace.css demo-app/tmp/.keep doc/apache.sample.conf doc/autodoc-footer.htmlpart doc/autodoc-header.htmlpart doc/lighttpd.sample.conf framework/accelerator/Makefile framework/accelerator/webmcp_accelerator.c framework/bin/autodoc.lua framework/bin/langtool.lua framework/bin/recursive-luac framework/bin/webmcp_shell framework/cgi-bin/webmcp-wrapper.lua framework/cgi-bin/webmcp.lua framework/env/__init.lua framework/env/charset/__init.lua framework/env/charset/data/utf_8.lua framework/env/charset/get.lua framework/env/charset/get_data.lua framework/env/charset/set.lua framework/env/convert/DEPRECATED framework/env/convert/__init.lua framework/env/convert/_from_boolean_to_human.lua framework/env/convert/_from_date_to_human.lua framework/env/convert/_from_fraction_to_human.lua framework/env/convert/_from_human_to_boolean.lua framework/env/convert/_from_human_to_date.lua framework/env/convert/_from_human_to_fraction.lua framework/env/convert/_from_human_to_integer.lua framework/env/convert/_from_human_to_number.lua framework/env/convert/_from_human_to_string.lua framework/env/convert/_from_human_to_time.lua framework/env/convert/_from_human_to_timestamp.lua framework/env/convert/_from_integer_to_human.lua framework/env/convert/_from_number_to_human.lua framework/env/convert/_from_string_to_human.lua framework/env/convert/_from_time_to_human.lua framework/env/convert/_from_timestamp_to_human.lua framework/env/convert/from_human.lua framework/env/convert/register_type.lua framework/env/convert/to_human.lua framework/env/encode/action_file_path.lua framework/env/encode/concat_file_path.lua framework/env/encode/file_path.lua framework/env/encode/file_path_element.lua framework/env/encode/format_info.lua framework/env/encode/format_options.lua framework/env/encode/html.lua framework/env/encode/html_newlines.lua framework/env/encode/json.lua framework/env/encode/mime/atom_token.lua framework/env/encode/mime/base64.lua framework/env/encode/mime/mail.lua framework/env/encode/mime/mailbox_list_header_line.lua framework/env/encode/mime/quoted_printable_text_content.lua framework/env/encode/mime/unstructured_header_line.lua framework/env/encode/url.lua framework/env/encode/url_part.lua framework/env/encode/view_file_path.lua framework/env/execute/__init.lua framework/env/execute/_add_filters_by_path.lua framework/env/execute/action.lua framework/env/execute/config.lua framework/env/execute/file_path.lua framework/env/execute/filtered_action.lua framework/env/execute/filtered_view.lua framework/env/execute/inner.lua framework/env/execute/multi_wrapped.lua framework/env/execute/view.lua framework/env/execute/wrapped.lua framework/env/format/boolean.lua framework/env/format/currency.lua framework/env/format/date.lua framework/env/format/decimal.lua framework/env/format/percentage.lua framework/env/format/string.lua framework/env/format/time.lua framework/env/format/timestamp.lua framework/env/locale/__init.lua framework/env/locale/_get_translation_table.lua framework/env/locale/do_with.lua framework/env/locale/get.lua framework/env/locale/set.lua framework/env/net/send_mail.lua framework/env/param/__init.lua framework/env/param/_get_parser.lua framework/env/param/exchange.lua framework/env/param/get.lua framework/env/param/get_all_cgi.lua framework/env/param/get_id.lua framework/env/param/get_id_cgi.lua framework/env/param/get_list.lua framework/env/param/iterate.lua framework/env/param/restore.lua framework/env/param/update.lua framework/env/param/update_relationship.lua framework/env/parse/_pre_fold.lua framework/env/parse/boolean.lua framework/env/parse/currency.lua framework/env/parse/date.lua framework/env/parse/decimal.lua framework/env/parse/percentage.lua framework/env/parse/time.lua framework/env/parse/timestamp.lua framework/env/request/__init.lua framework/env/request/force_absolute_baseurl.lua framework/env/request/forward.lua framework/env/request/get_404_route.lua framework/env/request/get_absolute_baseurl.lua framework/env/request/get_action.lua framework/env/request/get_app_basepath.lua framework/env/request/get_app_name.lua framework/env/request/get_config_name.lua framework/env/request/get_csrf_secret.lua framework/env/request/get_module.lua framework/env/request/get_perm_params.lua framework/env/request/get_redirect_data.lua framework/env/request/get_relative_baseurl.lua framework/env/request/get_status.lua framework/env/request/get_view.lua framework/env/request/is_ajax.lua framework/env/request/is_post.lua framework/env/request/is_rerouted.lua framework/env/request/process_forward.lua framework/env/request/redirect.lua framework/env/request/set_404_route.lua framework/env/request/set_absolute_baseurl.lua framework/env/request/set_csrf_secret.lua framework/env/request/set_perm_param.lua framework/env/request/set_status.lua framework/env/slot/__init.lua framework/env/slot/dump_all.lua framework/env/slot/get_content.lua framework/env/slot/get_content_type.lua framework/env/slot/get_state_table.lua framework/env/slot/get_state_table_of.lua framework/env/slot/put.lua framework/env/slot/put_into.lua framework/env/slot/render_layout.lua framework/env/slot/reset.lua framework/env/slot/reset_all.lua framework/env/slot/restore_all.lua framework/env/slot/select.lua framework/env/slot/set_layout.lua framework/env/slot/use_temporary.lua framework/env/tempstore/pop.lua framework/env/tempstore/save.lua framework/env/trace/__init.lua framework/env/trace/_close_section.lua framework/env/trace/_new_entry.lua framework/env/trace/_open_section.lua framework/env/trace/_render_sub_tree.lua framework/env/trace/debug.lua framework/env/trace/enter_action.lua framework/env/trace/enter_config.lua framework/env/trace/enter_filter.lua framework/env/trace/enter_view.lua framework/env/trace/error.lua framework/env/trace/exectime.lua framework/env/trace/execution_return.lua framework/env/trace/forward.lua framework/env/trace/redirect.lua framework/env/trace/render.lua framework/env/trace/request.lua framework/env/trace/restore_slots.lua framework/env/trace/sql.lua framework/env/ui/autofield.lua framework/env/ui/container.lua framework/env/ui/create_unique_id.lua framework/env/ui/field/boolean.lua framework/env/ui/field/date.lua framework/env/ui/field/hidden.lua framework/env/ui/field/integer.lua framework/env/ui/field/password.lua framework/env/ui/field/select.lua framework/env/ui/field/text.lua framework/env/ui/form.lua framework/env/ui/form_element.lua framework/env/ui/heading.lua framework/env/ui/hidden_field.lua framework/env/ui/image.lua framework/env/ui/link.lua framework/env/ui/list.lua framework/env/ui/multiselect.lua framework/env/ui/paginate.lua framework/env/ui/script.lua framework/env/ui/submit.lua framework/env/ui/tag.lua framework/env/ui_deprecated/__init.lua framework/env/ui_deprecated/_prepare_redirect_params.lua framework/env/ui_deprecated/_stringify_table.lua framework/env/ui_deprecated/box.lua framework/env/ui_deprecated/calendar.lua framework/env/ui_deprecated/field.lua framework/env/ui_deprecated/form.lua framework/env/ui_deprecated/image.lua framework/env/ui_deprecated/input.lua framework/env/ui_deprecated/input_field/boolean.lua framework/env/ui_deprecated/input_field/date.lua framework/env/ui_deprecated/input_field/datetime.lua framework/env/ui_deprecated/input_field/number.lua framework/env/ui_deprecated/input_field/percentage.lua framework/env/ui_deprecated/input_field/reset.lua framework/env/ui_deprecated/input_field/text.lua framework/env/ui_deprecated/input_field/textarea.lua framework/env/ui_deprecated/input_field/time.lua framework/env/ui_deprecated/label.lua framework/env/ui_deprecated/link.lua framework/env/ui_deprecated/list.lua framework/env/ui_deprecated/multiselect.lua framework/env/ui_deprecated/select.lua framework/env/ui_deprecated/submit.lua framework/env/ui_deprecated/tag.lua framework/env/ui_deprecated/text.lua framework/lib/.keep libraries/atom/atom.lua libraries/atom/benchmark.sh libraries/extos/Makefile libraries/extos/extos.c libraries/luatex/luatex.lua libraries/mondelefant/Makefile libraries/mondelefant/example.lua libraries/mondelefant/mondelefant.lua libraries/mondelefant/mondelefant_atom_connector.lua libraries/mondelefant/mondelefant_native.c libraries/multirand/Makefile libraries/multirand/multirand.c libraries/nihil/nihil.lua libraries/rocketcgi/rocketcgi.lua
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/LICENSE	Sun Oct 25 12:00:00 2009 +0100
     1.3 @@ -0,0 +1,19 @@
     1.4 +Copyright (c) 2009 Public Software Group e. V., Berlin
     1.5 +
     1.6 +Permission is hereby granted, free of charge, to any person obtaining a
     1.7 +copy of this software and associated documentation files (the "Software"),
     1.8 +to deal in the Software without restriction, including without limitation
     1.9 +the rights to use, copy, modify, merge, publish, distribute, sublicense,
    1.10 +and/or sell copies of the Software, and to permit persons to whom the
    1.11 +Software is furnished to do so, subject to the following conditions:
    1.12 +
    1.13 +The above copyright notice and this permission notice shall be included in
    1.14 +all copies or substantial portions of the Software.
    1.15 +
    1.16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    1.17 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    1.18 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    1.19 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    1.20 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    1.21 +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    1.22 +DEALINGS IN THE SOFTWARE.
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/Makefile	Sun Oct 25 12:00:00 2009 +0100
     2.3 @@ -0,0 +1,53 @@
     2.4 +include Makefile.options
     2.5 +
     2.6 +all::
     2.7 +	make documentation
     2.8 +	make accelerator
     2.9 +	make libraries
    2.10 +	make symlinks
    2.11 +	make precompile
    2.12 +
    2.13 +documentation::
    2.14 +	rm -f doc/autodoc.tmp
    2.15 +	lua framework/bin/autodoc.lua framework/cgi-bin/ framework/env/ libraries/ > doc/autodoc.tmp
    2.16 +	cat doc/autodoc-header.htmlpart doc/autodoc.tmp doc/autodoc-footer.htmlpart > doc/autodoc.html
    2.17 +	rm -f doc/autodoc.tmp
    2.18 +
    2.19 +accelerator::
    2.20 +	cd framework/accelerator; make
    2.21 +
    2.22 +libraries::
    2.23 +	cd libraries/extos; make
    2.24 +	cd libraries/mondelefant; make
    2.25 +	cd libraries/multirand; make
    2.26 +
    2.27 +symlinks::
    2.28 +	ln -s -f ../../libraries/atom/atom.lua framework/lib/
    2.29 +	ln -s -f ../../libraries/extos/extos.so framework/lib/
    2.30 +	ln -s -f ../../libraries/mondelefant/mondelefant.lua framework/lib/
    2.31 +	ln -s -f ../../libraries/mondelefant/mondelefant_native.so framework/lib/
    2.32 +	ln -s -f ../../libraries/mondelefant/mondelefant_atom_connector.lua framework/lib/
    2.33 +	ln -s -f ../../libraries/multirand/multirand.so framework/lib/
    2.34 +	ln -s -f ../../libraries/rocketcgi/rocketcgi.lua framework/lib/
    2.35 +	ln -s -f ../../libraries/nihil/nihil.lua framework/lib/
    2.36 +	ln -s -f ../../libraries/luatex/luatex.lua framework/lib/
    2.37 +
    2.38 +precompile::
    2.39 +	rm -Rf framework.precompiled
    2.40 +	rm -Rf demo-app.precompiled
    2.41 +	sh framework/bin/recursive-luac framework/ framework.precompiled/
    2.42 +	rm -f framework.precompiled/accelerator/Makefile
    2.43 +	rm -f framework.precompiled/accelerator/webmcp_accelerator.c
    2.44 +	rm -f framework.precompiled/accelerator/webmcp_accelerator.o
    2.45 +	framework/bin/recursive-luac demo-app/ demo-app.precompiled/
    2.46 +
    2.47 +clean::
    2.48 +	rm -f doc/autodoc.tmp doc/autodoc.html
    2.49 +	rm -Rf framework.precompiled
    2.50 +	rm -Rf demo-app.precompiled
    2.51 +	rm -f demo-app/tmp/*
    2.52 +	rm -f framework/lib/*
    2.53 +	cd libraries/extos; make clean
    2.54 +	cd libraries/mondelefant; make clean
    2.55 +	cd libraries/multirand; make clean
    2.56 +	cd framework/accelerator; make clean
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/Makefile.options	Sun Oct 25 12:00:00 2009 +0100
     3.3 @@ -0,0 +1,20 @@
     3.4 +# C compiler command
     3.5 +CC = cc
     3.6 +
     3.7 +# linker command
     3.8 +LD = ld
     3.9 +
    3.10 +# filename extension for shared libraries
    3.11 +SLIB_EXT = so
    3.12 +
    3.13 +# C compiler flags
    3.14 +CFLAGS = -O2 -Wall -I /usr/include -I /usr/local/include -I /usr/local/include/lua51 -I /usr/include/lua5.1
    3.15 +
    3.16 +# additional C compiler flags for parts which depend on PostgreSQL
    3.17 +CFLAGS_PGSQL = -I /usr/include/postgresql -I /usr/include/postgresql/server -I /usr/local/include/postgresql -I /usr/local/include/postgresql/server
    3.18 +
    3.19 +# linker flags
    3.20 +LDFLAGS = -shared -L /usr/lib -L /usr/local/lib -L /usr/local/lib/lua51 -L /usr/lib/lua5.1
    3.21 +
    3.22 +# additional linker flags for parts which depend on PostgreSQL
    3.23 +LDFLAGS_PGSQL =
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/demo-app/app/main/_filter/20_session.lua	Sun Oct 25 12:00:00 2009 +0100
     4.3 @@ -0,0 +1,19 @@
     4.4 +if cgi.cookies.session then
     4.5 +  app.session = Session:by_ident(cgi.cookies.session)
     4.6 +end
     4.7 +if not app.session then
     4.8 +  app.session = Session:new()
     4.9 +  cgi.add_header('Set-Cookie: session=' .. app.session.ident .. '; path=/' )
    4.10 +end
    4.11 +
    4.12 +request.set_csrf_secret(app.session.csrf_secret)
    4.13 +
    4.14 +if app.session.user then
    4.15 +  locale.set{ lang = app.session.user.lang or "en" }
    4.16 +end
    4.17 +
    4.18 +if param.get("lang") then
    4.19 +  locale.set{ lang = param.get("lang") }
    4.20 +end
    4.21 +
    4.22 +execute.inner()
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/demo-app/app/main/_filter/21_auth.lua	Sun Oct 25 12:00:00 2009 +0100
     5.3 @@ -0,0 +1,25 @@
     5.4 +local auth_needed = not (
     5.5 +  request.get_module() == 'index'
     5.6 +  and (
     5.7 +    request.get_view() == 'login'
     5.8 +    or request.get_action() == 'login'
     5.9 +  )
    5.10 +)
    5.11 +
    5.12 +-- if not app.session.user_id then
    5.13 +--   trace.debug("DEBUG: AUTHENTICATION BYPASS ENABLED")
    5.14 +--   app.session.user_id = 1
    5.15 +-- end
    5.16 +
    5.17 +if app.session.user == nil and auth_needed then
    5.18 +  trace.debug("Not authenticated yet.")
    5.19 +  request.redirect{ module = 'index', view = 'login' }
    5.20 +else
    5.21 +  if auth_needed then
    5.22 +    trace.debug("Authentication accepted.")
    5.23 +  else
    5.24 +    trace.debug("No authentication needed.")
    5.25 +  end
    5.26 +  execute.inner()
    5.27 +  trace.debug("End of authentication filter.")
    5.28 +end
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/demo-app/app/main/_filter_action/23_write_priv.lua	Sun Oct 25 12:00:00 2009 +0100
     6.3 @@ -0,0 +1,7 @@
     6.4 +if 
     6.5 +  not (request.get_module() == "index" and request.get_action() == "login")
     6.6 +  and not (request.get_module() == "index" and request.get_action() == "logout")
     6.7 +then
     6.8 +  app.session.user:require_privilege("write")
     6.9 +end
    6.10 +execute.inner()
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/demo-app/app/main/_filter_view/30_topnav.lua	Sun Oct 25 12:00:00 2009 +0100
     7.3 @@ -0,0 +1,83 @@
     7.4 +-- display navigation only, if user is logged in
     7.5 +if app.session.user_id == nil then
     7.6 +  execute.inner()
     7.7 +  return
     7.8 +end
     7.9 +
    7.10 +slot.select("topnav", function()
    7.11 +  ui.link{
    7.12 +    attr = { class = "nav" },
    7.13 +    text = _"Home",
    7.14 +    module = "index",
    7.15 +    view = "index"
    7.16 +  }
    7.17 +  ui.link{
    7.18 +    attr = { class = "nav" },
    7.19 +    text = _"Media",
    7.20 +    module = "medium"
    7.21 +  }
    7.22 +  ui.link{
    7.23 +    attr = { class = "nav" },
    7.24 +    text = _"Media types",
    7.25 +    module = "media_type"
    7.26 +  }
    7.27 +  ui.link{
    7.28 +    attr = { class = "nav" },
    7.29 +    text = _"Genres",
    7.30 +    module = "genre"
    7.31 +  }
    7.32 +  if app.session.user.admin then
    7.33 +    ui.link{
    7.34 +    attr = { class = "nav" },
    7.35 +      text = _"Users",
    7.36 +      module = "user"
    7.37 +    }
    7.38 +  end
    7.39 +  ui.container{
    7.40 +    attr = { class = "nav lang_chooser" },
    7.41 +    content = function()
    7.42 +      for i, lang in ipairs{"en", "de", "es"} do
    7.43 +        ui.container{
    7.44 +          content = function()
    7.45 +            ui.link{
    7.46 +              content = function()
    7.47 +                ui.image{
    7.48 +                  static = "lang/" .. lang .. ".png",
    7.49 +                  attr = { alt = lang }
    7.50 +                }
    7.51 +                slot.put(lang)
    7.52 +              end,
    7.53 +              module = "index",
    7.54 +              action = "set_lang",
    7.55 +              params = { lang = lang },
    7.56 +              routing = {
    7.57 +                default = {
    7.58 +                  mode = "redirect",
    7.59 +                  module = request.get_module(),
    7.60 +                  view = request.get_view(),
    7.61 +                  id = param.get_id_cgi(),
    7.62 +                  params = param.get_all_cgi()
    7.63 +                }
    7.64 +              }
    7.65 +            }
    7.66 +          end
    7.67 +        }
    7.68 +      end
    7.69 +    end
    7.70 +  }
    7.71 +
    7.72 +  ui.link{
    7.73 +    attr = { class = "nav" },
    7.74 +    text = _"Logout",
    7.75 +    module = "index",
    7.76 +    action = "logout",
    7.77 +    redirect_to = {
    7.78 +      ok = {
    7.79 +        module = "index",
    7.80 +        view = "login"
    7.81 +      }
    7.82 +    }
    7.83 +  }
    7.84 +end)
    7.85 +
    7.86 +execute.inner()
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/demo-app/app/main/_layout/default.html	Sun Oct 25 12:00:00 2009 +0100
     8.3 @@ -0,0 +1,58 @@
     8.4 +<html>
     8.5 +  <head>
     8.6 +    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     8.7 +    <title>WebMCP Demo Application</title>
     8.8 +    <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/style.css" />
     8.9 +    <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/trace.css" />
    8.10 +  </head>
    8.11 +  <body>
    8.12 +    <div class="layout_logo">
    8.13 +      <div class="logo">
    8.14 +        <img src="__BASEURL__/static/logo.png"> Demo
    8.15 +      </div>
    8.16 +    </div>
    8.17 +    <div class="layout_topnav">
    8.18 +      <div class="topnav" id="topnav">
    8.19 +        <!-- WEBMCP SLOT topnav -->
    8.20 +      </div>
    8.21 +    </div>
    8.22 +    <br style="clear: left;">
    8.23 +    <div class="layout_sidenav">
    8.24 +      <div class="sidenav" id="sidenav">
    8.25 +        <!-- WEBMCP SLOT sidenav -->
    8.26 +      </div>
    8.27 +    </div>
    8.28 +    <div class="layout_content">
    8.29 +      <div class="layout_title">
    8.30 +        <!-- WEBMCP SLOT title -->
    8.31 +      </div>
    8.32 +      <br style="clear: left;">
    8.33 +      <div class="layout_actions">
    8.34 +        <!-- WEBMCP SLOT actions -->
    8.35 +      </div>
    8.36 +      <div class="layout_main">
    8.37 +        <!-- WEBMCP SLOT main -->
    8.38 +      </div>
    8.39 +      <div class="layout_trace" id="layout_trace" style="xdisplay: none">
    8.40 +	    <div id="trace_show" onclick="document.getElementById('trace_content').style.display='block';this.style.display='none';">TRACE</div>
    8.41 +        <div id="trace_content" style="display: none;">
    8.42 +          <!-- WEBMCP SLOT trace -->
    8.43 +	      <div class="trace_close" onclick="document.getElementById('trace_show').style.display='block';document.getElementById('trace_content').style.display='none';">
    8.44 +	        close
    8.45 +	      </div>
    8.46 +	    </div>
    8.47 +      </div>
    8.48 +    </div>
    8.49 +    <div class="layout_notice" id="layout_notice" onclick="document.getElementById('layout_notice').style.display='none';">
    8.50 +      <!-- WEBMCP SLOT notice -->
    8.51 +    </div>
    8.52 +    <div class="layout_warning" id="layout_warning" onclick="document.getElementById('layout_warning').style.display='none';">
    8.53 +      <!-- WEBMCP SLOT warning -->
    8.54 +    </div>
    8.55 +    <div class="layout_error" id="layout_error" onclick="document.getElementById('layout_error').style.display='none';">
    8.56 +      <!-- WEBMCP SLOT error -->
    8.57 +    </div>
    8.58 +  </body>
    8.59 +  <script>
    8.60 +  </script>
    8.61 +</html>
    8.62 \ No newline at end of file
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/demo-app/app/main/_layout/system_error.html	Sun Oct 25 12:00:00 2009 +0100
     9.3 @@ -0,0 +1,41 @@
     9.4 +<html>
     9.5 +  <head>
     9.6 +    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     9.7 +    <title>webmcp demo application</title>
     9.8 +    <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/style.css" />
     9.9 +    <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/trace.css" />
    9.10 +  </head>
    9.11 +  <body class="system_error">
    9.12 +    <div class="layout_content">
    9.13 +      <div class="layout_title">
    9.14 +        <div class="title">
    9.15 +          <br />
    9.16 +          <br />
    9.17 +          System message
    9.18 +        </div>
    9.19 +      </div>
    9.20 +      <br style="clear: left;">
    9.21 +      <div class="layout_actions">
    9.22 +        &nbsp;
    9.23 +      </div>
    9.24 +      <div class="layout_main">
    9.25 +        <div class="main">
    9.26 +          <tt><!-- WEBMCP SLOT system_error --></tt>
    9.27 +          <br />
    9.28 +          <br />
    9.29 +          <br />
    9.30 +          <br />
    9.31 +          <button onclick="window.location.reload()">Retry request</button>
    9.32 +          <a href="__BASEURL__">index</a>
    9.33 +        </div>
    9.34 +      </div>
    9.35 +    </div>
    9.36 +      <div class="layout_trace" id="layout_trace" style="xdisplay: none">
    9.37 +	    <div id="trace_show" onclick="document.getElementById('trace_content').style.display='block';this.style.display='none';" style="display: none;">TRACE</div>
    9.38 +        <div id="trace_content">
    9.39 +	        <!-- WEBMCP SLOT trace -->
    9.40 +	        <div class="trace_close" onclick="document.getElementById('trace_show').style.display='block';document.getElementById('trace_content').style.display='none';">close</div>
    9.41 +	    </div>
    9.42 +      </div>
    9.43 +  </body>
    9.44 +</html>
    9.45 \ No newline at end of file
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/demo-app/app/main/genre/_action/update.lua	Sun Oct 25 12:00:00 2009 +0100
    10.3 @@ -0,0 +1,24 @@
    10.4 +local genre
    10.5 +local id = param.get_id()
    10.6 +if id then
    10.7 +  genre = Genre:by_id(id)
    10.8 +else
    10.9 +  genre = Genre:new()
   10.10 +end
   10.11 +
   10.12 +if param.get("delete", atom.boolean) then
   10.13 +  local name = genre.name
   10.14 +  genre:destroy()
   10.15 +  slot.put_into("notice", _("Genre '#{name}' deleted", {name = name}))
   10.16 +  return
   10.17 +end
   10.18 +
   10.19 +param.update(genre, "name", "description")
   10.20 +
   10.21 +genre:save()
   10.22 +
   10.23 +if id then
   10.24 +  slot.put_into("notice", _("Genre '#{name}' updated", {name = genre.name}))
   10.25 +else
   10.26 +  slot.put_into("notice", _("Genre '#{name}' created", {name = genre.name}))
   10.27 +end
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/demo-app/app/main/genre/index.lua	Sun Oct 25 12:00:00 2009 +0100
    11.3 @@ -0,0 +1,46 @@
    11.4 +slot.put_into("title", encode.html(_"Genres"))
    11.5 +
    11.6 +slot.select("actions", function()
    11.7 +  if app.session.user.write_priv then
    11.8 +    ui.link{
    11.9 +      content = _"Create new genre",
   11.10 +      module = "genre",
   11.11 +      view = "show"
   11.12 +    }
   11.13 +  end
   11.14 +end)
   11.15 +
   11.16 +
   11.17 +local selector = Genre:new_selector():add_order_by('"name", "id"')
   11.18 +
   11.19 +slot.select("main", function()
   11.20 +  ui.paginate{
   11.21 +    selector = selector,
   11.22 +    content = function()
   11.23 +      ui.list{
   11.24 +        records = selector:exec(),
   11.25 +        columns = {
   11.26 +          {
   11.27 +            field_attr = { style = "float: right;" },
   11.28 +            label = _"Id",
   11.29 +            name = "id"
   11.30 +          },
   11.31 +          {
   11.32 +            label = _"Name",
   11.33 +            name = "name"
   11.34 +          },
   11.35 +          {
   11.36 +            content = function(record)
   11.37 +              ui.link{
   11.38 +                content = _"Show",
   11.39 +                module  = "genre",
   11.40 +                view    = "show",
   11.41 +                id      = record.id
   11.42 +              }
   11.43 +            end
   11.44 +          },
   11.45 +        }
   11.46 +      }
   11.47 +    end
   11.48 +  }
   11.49 +end)
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/demo-app/app/main/genre/show.lua	Sun Oct 25 12:00:00 2009 +0100
    12.3 @@ -0,0 +1,63 @@
    12.4 +local genre
    12.5 +local id = param.get_id()
    12.6 +if id then
    12.7 +  genre = Genre:by_id(id)
    12.8 +end
    12.9 +
   12.10 +if genre then
   12.11 +  slot.put_into("title", encode.html(_"Genre"))
   12.12 +else
   12.13 +  slot.put_into("title", encode.html(_"New genre"))
   12.14 +end
   12.15 +
   12.16 +slot.select("actions", function()
   12.17 +  ui.link{
   12.18 +    content = _"Back",
   12.19 +    module = "genre"
   12.20 +  }
   12.21 +  if genre and app.session.user.write_priv then
   12.22 +    ui.link{
   12.23 +      content = _"Delete",
   12.24 +      form_attr = {
   12.25 +        onsubmit = "return confirm('" .. _'Are you sure?' .. "');"
   12.26 +      },
   12.27 +      module  = "genre",
   12.28 +      action  = "update",
   12.29 +      id      = genre.id,
   12.30 +      params = { delete = true },
   12.31 +      routing = {
   12.32 +        default = {
   12.33 +          mode = "redirect",
   12.34 +          module = "genre",
   12.35 +          view = "index"
   12.36 +        }
   12.37 +      }
   12.38 +    }
   12.39 +  end
   12.40 +end)
   12.41 +
   12.42 +slot.select("main", function()
   12.43 +  ui.form{
   12.44 +    attr = { class = "vertical" },
   12.45 +    record = genre,
   12.46 +    readonly = not app.session.user.write_priv,
   12.47 +    module = "genre",
   12.48 +    action = "update",
   12.49 +    id = id,
   12.50 +    routing = {
   12.51 +      default = {
   12.52 +        mode = "redirect",
   12.53 +        module = "genre",
   12.54 +        view = "index"
   12.55 +      }
   12.56 +    },
   12.57 +    content = function()
   12.58 +      if id then
   12.59 +        ui.field.integer{ label = _"Id", name = "id", readonly = true }
   12.60 +      end
   12.61 +      ui.field.text{    label = _"Name",        name = "name"                          }
   12.62 +      ui.field.text{    label = _"Description", name = "description", multiline = true }
   12.63 +      ui.submit{ text = _"Save" }
   12.64 +    end
   12.65 +  }
   12.66 +end)
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/demo-app/app/main/index/_action/login.lua	Sun Oct 25 12:00:00 2009 +0100
    13.3 @@ -0,0 +1,12 @@
    13.4 +local user = User:by_ident_and_password(param.get('ident'), param.get('password'))
    13.5 +
    13.6 +if user then
    13.7 +  app.session.user = user
    13.8 +  app.session:save()
    13.9 +  slot.put_into('notice', _'Login successful!')
   13.10 +  trace.debug('User authenticated')
   13.11 +else
   13.12 +  slot.put_into('error', _'Invalid username or password!')
   13.13 +  trace.debug('User NOT authenticated')
   13.14 +  return false
   13.15 +end
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/demo-app/app/main/index/_action/logout.lua	Sun Oct 25 12:00:00 2009 +0100
    14.3 @@ -0,0 +1,4 @@
    14.4 +if app.session then
    14.5 +  app.session:destroy()
    14.6 +  slot.put_into("notice", _"Logout successful")
    14.7 +end
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/demo-app/app/main/index/_action/set_lang.lua	Sun Oct 25 12:00:00 2009 +0100
    15.3 @@ -0,0 +1,6 @@
    15.4 +app.session.user.lang = param.get("lang")
    15.5 +app.session.user:save()
    15.6 +
    15.7 +locale.set{ lang = app.session.user.lang }
    15.8 +
    15.9 +slot.put_into("notice", _"Language changed")
   15.10 \ No newline at end of file
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/demo-app/app/main/index/index.lua	Sun Oct 25 12:00:00 2009 +0100
    16.3 @@ -0,0 +1,3 @@
    16.4 +slot.put_into('title', encode.html(_"webmcp demo application"))
    16.5 +
    16.6 +slot.put_into('main', encode.html(_"Welcome to webmcp demo application"))
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/demo-app/app/main/index/login.lua	Sun Oct 25 12:00:00 2009 +0100
    17.3 @@ -0,0 +1,47 @@
    17.4 +slot.put_into("title", encode.html(_"Password login"))
    17.5 +
    17.6 +slot.select("main", function()
    17.7 +
    17.8 +  ui.form{
    17.9 +    attr = { class = "vertical" },
   17.10 +    module = "index",
   17.11 +    action = "login", 
   17.12 +    routing = { 
   17.13 +      default = {
   17.14 +        mode = "redirect",
   17.15 +        module = "index",
   17.16 +        view = "index"
   17.17 +      }
   17.18 +    },
   17.19 +    content = function()
   17.20 +
   17.21 +      ui.container{
   17.22 +        attr = { class = "lang_chooser" },
   17.23 +        content = function()
   17.24 +          for i, lang in ipairs{"en", "de", "es"} do
   17.25 +            ui.container{
   17.26 +              content = function()
   17.27 +                ui.link{
   17.28 +                  content = function()
   17.29 +                    ui.image{
   17.30 +                      static = "lang/" .. lang .. ".png",
   17.31 +                      attr = { alt = lang }
   17.32 +                    }
   17.33 +                    slot.put(lang)
   17.34 +                  end,
   17.35 +                  module = "index",
   17.36 +                  view = "login",
   17.37 +                  params = { lang = lang }
   17.38 +                }
   17.39 +              end
   17.40 +            }
   17.41 +          end
   17.42 +        end
   17.43 +      }
   17.44 +
   17.45 +      ui.field.text{ label = _"Username", name = "ident" }
   17.46 +      ui.field.text{ label = _"Password", name = "password" }
   17.47 +      ui.submit{ text = _"Login" }
   17.48 +    end
   17.49 +  }
   17.50 +end)
   17.51 \ No newline at end of file
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/demo-app/app/main/media_type/_action/update.lua	Sun Oct 25 12:00:00 2009 +0100
    18.3 @@ -0,0 +1,24 @@
    18.4 +local media_type
    18.5 +local id = param.get_id()
    18.6 +if id then
    18.7 +  media_type = MediaType:by_id(id)
    18.8 +else
    18.9 +  media_type = MediaType:new()
   18.10 +end
   18.11 +
   18.12 +if param.get("delete", atom.boolean) then
   18.13 +  local name = media_type.name
   18.14 +  media_type:destroy()
   18.15 +  slot.put_into("notice", _("Media type '#{name}' deleted", {name = name}))
   18.16 +  return
   18.17 +end
   18.18 +
   18.19 +param.update(media_type, "name", "description")
   18.20 +
   18.21 +media_type:save()
   18.22 +
   18.23 +if id then
   18.24 +  slot.put_into("notice", _("Media type '#{name}' updated", {name = media_type.name}))
   18.25 +else
   18.26 +  slot.put_into("notice", _("Media type '#{name}' created", {name = media_type.name}))
   18.27 +end
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/demo-app/app/main/media_type/index.lua	Sun Oct 25 12:00:00 2009 +0100
    19.3 @@ -0,0 +1,46 @@
    19.4 +slot.put_into("title", encode.html(_"Media types"))
    19.5 +
    19.6 +slot.select("actions", function()
    19.7 +  if app.session.user.write_priv then
    19.8 +    ui.link{
    19.9 +      content = _"Create new media type",
   19.10 +      module = "media_type",
   19.11 +      view = "show"
   19.12 +    }
   19.13 +  end
   19.14 +end)
   19.15 +
   19.16 +
   19.17 +local selector = MediaType:new_selector():add_order_by('"name", "id"')
   19.18 +
   19.19 +slot.select("main", function()
   19.20 +  ui.paginate{
   19.21 +    selector = selector,
   19.22 +    content = function()
   19.23 +      ui.list{
   19.24 +        records = selector:exec(),
   19.25 +        columns = {
   19.26 +          {
   19.27 +            field_attr = { style = "float: right;" },
   19.28 +            label = _"Id",
   19.29 +            name = "id"
   19.30 +          },
   19.31 +          {
   19.32 +            label = _"Name",
   19.33 +            name = "name"
   19.34 +          },
   19.35 +          {
   19.36 +            content = function(record)
   19.37 +              ui.link{
   19.38 +                content = _"Show",
   19.39 +                module  = "media_type",
   19.40 +                view    = "show",
   19.41 +                id      = record.id
   19.42 +              }
   19.43 +            end
   19.44 +          },
   19.45 +        }
   19.46 +      }
   19.47 +    end
   19.48 +  }
   19.49 +end)
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/demo-app/app/main/media_type/show.lua	Sun Oct 25 12:00:00 2009 +0100
    20.3 @@ -0,0 +1,63 @@
    20.4 +local media_type
    20.5 +local id = param.get_id()
    20.6 +if id then
    20.7 +  media_type = MediaType:by_id(id)
    20.8 +end
    20.9 +
   20.10 +if media_type then
   20.11 +  slot.put_into("title", encode.html(_"Media type"))
   20.12 +else
   20.13 +  slot.put_into("title", encode.html(_"New media type"))
   20.14 +end
   20.15 +
   20.16 +slot.select("actions", function()
   20.17 +  ui.link{
   20.18 +    content = _"Back",
   20.19 +    module = "media_type"
   20.20 +  }
   20.21 +  if media_type and app.session.user.write_priv then
   20.22 +    ui.link{
   20.23 +      content = _"Delete",
   20.24 +      form_attr = {
   20.25 +        onsubmit = "return confirm('" .. _'Are you sure?' .. "');"
   20.26 +      },
   20.27 +      module  = "media_type",
   20.28 +      action  = "update",
   20.29 +      id      = media_type.id,
   20.30 +      params = { delete = true },
   20.31 +      routing = {
   20.32 +        default = {
   20.33 +          mode = "redirect",
   20.34 +          module = "media_type",
   20.35 +          view = "index"
   20.36 +        }
   20.37 +      }
   20.38 +    }
   20.39 +  end
   20.40 +end)
   20.41 +
   20.42 +slot.select("main", function()
   20.43 +  ui.form{
   20.44 +    attr = { class = "vertical" },
   20.45 +    record = media_type,
   20.46 +    readonly = not app.session.user.write_priv,
   20.47 +    module = "media_type",
   20.48 +    action = "update",
   20.49 +    id = id,
   20.50 +    routing = {
   20.51 +      default = {
   20.52 +        mode = "redirect",
   20.53 +        module = "media_type",
   20.54 +        view = "index"
   20.55 +      }
   20.56 +    },
   20.57 +    content = function()
   20.58 +      if id then
   20.59 +        ui.field.integer{ label = _"Id", name = "id", readonly = true }
   20.60 +      end
   20.61 +      ui.field.text{    label = _"Name",        name = "name"                          }
   20.62 +      ui.field.text{    label = _"Description", name = "description", multiline = true }
   20.63 +      ui.submit{ text = _"Save" }
   20.64 +    end
   20.65 +  }
   20.66 +end)
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/demo-app/app/main/medium/_action/update.lua	Sun Oct 25 12:00:00 2009 +0100
    21.3 @@ -0,0 +1,51 @@
    21.4 +local medium
    21.5 +local id = param.get_id()
    21.6 +if id then
    21.7 +  medium = Medium:by_id(id)
    21.8 +else
    21.9 +  medium = Medium:new()
   21.10 +end
   21.11 +
   21.12 +if param.get("delete", atom.boolean) then
   21.13 +  local name = medium.name
   21.14 +  medium:destroy()
   21.15 +  slot.put_into("notice", _("Medium '#{name}' deleted", {name = name}))
   21.16 +  return
   21.17 +end
   21.18 +
   21.19 +param.update(medium, "media_type_id", "name", "copyprotected")
   21.20 +
   21.21 +medium:save()
   21.22 +
   21.23 +param.update_relationship{
   21.24 +  param_name        = "genres",
   21.25 +  id                = medium.id,
   21.26 +  connecting_model  = Classification,
   21.27 +  own_reference     = "medium_id",
   21.28 +  foreign_reference = "genre_id"
   21.29 +}
   21.30 +
   21.31 +for index, prefix in param.iterate("tracks") do
   21.32 +  local id = param.get(prefix .. "id", atom.integer)
   21.33 +  local track
   21.34 +  if id then
   21.35 +    track = Track:by_id(id)
   21.36 +  elseif #param.get(prefix .. "name") > 0 then
   21.37 +    track = Track:new()
   21.38 +    track.medium_id = medium.id
   21.39 +  else
   21.40 +    break
   21.41 +  end
   21.42 +  track.position    = param.get(prefix .. "position", atom.integer)
   21.43 +  track.name        = param.get(prefix .. "name")
   21.44 +  track.description = param.get(prefix .. "description")
   21.45 +  track.duration    = param.get(prefix .. "duration")
   21.46 +  track:save()
   21.47 +end
   21.48 +
   21.49 +
   21.50 +if id then
   21.51 +  slot.put_into("notice", _("Medium '#{name}' updated", {name = medium.name}))
   21.52 +else
   21.53 +  slot.put_into("notice", _("Medium '#{name}' created", {name = medium.name}))
   21.54 +end
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/demo-app/app/main/medium/index.lua	Sun Oct 25 12:00:00 2009 +0100
    22.3 @@ -0,0 +1,50 @@
    22.4 +slot.put_into("title", encode.html(_"Media"))
    22.5 +
    22.6 +slot.select("actions", function()
    22.7 +  if app.session.user.write_priv then
    22.8 +    ui.link{
    22.9 +      content = _"Create new medium",
   22.10 +      module = "medium",
   22.11 +      view = "show"
   22.12 +    }
   22.13 +  end
   22.14 +end)
   22.15 +
   22.16 +
   22.17 +local selector = Medium:new_selector():add_order_by('"name", "id"')
   22.18 +
   22.19 +slot.select("main", function()
   22.20 +  ui.paginate{
   22.21 +    selector = selector,
   22.22 +    content = function()
   22.23 +      ui.list{
   22.24 +        records = selector:exec(),
   22.25 +        columns = {
   22.26 +          {
   22.27 +            field_attr = { style = "float: right;" },
   22.28 +            label = _"Id",
   22.29 +            name = "id"
   22.30 +          },
   22.31 +          {
   22.32 +            label = _"Name",
   22.33 +            name = "name"
   22.34 +          },
   22.35 +          {
   22.36 +            label = _"Copy protected",
   22.37 +            name = "copyprotected"
   22.38 +          },
   22.39 +          {
   22.40 +            content = function(record)
   22.41 +              ui.link{
   22.42 +                content = _"Show",
   22.43 +                module  = "medium",
   22.44 +                view    = "show",
   22.45 +                id      = record.id
   22.46 +              }
   22.47 +            end
   22.48 +          },
   22.49 +        }
   22.50 +      }
   22.51 +    end
   22.52 +  }
   22.53 +end)
   22.54 \ No newline at end of file
    23.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.2 +++ b/demo-app/app/main/medium/show.lua	Sun Oct 25 12:00:00 2009 +0100
    23.3 @@ -0,0 +1,117 @@
    23.4 +local medium
    23.5 +local id = param.get_id()
    23.6 +if id then
    23.7 +  medium = Medium:by_id(id)
    23.8 +end
    23.9 +
   23.10 +if medium then
   23.11 +  slot.put_into("title", encode.html(_"Medium"))
   23.12 +else
   23.13 +  slot.put_into("title", encode.html(_"New medium"))
   23.14 +end
   23.15 +
   23.16 +slot.select("actions", function()
   23.17 +  ui.link{
   23.18 +    content = _"Back",
   23.19 +    module = "medium"
   23.20 +  }
   23.21 +  if medium and app.session.user.write_priv then
   23.22 +    ui.link{
   23.23 +      content = _"Delete",
   23.24 +      form_attr = {
   23.25 +        onsubmit = "return confirm(" .. encode.json(_'Are you sure?') .. ");"
   23.26 +      },
   23.27 +      module  = "medium",
   23.28 +      action  = "update",
   23.29 +      id      = medium.id,
   23.30 +      params = { delete = true },
   23.31 +      routing = {
   23.32 +        default = {
   23.33 +          mode = "redirect",
   23.34 +          module = "medium",
   23.35 +          view = "index"
   23.36 +        }
   23.37 +      }
   23.38 +    }
   23.39 +  end
   23.40 +end)
   23.41 +
   23.42 +slot.select("main", function()
   23.43 +  ui.form{
   23.44 +    attr = { class = "vertical" },
   23.45 +    record = medium,
   23.46 +    readonly = not app.session.user.write_priv,
   23.47 +    module = "medium",
   23.48 +    action = "update",
   23.49 +    id = id,
   23.50 +    routing = {
   23.51 +      default = {
   23.52 +        mode = "redirect",
   23.53 +        module = "medium",
   23.54 +        view = "index"
   23.55 +      }
   23.56 +    },
   23.57 +    content = function()
   23.58 +      if id then
   23.59 +        ui.field.integer{ label = _"Id", name = "id", readonly = true }
   23.60 +      end
   23.61 +      ui.field.select{
   23.62 +        label = _"Media type",
   23.63 +        name  = "media_type_id",
   23.64 +        foreign_records = MediaType:new_selector():exec(),
   23.65 +        foreign_id = "id",
   23.66 +        foreign_name = "name"
   23.67 +      }
   23.68 +      ui.field.text{    label = _"Name",           name = "name"           }
   23.69 +      ui.field.boolean{ label = _"Copy protected", name = "copyprotected"  }
   23.70 +
   23.71 +      ui.multiselect{
   23.72 +        name               = "genres[]",
   23.73 +        label              = _"Genres",
   23.74 +        style              = "select",
   23.75 +        attr = { size = 5 },
   23.76 +        foreign_records    = Genre:new_selector():exec(),
   23.77 +        connecting_records = medium and medium.classifications or {},
   23.78 +        own_id             = "id",
   23.79 +        own_reference      = "medium_id",
   23.80 +        foreign_reference  = "genre_id",
   23.81 +        foreign_id         = "id",
   23.82 +        foreign_name       = "name",
   23.83 +      }
   23.84 +      local tracks = medium and medium.tracks or {}
   23.85 +      for i = 1, 5 do
   23.86 +        tracks[#tracks+1] = Track:new()
   23.87 +      end
   23.88 +      ui.list{
   23.89 +        label = _"Tracks",
   23.90 +        prefix = "tracks",
   23.91 +        records = tracks,
   23.92 +        columns = {
   23.93 +          {
   23.94 +            label = _"Pos",
   23.95 +            name = "position",
   23.96 +          },
   23.97 +          {
   23.98 +            label = _"Name",
   23.99 +            name = "name",
  23.100 +          },
  23.101 +          {
  23.102 +            label = _"Description",
  23.103 +            name = "description",
  23.104 +          },
  23.105 +          {
  23.106 +            label = _"Duration",
  23.107 +            name = "duration",
  23.108 +          },
  23.109 +          {
  23.110 +            content = function()
  23.111 +              ui.field.hidden{ name = "id" }
  23.112 +            end
  23.113 +          }
  23.114 +        }
  23.115 +      }
  23.116 +
  23.117 +      ui.submit{ text = _"Save" }
  23.118 +    end
  23.119 +  }
  23.120 +end)
    24.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.2 +++ b/demo-app/app/main/user/_action/update.lua	Sun Oct 25 12:00:00 2009 +0100
    24.3 @@ -0,0 +1,24 @@
    24.4 +local user
    24.5 +local id = param.get_id()
    24.6 +if id then
    24.7 +  user = User:by_id(id)
    24.8 +else
    24.9 +  user = User:new()
   24.10 +end
   24.11 +
   24.12 +if param.get("delete", atom.boolean) then
   24.13 +  local name = user.name
   24.14 +  user:destroy()
   24.15 +  slot.put_into("notice", _("User '#{name}' deleted", {name = name}))
   24.16 +  return
   24.17 +end
   24.18 +
   24.19 +param.update(user, "ident", "password", "name", "write_priv", "admin")
   24.20 +
   24.21 +user:save()
   24.22 +
   24.23 +if id then
   24.24 +  slot.put_into("notice", _("User '#{name}' updated", {name = user.name}))
   24.25 +else
   24.26 +  slot.put_into("notice", _("User '#{name}' created", {name = user.name}))
   24.27 +end
    25.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.2 +++ b/demo-app/app/main/user/_filter/25_require_admin.lua	Sun Oct 25 12:00:00 2009 +0100
    25.3 @@ -0,0 +1,3 @@
    25.4 +app.session.user:require_privilege("admin")
    25.5 +
    25.6 +execute.inner()
    26.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.2 +++ b/demo-app/app/main/user/index.lua	Sun Oct 25 12:00:00 2009 +0100
    26.3 @@ -0,0 +1,56 @@
    26.4 +slot.put_into("title", encode.html(_"Users"))
    26.5 +
    26.6 +slot.select("actions", function()
    26.7 +  ui.link{
    26.8 +    content = _"Create new user",
    26.9 +    module = "user",
   26.10 +    view = "show"
   26.11 +  }
   26.12 +end)
   26.13 +
   26.14 +
   26.15 +local selector = User:new_selector():add_order_by('"ident", "id"')
   26.16 +
   26.17 +slot.select("main", function()
   26.18 +  ui.paginate{
   26.19 +    selector = selector,
   26.20 +    content = function()
   26.21 +      ui.list{
   26.22 +        records = selector:exec(),
   26.23 +        columns = {
   26.24 +          {
   26.25 +            field_attr = { style = "float: right;" },
   26.26 +            label = _"Id",
   26.27 +            name = "id"
   26.28 +          },
   26.29 +          {
   26.30 +            label = _"Ident",
   26.31 +            name = "ident"
   26.32 +          },
   26.33 +          {
   26.34 +            label = _"Name",
   26.35 +            name = "name"
   26.36 +          },
   26.37 +          {
   26.38 +            label = _"w",
   26.39 +            name = "write_priv"
   26.40 +          },
   26.41 +          {
   26.42 +            label = _"Admin",
   26.43 +            name = "admin"
   26.44 +          },
   26.45 +          {
   26.46 +            content = function(record)
   26.47 +              ui.link{
   26.48 +                content = _"Show",
   26.49 +                module  = "user",
   26.50 +                view    = "show",
   26.51 +                id      = record.id
   26.52 +              }
   26.53 +            end
   26.54 +          },
   26.55 +        }
   26.56 +      }
   26.57 +    end
   26.58 +  }
   26.59 +end)
   26.60 \ No newline at end of file
    27.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    27.2 +++ b/demo-app/app/main/user/show.lua	Sun Oct 25 12:00:00 2009 +0100
    27.3 @@ -0,0 +1,65 @@
    27.4 +local user
    27.5 +local id = param.get_id()
    27.6 +if id then
    27.7 +  user = User:by_id(id)
    27.8 +end
    27.9 +
   27.10 +if user then
   27.11 +  slot.put_into("title", encode.html(_"User"))
   27.12 +else
   27.13 +  slot.put_into("title", encode.html(_"New user"))
   27.14 +end
   27.15 +
   27.16 +slot.select("actions", function()
   27.17 +  ui.link{
   27.18 +    content = _"Back",
   27.19 +    module = "user"
   27.20 +  }
   27.21 +  if user then
   27.22 +    ui.link{
   27.23 +      content = _"Delete",
   27.24 +      form_attr = {
   27.25 +        onsubmit = "return confirm('" .. _'Are you sure?' .. "');"
   27.26 +      },
   27.27 +      module  = "user",
   27.28 +      action  = "update",
   27.29 +      id      = user.id,
   27.30 +      params = { delete = true },
   27.31 +      routing = {
   27.32 +        default = {
   27.33 +          mode = "redirect",
   27.34 +          module = "user",
   27.35 +          view = "index"
   27.36 +        }
   27.37 +      }
   27.38 +    }
   27.39 +  end
   27.40 +end)
   27.41 +
   27.42 +slot.select("main", function()
   27.43 +  ui.form{
   27.44 +    attr = { class = "vertical" },
   27.45 +    record = user,
   27.46 +    module = "user",
   27.47 +    action = "update",
   27.48 +    id = id,
   27.49 +    routing = {
   27.50 +      default = {
   27.51 +        mode = "redirect",
   27.52 +        module = "user",
   27.53 +        view = "index"
   27.54 +      }
   27.55 +    },
   27.56 +    content = function()
   27.57 +      if id then
   27.58 +        ui.field.integer{ label = _"Id", name = "id", readonly = true }
   27.59 +      end
   27.60 +      ui.field.text{    label = _"Ident",      name = "ident"      }
   27.61 +      ui.field.text{    label = _"Password",   name = "password"   }
   27.62 +      ui.field.text{    label = _"Name",       name = "name"       }
   27.63 +      ui.field.boolean{ label = _"Write Priv", name = "write_priv" }
   27.64 +      ui.field.boolean{ label = _"Admin",      name = "admin"      }
   27.65 +      ui.submit{ text = _"Save" }
   27.66 +    end
   27.67 +  }
   27.68 +end)
    28.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    28.2 +++ b/demo-app/config/demo.lua	Sun Oct 25 12:00:00 2009 +0100
    28.3 @@ -0,0 +1,47 @@
    28.4 +-- uncomment the following two lines to use C implementations of chosen
    28.5 +-- functions and to disable garbage collection during the request, to
    28.6 +-- increase speed:
    28.7 +--
    28.8 +-- require 'webmcp_accelerator'
    28.9 +-- collectgarbage("stop")
   28.10 +
   28.11 +-- open and set default database handle
   28.12 +db = assert(mondelefant.connect{
   28.13 +  engine='postgresql',
   28.14 +  dbname='webmcp_demo'
   28.15 +})
   28.16 +at_exit(function() 
   28.17 +  db:close()
   28.18 +end)
   28.19 +function mondelefant.class_prototype:get_db_conn() return db end
   28.20 +
   28.21 +-- enable output of SQL commands in trace system
   28.22 +function db:sql_tracer(command)
   28.23 +  return function(error_info)
   28.24 +    local error_info = error_info or {}
   28.25 +    trace.sql{ command = command, error_position = error_info.position }
   28.26 +  end
   28.27 +end
   28.28 +
   28.29 +-- 'request.get_relative_baseurl()' should be replaced by the absolute
   28.30 +-- base URL of the application, as otherwise HTTP redirects will not be
   28.31 +-- standard compliant
   28.32 +request.set_absolute_baseurl(request.get_relative_baseurl())
   28.33 +
   28.34 +-- uncomment the following lines, if you want to use a database driven
   28.35 +-- tempstore (for flash messages):
   28.36 +--
   28.37 +-- function tempstore.save(blob)
   28.38 +--   return Tempstore:create(blob)
   28.39 +-- end
   28.40 +-- function tempstore.pop(key)
   28.41 +--   return Tempstore:data_by_key(key)
   28.42 +-- end
   28.43 +
   28.44 +
   28.45 +function mondelefant.class_prototype:by_id(id)
   28.46 +  return self:new_selector()
   28.47 +    :add_where{ "id = ?", id }
   28.48 +    :optional_object_mode()
   28.49 +    :exec()
   28.50 +end
    29.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    29.2 +++ b/demo-app/db/schema.sql	Sun Oct 25 12:00:00 2009 +0100
    29.3 @@ -0,0 +1,79 @@
    29.4 +-- only needed for database driven tempstore (see application config)
    29.5 +CREATE TABLE "tempstore" (
    29.6 +        "key"           TEXT            PRIMARY KEY,
    29.7 +        "data"          BYTEA           NOT NULL );
    29.8 +
    29.9 +-- Attention: USER is a reserved word in PostgreSQL. We use it anyway in
   29.10 +-- this example. Don't forget the double quotes where neccessary.
   29.11 +CREATE TABLE "user" (
   29.12 +        "id"            SERIAL8         PRIMARY KEY,
   29.13 +        "ident"         TEXT            NOT NULL,
   29.14 +        "password"      TEXT,
   29.15 +        "name"          TEXT,
   29.16 +        "lang"          TEXT,
   29.17 +        "write_priv"    BOOLEAN         NOT NULL DEFAULT FALSE,
   29.18 +        "admin"         BOOLEAN         NOT NULL DEFAULT FALSE );
   29.19 +
   29.20 +CREATE TABLE "session" (
   29.21 +        "ident"         TEXT            PRIMARY KEY,
   29.22 +        "csrf_secret"   TEXT            NOT NULL,
   29.23 +        "expiry"        TIMESTAMPTZ     NOT NULL DEFAULT NOW() + '24 hours',
   29.24 +        "user_id"       INT8            REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE );
   29.25 +
   29.26 +CREATE TABLE "media_type" (
   29.27 +        "id"            SERIAL8         PRIMARY KEY,
   29.28 +        "name"          TEXT            NOT NULL,
   29.29 +        "description"   TEXT );
   29.30 +
   29.31 +CREATE TABLE "genre" (
   29.32 +        "id"            SERIAL8         PRIMARY KEY,
   29.33 +        "name"          TEXT            NOT NULL,
   29.34 +        "description"   TEXT );
   29.35 +
   29.36 +CREATE TABLE "medium" (
   29.37 +        "id"            SERIAL8         PRIMARY KEY,
   29.38 +        "media_type_id" INT8            NOT NULL REFERENCES "media_type" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
   29.39 +        "name"          TEXT            NOT NULL,
   29.40 +        "copyprotected" BOOLEAN         NOT NULL );
   29.41 +
   29.42 +CREATE TABLE "classification" (
   29.43 +        PRIMARY KEY ("medium_id", "genre_id"),
   29.44 +        "medium_id"     INT8            REFERENCES "medium" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
   29.45 +        "genre_id"      INT8            REFERENCES "genre" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
   29.46 +
   29.47 +CREATE TABLE "track" (
   29.48 +        "id"            SERIAL8         PRIMARY KEY,
   29.49 +        "medium_id"     INT8            NOT NULL REFERENCES "medium" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
   29.50 +        "position"      INT8            NOT NULL,
   29.51 +        "name"          TEXT            NOT NULL,
   29.52 +        "description"   TEXT,
   29.53 +        "duration"      INTERVAL,
   29.54 +        UNIQUE ("medium_id", "position") );
   29.55 +
   29.56 +INSERT INTO "user" ("ident", "password", "name", "write_priv", "admin")
   29.57 +  VALUES ('admin', 'admin', 'Administrator', true, true);
   29.58 +
   29.59 +INSERT INTO "user" ("ident", "password", "name", "write_priv", "admin")
   29.60 +  VALUES ('user', 'User', 'User', true, false);
   29.61 +
   29.62 +INSERT INTO "user" ("ident", "password", "name", "write_priv", "admin")
   29.63 +  VALUES ('anon', 'anon', 'Anonymous', false, false);
   29.64 +
   29.65 +INSERT INTO "media_type" ("name", "description") VALUES ('CD', '');
   29.66 +INSERT INTO "media_type" ("name", "description") VALUES ('Tape', '');
   29.67 +
   29.68 +INSERT INTO "genre" ("name", "description") VALUES ('Klassik', '');
   29.69 +INSERT INTO "genre" ("name", "description") VALUES ('Gospel', '');
   29.70 +INSERT INTO "genre" ("name", "description") VALUES ('Jazz', '');
   29.71 +INSERT INTO "genre" ("name", "description") VALUES ('Traditional', '');
   29.72 +INSERT INTO "genre" ("name", "description") VALUES ('Latin', '');
   29.73 +INSERT INTO "genre" ("name", "description") VALUES ('Blues', '');
   29.74 +INSERT INTO "genre" ("name", "description") VALUES ('Rhythm & blues', '');
   29.75 +INSERT INTO "genre" ("name", "description") VALUES ('Funk', '');
   29.76 +INSERT INTO "genre" ("name", "description") VALUES ('Rock', '');
   29.77 +INSERT INTO "genre" ("name", "description") VALUES ('Pop', '');
   29.78 +INSERT INTO "genre" ("name", "description") VALUES ('Country', '');
   29.79 +INSERT INTO "genre" ("name", "description") VALUES ('Electronic', '');
   29.80 +INSERT INTO "genre" ("name", "description") VALUES ('Ska / Reggea', '');
   29.81 +INSERT INTO "genre" ("name", "description") VALUES ('Hip hop / Rap', '');
   29.82 +
    30.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    30.2 +++ b/demo-app/locale/translations.de.lua	Sun Oct 25 12:00:00 2009 +0100
    30.3 @@ -0,0 +1,59 @@
    30.4 +#!/usr/bin/env lua
    30.5 +return {
    30.6 +["Admin"] = "Admin";
    30.7 +["Are you sure?"] = "Bist Du sicher?";
    30.8 +["Back"] = "Zurück";
    30.9 +["Copy protected"] = "Kopiergeschützt";
   30.10 +["Create new genre"] = "Neues Genre anlegen";
   30.11 +["Create new media type"] = "Neuen Medientyp anlegen";
   30.12 +["Create new medium"] = "Neues Medium anlegen";
   30.13 +["Create new user"] = "Neuen Benutzer anlegen";
   30.14 +["Delete"] = "Löschen";
   30.15 +["Description"] = "Beschreibung";
   30.16 +["Duration"] = "Dauer";
   30.17 +["Genre"] = "Genre";
   30.18 +["Genre '#{name}' created"] = "Genre '#{name}' angelegt";
   30.19 +["Genre '#{name}' deleted"] = "Genre '#{name}' gelöscht";
   30.20 +["Genre '#{name}' updated"] = "Genre '#{name}' aktualisiert";
   30.21 +["Genres"] = "Genres";
   30.22 +["Home"] = "Startseite";
   30.23 +["Id"] = "Id";
   30.24 +["Ident"] = "Ident";
   30.25 +["Invalid username or password!"] = "Ungülter Benutzername oder Kennwort";
   30.26 +["Language changed"] = "Sprache gewechselt";
   30.27 +["Login"] = "Anmeldung";
   30.28 +["Login successful!"] = "Anmeldung erfolgreich!";
   30.29 +["Logout"] = "Abmelden";
   30.30 +["Logout successful"] = "Anmeldung erfolgreich";
   30.31 +["Media"] = "Medium";
   30.32 +["Media type"] = "Medientyp";
   30.33 +["Media type '#{name}' created"] = "Medientyp '#{name}' angelegt";
   30.34 +["Media type '#{name}' deleted"] = "Medientyp '#{name}' gelöscht";
   30.35 +["Media type '#{name}' updated"] = "Medientyp '#{name}' aktualisiert";
   30.36 +["Media types"] = "Medientypen";
   30.37 +["Medium"] = "Medium";
   30.38 +["Medium '#{name}' created"] = "Medium '#{name}' angelegt";
   30.39 +["Medium '#{name}' deleted"] = "Medium '#{name}' gelöscht";
   30.40 +["Medium '#{name}' updated"] = "Medium '#{name}' aktualisiert";
   30.41 +["Name"] = "Name";
   30.42 +["New genre"] = "Neues Genre";
   30.43 +["New media type"] = "Neuer Medientyp";
   30.44 +["New medium"] = "Neues Medium";
   30.45 +["New user"] = "Neuer Benutzer";
   30.46 +["Password"] = "Kennwort";
   30.47 +["Password login"] = "Anmeldung mit Kennwort";
   30.48 +["Pos"] = "Pos";
   30.49 +["Save"] = "Speichern";
   30.50 +["Show"] = "Anzeigen";
   30.51 +["Tracks"] = "Stücke";
   30.52 +["User"] = "Benutzer";
   30.53 +["User '#{name}' created"] = "Benutzer '#{name}' angelegt";
   30.54 +["User '#{name}' deleted"] = "Benutzer '#{name}' gelöscht";
   30.55 +["User '#{name}' updated"] = "Benutzer '#{name}' aktualisiert";
   30.56 +["Username"] = "Benutzername";
   30.57 +["Users"] = "Benutzer";
   30.58 +["Welcome to webmcp demo application"] = "Willkommen zur webmcp Demo-Anwendung";
   30.59 +["Write Priv"] = "Schreibrecht";
   30.60 +["w"] = "w";
   30.61 +["webmcp demo application"] = "webmcp Demo-Anwendung";
   30.62 +}
    31.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    31.2 +++ b/demo-app/locale/translations.en.lua	Sun Oct 25 12:00:00 2009 +0100
    31.3 @@ -0,0 +1,59 @@
    31.4 +#!/usr/bin/env lua
    31.5 +return {
    31.6 +["Admin"] = false;
    31.7 +["Are you sure?"] = false;
    31.8 +["Back"] = false;
    31.9 +["Copy protected"] = false;
   31.10 +["Create new genre"] = false;
   31.11 +["Create new media type"] = false;
   31.12 +["Create new medium"] = false;
   31.13 +["Create new user"] = false;
   31.14 +["Delete"] = false;
   31.15 +["Description"] = false;
   31.16 +["Duration"] = false;
   31.17 +["Genre"] = false;
   31.18 +["Genre '#{name}' created"] = false;
   31.19 +["Genre '#{name}' deleted"] = false;
   31.20 +["Genre '#{name}' updated"] = false;
   31.21 +["Genres"] = false;
   31.22 +["Home"] = false;
   31.23 +["Id"] = false;
   31.24 +["Ident"] = false;
   31.25 +["Invalid username or password!"] = false;
   31.26 +["Language changed"] = false;
   31.27 +["Login"] = false;
   31.28 +["Login successful!"] = false;
   31.29 +["Logout"] = false;
   31.30 +["Logout successful"] = false;
   31.31 +["Media"] = false;
   31.32 +["Media type"] = false;
   31.33 +["Media type '#{name}' created"] = false;
   31.34 +["Media type '#{name}' deleted"] = false;
   31.35 +["Media type '#{name}' updated"] = false;
   31.36 +["Media types"] = false;
   31.37 +["Medium"] = false;
   31.38 +["Medium '#{name}' created"] = false;
   31.39 +["Medium '#{name}' deleted"] = false;
   31.40 +["Medium '#{name}' updated"] = false;
   31.41 +["Name"] = false;
   31.42 +["New genre"] = false;
   31.43 +["New media type"] = false;
   31.44 +["New medium"] = false;
   31.45 +["New user"] = false;
   31.46 +["Password"] = false;
   31.47 +["Password login"] = false;
   31.48 +["Pos"] = false;
   31.49 +["Save"] = false;
   31.50 +["Show"] = false;
   31.51 +["Tracks"] = false;
   31.52 +["User"] = false;
   31.53 +["User '#{name}' created"] = false;
   31.54 +["User '#{name}' deleted"] = false;
   31.55 +["User '#{name}' updated"] = false;
   31.56 +["Username"] = false;
   31.57 +["Users"] = false;
   31.58 +["Welcome to webmcp demo application"] = false;
   31.59 +["Write Priv"] = false;
   31.60 +["w"] = false;
   31.61 +["webmcp demo application"] = false;
   31.62 +}
    32.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    32.2 +++ b/demo-app/locale/translations.es.lua	Sun Oct 25 12:00:00 2009 +0100
    32.3 @@ -0,0 +1,59 @@
    32.4 +#!/usr/bin/env lua
    32.5 +return {
    32.6 +["Admin"] = "Admin";
    32.7 +["Are you sure?"] = "Estás seguro?";
    32.8 +["Back"] = "Atrás";
    32.9 +["Copy protected"] = "Protegido anticopia";
   32.10 +["Create new genre"] = "Crear un nuevo género";
   32.11 +["Create new media type"] = "Crear un nuevo soporte";
   32.12 +["Create new medium"] = "Crear un nuevo medio";
   32.13 +["Create new user"] = "Crear un nuevo usuario";
   32.14 +["Delete"] = "Borrar";
   32.15 +["Description"] = "Descripción";
   32.16 +["Duration"] = "Duración";
   32.17 +["Genre"] = "Género";
   32.18 +["Genre '#{name}' created"] = "Género '#{name}' creado";
   32.19 +["Genre '#{name}' deleted"] = "Género '#{name}' borrado";
   32.20 +["Genre '#{name}' updated"] = "Género '#{name}' actualizado";
   32.21 +["Genres"] = "Géneros";
   32.22 +["Home"] = "Inicio";
   32.23 +["Id"] = "Id";
   32.24 +["Ident"] = "Ident";
   32.25 +["Invalid username or password!"] = "Nombre de usuario o Contraseña Incorrecta";
   32.26 +["Language changed"] = "Idioma cambiado";
   32.27 +["Login"] = "Login";
   32.28 +["Login successful!"] = "Login realizado!";
   32.29 +["Logout"] = "Logout";
   32.30 +["Logout successful"] = "Logout realizado";
   32.31 +["Media"] = "Medios";
   32.32 +["Media type"] = "Soporte";
   32.33 +["Media type '#{name}' created"] = "Soporte '#{name}' creado";
   32.34 +["Media type '#{name}' deleted"] = "Soporte '#{name}' borrado";
   32.35 +["Media type '#{name}' updated"] = "Soporte '#{name}' actualizado";
   32.36 +["Media types"] = "Soporte";
   32.37 +["Medium"] = "Medio";
   32.38 +["Medium '#{name}' created"] = "Medio '#{name}' creado";
   32.39 +["Medium '#{name}' deleted"] = "Medio '#{name}' borrado";
   32.40 +["Medium '#{name}' updated"] = "Medio '#{name}' actualizado";
   32.41 +["Name"] = "Nombre";
   32.42 +["New genre"] = "Nuevo género";
   32.43 +["New media type"] = "Nuevo soporte";
   32.44 +["New medium"] = "Nuevo medio";
   32.45 +["New user"] = "Nuevo usuario";
   32.46 +["Password"] = "Contraseña";
   32.47 +["Password login"] = "Login con contraseña";
   32.48 +["Pos"] = "Pos";
   32.49 +["Save"] = "Guardar";
   32.50 +["Show"] = "Mostrar";
   32.51 +["Tracks"] = "Pistas";
   32.52 +["User"] = "Usuario";
   32.53 +["User '#{name}' created"] = "Usuario '#{name}' creado";
   32.54 +["User '#{name}' deleted"] = "Usuario '#{name}' borrado";
   32.55 +["User '#{name}' updated"] = "Usuario '#{name}' actualizado";
   32.56 +["Username"] = "Nombre de usuario";
   32.57 +["Users"] = "Usuarios";
   32.58 +["Welcome to webmcp demo application"] = "Bienvenido a la aplicación de demostración de webmcp";
   32.59 +["Write Priv"] = "Permiso de escritura";
   32.60 +["w"] = "w";
   32.61 +["webmcp demo application"] = "Aplicación de demostración de webmcp";
   32.62 +}
    33.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.2 +++ b/demo-app/model/classification.lua	Sun Oct 25 12:00:00 2009 +0100
    33.3 @@ -0,0 +1,22 @@
    33.4 +Classification = mondelefant.new_class()
    33.5 +Classification.table = 'classification'
    33.6 +
    33.7 +Classification:add_reference{
    33.8 +  mode          = 'm1',         -- many (m) Classifications can refer to one (1) Medium
    33.9 +  to            = "Medium",     -- name of referenced model (quoting avoids auto-loading of model here)
   33.10 +  this_key      = 'medium_id',  -- our key in the classification table
   33.11 +  that_key      = 'id',         -- other key in the medium table
   33.12 +  ref           = 'medium',     -- name of reference
   33.13 +  back_ref      = nil,          -- not used for m1 relation!
   33.14 +  default_order = nil           -- not used for m1 relation!
   33.15 +}
   33.16 +
   33.17 +Classification:add_reference{
   33.18 +  mode          = 'm1',        -- many (m) Classifications can refer to one (1) Medium
   33.19 +  to            = "Genre",     -- name of referenced model (quoting avoids auto-loading of model here)
   33.20 +  this_key      = 'genre_id',  -- our key in the classification table
   33.21 +  that_key      = 'id',        -- other key in the genre table
   33.22 +  ref           = 'genre',     -- name of reference
   33.23 +  back_ref      = nil,         -- not used for m1 relation!
   33.24 +  default_order = nil          -- not used for m1 relation!
   33.25 +}
    34.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    34.2 +++ b/demo-app/model/genre.lua	Sun Oct 25 12:00:00 2009 +0100
    34.3 @@ -0,0 +1,25 @@
    34.4 +Genre = mondelefant.new_class()
    34.5 +Genre.table = 'genre'
    34.6 +
    34.7 +Genre:add_reference{
    34.8 +  mode          = '1m',               -- one (1) Genre is used for many (m) Classifications
    34.9 +  to            = "Classification",   -- name of referenced model (using a string instead of reference avoids auto-loading here)
   34.10 +  this_key      = 'id',               -- own key in genre table
   34.11 +  that_key      = 'genre_id',         -- other key in classification table
   34.12 +  ref           = 'classifications',  -- name of reference
   34.13 +  back_ref      = 'genre',            -- each autoloaded Classification automatically refers back to the Genre
   34.14 +  default_order = '"media_id"'        -- order Classifications by SQL expression "media_id"
   34.15 +}
   34.16 +
   34.17 +Genre:add_reference{
   34.18 +  mode                  = 'mm',              -- many (m) Genres belong to many (m) Medium entries
   34.19 +  to                    = "Medium",          -- name of referenced model (quoting avoids auto-loading here)
   34.20 +  this_key              = 'id',              -- (primary) key of genre table
   34.21 +  that_key              = 'id',              -- (primary) key of medium talbe
   34.22 +  connected_by_table    = 'classification',  -- table connecting genres with media
   34.23 +  connected_by_this_key = 'genre_id',        -- key in connection table referencing genres
   34.24 +  connected_by_that_key = 'medium_id',       -- key in connection table referencing media
   34.25 +  ref                   = 'media',           -- name of reference
   34.26 +  back_ref              = nil,               -- not used for mm relation!
   34.27 +  default_order         = '"medium"."name", "medium"."id"'  -- mm references need qualified names in SQL order expression!
   34.28 +}
    35.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    35.2 +++ b/demo-app/model/media_type.lua	Sun Oct 25 12:00:00 2009 +0100
    35.3 @@ -0,0 +1,12 @@
    35.4 +MediaType = mondelefant.new_class()
    35.5 +MediaType.table = 'media_type'
    35.6 +
    35.7 +MediaType:add_reference{
    35.8 +  mode          = '1m',             -- one (1) MediaType is set for many (m) media
    35.9 +  to            = "Medium",         -- name of referenced model (quoting avoids auto-loading here)
   35.10 +  this_key      = 'id',             -- own key in media_type table
   35.11 +  that_key      = 'media_type_id',  -- other key in medium table
   35.12 +  ref           = 'media',          -- name of reference
   35.13 +  back_ref      = 'media_type',     -- each autoloaded Medium automatically refers back to the MediaType
   35.14 +  default_order = '"name", "id"'    -- order media by SQL expression "name", "id"
   35.15 +}
    36.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    36.2 +++ b/demo-app/model/medium.lua	Sun Oct 25 12:00:00 2009 +0100
    36.3 @@ -0,0 +1,45 @@
    36.4 +Medium = mondelefant.new_class()
    36.5 +Medium.table = 'medium'
    36.6 +
    36.7 +Medium:add_reference{
    36.8 +  mode          = 'm1',             -- many (m) Medium entries can refer to one (1) MediaType
    36.9 +  to            = "MediaType",      -- name of referenced model (quoting avoids auto-loading here)
   36.10 +  this_key      = 'media_type_id',  -- own key in medium table
   36.11 +  that_key      =  'id',            -- other key in media_type table
   36.12 +  ref           = 'media_type',     -- name of reference
   36.13 +  back_ref      = nil,              -- not used for m1 relation!
   36.14 +  default_order = nil               -- not used for m1 relation!
   36.15 +}
   36.16 +
   36.17 +Medium:add_reference{
   36.18 +  mode          = '1m',               -- one (1) Medium has many (m) Classifications
   36.19 +  to            = "Classification",   -- name of referenced model (quoting avoids auto-loading here)
   36.20 +  this_key      = 'id',               -- own key in medium table
   36.21 +  that_key      = 'medium_id',        -- other key in classification table
   36.22 +  ref           = 'classifications',  -- name of reference
   36.23 +  back_ref      = 'medium',           -- each autoloaded classification automatically refers back to the Medium
   36.24 +  default_order = '"genre_id"'        -- order classifications by SQL expression "genre_id"
   36.25 +}
   36.26 +
   36.27 +Medium:add_reference{
   36.28 +  mode                  = 'mm',              -- many (m) Media belong to many (m) Genres
   36.29 +  to                    = "Genre",           -- name of referenced model (quoting avoids auto-loading here)
   36.30 +  this_key              = 'id',              -- (primary) key of medium table
   36.31 +  that_key              = 'id',              -- (primary) key of genre talbe
   36.32 +  connected_by_table    = 'classification',  -- table connecting media with genres
   36.33 +  connected_by_this_key = 'medium_id',       -- key in classification table referencing media
   36.34 +  connected_by_that_key = 'genre_id',        -- key in classification table referencing genres
   36.35 +  ref                   = 'genres',          -- name of reference
   36.36 +  back_ref              = nil,               -- not used for mm relation!
   36.37 +  default_order         = '"genre"."name", "genre"."id"'  -- mm references need qualified names in SQL order expression!
   36.38 +}
   36.39 +
   36.40 +Medium:add_reference{
   36.41 +  mode          = '1m',         -- one (1) Medium has many (m) Tracks
   36.42 +  to            = "Track",      -- name of referenced model (quoting avoids auto-loading here)
   36.43 +  this_key      = 'id',         -- own key in medium table
   36.44 +  that_key      = 'medium_id',  -- other key in track table
   36.45 +  ref           = 'tracks',     -- name of reference
   36.46 +  back_ref      = 'medium',     -- each autoloaded classification automatically refers back to the Medium
   36.47 +  default_order = '"position"'  -- order tracks by SQL expression "position"
   36.48 +}
    37.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    37.2 +++ b/demo-app/model/session.lua	Sun Oct 25 12:00:00 2009 +0100
    37.3 @@ -0,0 +1,35 @@
    37.4 +Session = mondelefant.new_class()
    37.5 +Session.table = 'session'
    37.6 +Session.primary_key = { "ident" }
    37.7 +
    37.8 +Session:add_reference{
    37.9 +  mode          = 'm1',       -- many (m) sessions refer to one (1) user
   37.10 +  to            = "User",     -- name of referenced model (quoting avoids auto-loading here)
   37.11 +  this_key      = 'user_id',  -- own key in session table
   37.12 +  that_key      = 'id',       -- other key in user table
   37.13 +  ref           = 'user',     -- name of reference
   37.14 +  back_ref      = nil,        -- not used for m1 relation!
   37.15 +  default_order = nil         -- not used for m1 relation!
   37.16 +}
   37.17 +
   37.18 +local function random_string()
   37.19 +  return multirand.string(
   37.20 +    32,
   37.21 +    '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
   37.22 +  )
   37.23 +end
   37.24 +
   37.25 +function Session:new()
   37.26 +  local session = self.prototype.new(self)  -- super call
   37.27 +  session.ident       = random_string()
   37.28 +  session.csrf_secret = random_string()
   37.29 +  session:save() 
   37.30 +  return session
   37.31 +end
   37.32 +
   37.33 +function Session:by_ident(ident)
   37.34 +  local selector = self:new_selector()
   37.35 +  selector:add_where{ 'ident = ?', ident }
   37.36 +  selector:optional_object_mode()
   37.37 +  return selector:exec()
   37.38 +end
    38.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    38.2 +++ b/demo-app/model/tempstore.lua	Sun Oct 25 12:00:00 2009 +0100
    38.3 @@ -0,0 +1,25 @@
    38.4 +Tempstore = mondelefant.new_class()
    38.5 +Tempstore.table = 'tempstore'
    38.6 +
    38.7 +function Tempstore:by_key(key)
    38.8 +  local selector = self:new_selector()
    38.9 +  selector:add_where{ 'key = ?', key }
   38.10 +  selector:optional_object_mode()
   38.11 +  return selector:exec()
   38.12 +end
   38.13 +
   38.14 +function Tempstore:data_by_key(key)
   38.15 +  local tempstore = Tempstore:by_key(key)
   38.16 +  if tempstore then
   38.17 +    tempstore:destroy()
   38.18 +    return tempstore.data
   38.19 +  end
   38.20 +end
   38.21 +
   38.22 +function Tempstore:create(data)
   38.23 +  tempstore = Tempstore:new()
   38.24 +  tempstore.key = multirand.string(22, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
   38.25 +  tempstore.data = data
   38.26 +  tempstore:save()
   38.27 +  return tempstore.key
   38.28 +end
    39.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    39.2 +++ b/demo-app/model/track.lua	Sun Oct 25 12:00:00 2009 +0100
    39.3 @@ -0,0 +1,12 @@
    39.4 +Track = mondelefant.new_class()
    39.5 +Track.table = 'track'
    39.6 +
    39.7 +Track:add_reference{
    39.8 +  mode          = 'm1',         -- many (m) Tracks can refer to one (1) Medium
    39.9 +  to            = "Medium",     -- name of referenced model (quoting avoids auto-loading of model here)
   39.10 +  this_key      = 'medium_id',  -- our key in the track table
   39.11 +  that_key      = 'id',         -- other key in the medium table
   39.12 +  ref           = 'medium',     -- name of reference
   39.13 +  back_ref      = nil,          -- not used for m1 relation!
   39.14 +  default_order = nil           -- not used for m1 relation!
   39.15 +}
    40.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    40.2 +++ b/demo-app/model/user.lua	Sun Oct 25 12:00:00 2009 +0100
    40.3 @@ -0,0 +1,33 @@
    40.4 +User = mondelefant.new_class()
    40.5 +User.table = 'user'
    40.6 +
    40.7 +User:add_reference{
    40.8 +  mode          = '1m',        -- one (1) user can have many (m) sessions
    40.9 +  to            = "Session",   -- referenced model (quoting avoids auto-loading here)
   40.10 +  this_key      = 'id',        -- own key in user table
   40.11 +  that_key      = 'user_id',   -- other key in session table
   40.12 +  ref           = 'sessions',  -- name of reference
   40.13 +  back_ref      = 'user',      -- each autoloaded Session automatically refers back to the User
   40.14 +  default_order = '"ident"'    -- order sessions by SQL expression "ident"
   40.15 +}
   40.16 +
   40.17 +function User:by_ident_and_password(ident, password)
   40.18 +  local selector = self:new_selector()
   40.19 +  selector:add_where{ 'ident = ? AND password = ?', ident, password }
   40.20 +  selector:optional_object_mode()
   40.21 +  return selector:exec()
   40.22 +end
   40.23 +
   40.24 +function User.object_get:name_with_login()
   40.25 +  return self.name .. ' (' .. self.login .. ')'
   40.26 +end
   40.27 +
   40.28 +function User.object:require_privilege(privilege)
   40.29 +  if privilege == "admin" then
   40.30 +    assert(self.admin, "Administrator privilege required")
   40.31 +  elseif privilege == "write" then
   40.32 +    assert(self.write_priv, "Write privilege required")
   40.33 +  else
   40.34 +    error("Unknown privilege passed to require_privilege method of User")
   40.35 +  end
   40.36 +end
    41.1 Binary file demo-app/static/lang/de.png has changed
    42.1 Binary file demo-app/static/lang/en.png has changed
    43.1 Binary file demo-app/static/lang/es.png has changed
    44.1 Binary file demo-app/static/logo.png has changed
    45.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    45.2 +++ b/demo-app/static/style.css	Sun Oct 25 12:00:00 2009 +0100
    45.3 @@ -0,0 +1,383 @@
    45.4 +/*
    45.5 + * ********** body ***********
    45.6 + */
    45.7 +
    45.8 +body {
    45.9 +	background-color: #ddd;
   45.10 +	font-family: sans-serif;
   45.11 +	margin: 0;
   45.12 +	padding: 0;
   45.13 +	font-size: 10pt;
   45.14 +}
   45.15 +
   45.16 +body, td, th, input, select {
   45.17 +	font-size: 10pt;
   45.18 +}
   45.19 +
   45.20 +div {
   45.21 +	padding: 0px;
   45.22 +	margin: 0px;
   45.23 +}
   45.24 +
   45.25 +/*
   45.26 + * ********** Logo ***********
   45.27 + */
   45.28 +
   45.29 +.layout_logo {
   45.30 +	background: #fff;
   45.31 +	border: 1px solid #444;
   45.32 +	border-left: none;
   45.33 +	border-top: none;
   45.34 +	color: #444;
   45.35 +	float: left;
   45.36 +	height: 2.29em;
   45.37 +	margin-bottom: 2ex;
   45.38 +	padding-bottom: 0.3ex;
   45.39 +	padding-left: 0.3ex;
   45.40 +	padding-top: 0.3ex;
   45.41 +}
   45.42 +
   45.43 +.logo {
   45.44 +	font-size: 1em;
   45.45 +	font-weight: bold;
   45.46 +}
   45.47 +
   45.48 +.logo img {
   45.49 +	height: 2.29em;
   45.50 +	margin-bottom: -0.7em;
   45.51 +}
   45.52 +
   45.53 +/*
   45.54 + * ********** Top navigation ***********
   45.55 + */
   45.56 +
   45.57 +.layout_topnav {
   45.58 +	float: left;
   45.59 +	margin-bottom: 2em;
   45.60 +	margin-left: 2ex;
   45.61 +}
   45.62 +
   45.63 +.slot_topnav .nav {
   45.64 +	background: #fff;
   45.65 +	border: 1px solid #444;
   45.66 +	border-top: none;
   45.67 +	color: #000;
   45.68 +	display: block;
   45.69 +	float: left;
   45.70 +	font-weight: bold;
   45.71 +	height: 2.29em;
   45.72 +	margin-right: 1ex;
   45.73 +	padding-bottom: 0.3ex;
   45.74 +	padding-left: 0.3ex;
   45.75 +	padding-right: 1ex;
   45.76 +	padding-top: 0.3ex;
   45.77 +	text-decoration: none;
   45.78 +}
   45.79 +
   45.80 +
   45.81 +
   45.82 +.vertical .lang_chooser {
   45.83 +  margin-left: 25ex;
   45.84 +  margin-bottom: 1ex;
   45.85 +}
   45.86 +
   45.87 +.vertical .lang_chooser div {
   45.88 +  margin-right: 1ex;
   45.89 +}
   45.90 +
   45.91 +.lang_chooser div {
   45.92 +  float: left;
   45.93 +  text-align: center;
   45.94 +}
   45.95 +
   45.96 +.lang_chooser a {
   45.97 +  color: #000;
   45.98 +  font-size: 75%;
   45.99 +}
  45.100 +
  45.101 +.lang_chooser img {
  45.102 +  display: block;
  45.103 +}
  45.104 +
  45.105 +.lang_chooser img {
  45.106 +  border: 1px solid white;
  45.107 +}
  45.108 +
  45.109 +.lang_chooser a:hover img {
  45.110 +  border: 1px solid #444;
  45.111 +}
  45.112 +
  45.113 +.lang_chooser a:hover {
  45.114 +  background: #444;
  45.115 +  color: #fff;
  45.116 +}
  45.117 +
  45.118 +
  45.119 +.slot_topnav a:hover {
  45.120 +  background: #444;
  45.121 +  color: #fff;
  45.122 +}
  45.123 +
  45.124 +/*
  45.125 + * ********** Content ***********
  45.126 + */
  45.127 +
  45.128 +.layout_content {
  45.129 +	margin-left: 2ex;
  45.130 +	margin-right: 2ex;
  45.131 +}
  45.132 +
  45.133 +
  45.134 +/*
  45.135 + * ********** Messages ***********
  45.136 + */
  45.137 +
  45.138 +.layout_notice, .layout_error, .layout_warning {
  45.139 +	background:	#fff;
  45.140 +	font-weight: bold;
  45.141 +	right: 2ex;
  45.142 +	line-height: 1.7em;
  45.143 +	position: absolute;
  45.144 +	top: 2ex;
  45.145 +	width: 60ex;
  45.146 +	-moz-opacity:0.7;
  45.147 +}
  45.148 +
  45.149 +.slot_notice, .slot_warning, .slot_error {
  45.150 +	padding: 2ex;
  45.151 +}
  45.152 +
  45.153 +.slot_notice {
  45.154 +	color: green;
  45.155 +	border: 2px solid green;
  45.156 +}
  45.157 +
  45.158 +.slot_warning {
  45.159 +	color: orange;
  45.160 +	border: 2px solid orange;
  45.161 +}
  45.162 +
  45.163 +.slot_error {
  45.164 +	color: red;
  45.165 +	border: 2px solid red;
  45.166 +}
  45.167 +
  45.168 +
  45.169 +
  45.170 +/*
  45.171 + * ********** Title, Actions and Main ***********
  45.172 + */
  45.173 +
  45.174 +.layout_title {
  45.175 +	background: #fff;
  45.176 +	border: 1px solid #444;
  45.177 +	border-bottom: none;
  45.178 +	padding: 0.5ex;
  45.179 +	font-size: 120%;
  45.180 +	font-weight: bold;
  45.181 +	float: left;
  45.182 +}
  45.183 +
  45.184 +.layout_title .object_class {
  45.185 +	float: left;
  45.186 +	font-size: 0.7em;
  45.187 +	font-weight: normal;
  45.188 +	margin-right: 1ex;
  45.189 +}
  45.190 +
  45.191 +.layout_actions {
  45.192 +	background: #444;
  45.193 +	border-left: 1px solid #444;
  45.194 +	padding-top: 0.5ex;
  45.195 +	padding-bottom: 0.5ex;
  45.196 +}
  45.197 +
  45.198 +.slot_actions a {
  45.199 +	color: #fff;
  45.200 +	text-decoration: none;
  45.201 +	padding: 0.5ex;
  45.202 +}
  45.203 +
  45.204 +.slot_actions a:hover {
  45.205 +	background: #ccc;
  45.206 +	color: #444;
  45.207 +}
  45.208 +
  45.209 +.slot_main {
  45.210 +	background: #fff;
  45.211 +	border: 1px solid #444;
  45.212 +	border-top: none;
  45.213 +	padding: 2ex;
  45.214 +	padding-bottom: 0px;
  45.215 +}
  45.216 +
  45.217 +
  45.218 +
  45.219 +/*
  45.220 + * ********** ui ***********
  45.221 + */
  45.222 +
  45.223 +
  45.224 +form.ui_link {
  45.225 +  display: inline;
  45.226 +  margin: 0px;
  45.227 +  padding: 0px;
  45.228 +}
  45.229 +
  45.230 +.vertical {
  45.231 +  margin-bottom: 1em;
  45.232 +}
  45.233 +
  45.234 +.vertical:after, .vertical div:after {
  45.235 +  clear: left;
  45.236 +  content: ".";
  45.237 +  display: block;
  45.238 +  height: 0;
  45.239 +  visibility: hidden;
  45.240 +}
  45.241 +
  45.242 +.vertical label {
  45.243 +  float: left;
  45.244 +  width: 25ex;
  45.245 +  padding-top: 0.7em;
  45.246 +  font-size: 80%;
  45.247 +  font-weight: bold;
  45.248 +  color: #666;
  45.249 +  text-align: right;
  45.250 +  text-transform: uppercase;
  45.251 +}
  45.252 +
  45.253 +.vertical select,
  45.254 +.vertical input,
  45.255 +.vertical textarea,
  45.256 +.vertical span {
  45.257 +  margin-left: 1em;
  45.258 +  margin-bottom: 2ex;
  45.259 +}
  45.260 +
  45.261 +.vertical textarea {
  45.262 +  border: none;
  45.263 +  border: 1px dotted #777;
  45.264 +  padding: 0px;
  45.265 +}
  45.266 +
  45.267 +.vertical input, 
  45.268 +.vertical select {
  45.269 +  border: 1px dotted #777;
  45.270 +}
  45.271 +
  45.272 +.vertical textarea {
  45.273 +  width: 80ex;
  45.274 +  height: 5em;
  45.275 +  font-family: sans-serif;
  45.276 +  font-size: 100%;
  45.277 +  padding: 0.5ex;
  45.278 +}
  45.279 +
  45.280 +input[type=text] {
  45.281 +  background: #fff;
  45.282 +  padding: 0.5ex;
  45.283 +}
  45.284 +
  45.285 +input[type=submit] {
  45.286 +  margin-left: 25ex;
  45.287 +  border: 1px dotted #777;
  45.288 +}
  45.289 +
  45.290 +input[type=submit]:hover {
  45.291 +  border: 1px solid #777;
  45.292 +}
  45.293 +
  45.294 +/* ui.list */
  45.295 +
  45.296 +table.ui_list {
  45.297 +	border-collapse: collapse;
  45.298 +}
  45.299 +
  45.300 +ul.ui_list {
  45.301 +	padding: 0px;
  45.302 +	margin: 0px;
  45.303 +}
  45.304 +
  45.305 +.ui_list li {
  45.306 +	clear: left;
  45.307 +	list-style-type: none;
  45.308 +}
  45.309 +
  45.310 +.ui_list_title {
  45.311 +	font-size: 110%;
  45.312 +	font-weight: bold;
  45.313 +	margin-bottom: 0.2em;
  45.314 +}
  45.315 +
  45.316 +
  45.317 +li.ui_list_head {
  45.318 +	font-weight: bold;
  45.319 +	background: #777;
  45.320 +	color: #fff;
  45.321 +}
  45.322 +
  45.323 +.ui_list li:hover,
  45.324 +.ui_list tr:hover {
  45.325 +	background: #ddd;
  45.326 +}
  45.327 +
  45.328 +.ui_list li.ui_list_head:hover,
  45.329 +.ui_list tr.ui_list_head:hover {
  45.330 +	background: #777;
  45.331 +}
  45.332 +
  45.333 +.ui_list a {
  45.334 +	text-decoration: none;
  45.335 +	color: #000;
  45.336 +	border-bottom: 1px dashed #777;
  45.337 +}
  45.338 +
  45.339 +.ui_list a:hover {
  45.340 +  border-bottom: 1px solid #000;
  45.341 +}
  45.342 +
  45.343 +.ui_list li div {
  45.344 +  float: left;
  45.345 +}
  45.346 +
  45.347 +.ui_list td {
  45.348 +  vertical-align: top;
  45.349 +}
  45.350 +
  45.351 +.ui_list li div,
  45.352 +.ui_list td {
  45.353 +  padding-top: 0.4ex;
  45.354 +  padding-bottom: 0.4ex;
  45.355 +  padding-right: 0.4em;
  45.356 +}
  45.357 +
  45.358 +.ui_list li div div {
  45.359 +  padding: 0px;
  45.360 +}
  45.361 +
  45.362 +.ui_list li div a {
  45.363 +  text-decoration: none;
  45.364 +  color: #000;
  45.365 +}
  45.366 +
  45.367 +.ui_list_col_align_right {
  45.368 +  text-align: right;
  45.369 +}
  45.370 +
  45.371 +.ui_paginate_select {
  45.372 +  text-align: center;
  45.373 +}
  45.374 +
  45.375 +.ui_paginate_select a {
  45.376 +  padding-left: 1ex;
  45.377 +  padding-right: 1ex;
  45.378 +  background-color: #ccc;
  45.379 +  color: #000;
  45.380 +}
  45.381 +
  45.382 +.ui_paginate_select a.active,
  45.383 +.ui_paginate_select a:hover {
  45.384 +  background-color: #444;
  45.385 +  color: #fff;
  45.386 +}
    46.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    46.2 +++ b/demo-app/static/trace.css	Sun Oct 25 12:00:00 2009 +0100
    46.3 @@ -0,0 +1,153 @@
    46.4 +.layout_trace {
    46.5 +  position: absolute;
    46.6 +  right: 0;
    46.7 +  margin-top: 20px;
    46.8 +  border: 1px solid #404040;
    46.9 +  font-size: 70%;
   46.10 +  padding: 5px;
   46.11 +  background: #ffe0c0;
   46.12 +}
   46.13 +
   46.14 +#trace_show {
   46.15 +  cursor: pointer;
   46.16 +}
   46.17 +
   46.18 +.trace_list {
   46.19 +  margin: 0px;
   46.20 +  margin-bottom: 10px;
   46.21 +  padding: 0px;
   46.22 +  list-style-type: none;
   46.23 +}
   46.24 +
   46.25 +.trace_list .trace_list {
   46.26 +  border-top: 1px solid;
   46.27 +  margin-bottom: 0px;
   46.28 +}
   46.29 +
   46.30 +.trace_list li {
   46.31 +  margin: 3px;
   46.32 +  padding: 0px;
   46.33 +}
   46.34 +
   46.35 +.trace_head {
   46.36 +  font-weight: bold;
   46.37 +  margin: 1px;
   46.38 +}
   46.39 +
   46.40 +.trace_error {
   46.41 +  border: 3px solid red;
   46.42 +  background-color: black;
   46.43 +  color: #c00000;
   46.44 +  text-align: center;
   46.45 +  text-decoration: blink;
   46.46 +}
   46.47 +
   46.48 +.trace_error_position {
   46.49 +  color: red;
   46.50 +  text-decoration: blink;
   46.51 +  font-weight: bold;
   46.52 +}
   46.53 +
   46.54 +.trace_config {
   46.55 +  border: 1px solid #608000;
   46.56 +  background-color: #ffffff;
   46.57 +  color: #608000;
   46.58 +}
   46.59 +
   46.60 +.trace_config .trace_list {
   46.61 +  border-color: #608000;
   46.62 +}
   46.63 +
   46.64 +.trace_request {
   46.65 +  border: 1px solid #6000ff;
   46.66 +  background-color: #c080ff;
   46.67 +  color: #6000ff;
   46.68 +}
   46.69 +
   46.70 +.trace_request .trace_list {
   46.71 +  border-color: #6000ff;
   46.72 +}
   46.73 +
   46.74 +.trace_filter {
   46.75 +  border: 1px solid #606060;
   46.76 +  background-color: #c0c0c0;
   46.77 +  color: #606060;
   46.78 +}
   46.79 +
   46.80 +.trace_filter .trace_list {
   46.81 +  border-color: #606060;
   46.82 +}
   46.83 +
   46.84 +.trace_view {
   46.85 +  border: 1px solid #0000ff;
   46.86 +  background-color: #40c0ff;
   46.87 +  color: #0000ff;
   46.88 +}
   46.89 +
   46.90 +.trace_view .trace_list {
   46.91 +  border-color: #0000ff;
   46.92 +}
   46.93 +
   46.94 +.trace_action_success {
   46.95 +  border: 1px solid #006000;
   46.96 +  background-color: #80ff80;
   46.97 +  color: #006000;
   46.98 +}
   46.99 +
  46.100 +.trace_action_success .trace_list {
  46.101 +  border-color: #006000;
  46.102 +}
  46.103 +
  46.104 +.trace_action_softfail {
  46.105 +  border: 1px solid #600000;
  46.106 +  background-color: #ff6020;
  46.107 +  color: #600000;
  46.108 +}
  46.109 +
  46.110 +.trace_action_softfail .trace_list {
  46.111 +  border-color: #600000;
  46.112 +}
  46.113 +
  46.114 +.trace_action_status {
  46.115 +  font-weight: bold;
  46.116 +}
  46.117 +
  46.118 +.trace_action_neutral {
  46.119 +  border: 1px solid #600000;
  46.120 +  background-color: #ffc040;
  46.121 +  color: #600000;
  46.122 +}
  46.123 +
  46.124 +.trace_action_neutral .trace_list {
  46.125 +  border-color: #600000;
  46.126 +}
  46.127 +
  46.128 +.trace_redirect, .trace_forward {
  46.129 +  border: 1px solid #404000;
  46.130 +  background-color: #c08040;
  46.131 +  color: #404000;
  46.132 +}
  46.133 +
  46.134 +.trace_redirect .trace_list, .trace_forward .trace_list {
  46.135 +  border-color: #404000;
  46.136 +}
  46.137 +
  46.138 +.trace_exectime {
  46.139 +  border: 1px solid black;
  46.140 +  background-color: #404040;
  46.141 +  color: white;
  46.142 +}
  46.143 +
  46.144 +.trace_exectime .trace_list {
  46.145 +  border-color: white;
  46.146 +}
  46.147 +
  46.148 +.trace_close {
  46.149 +  border: 1px solid black;
  46.150 +  background-color: #605040;
  46.151 +  margin: 3px;
  46.152 +  padding: 3px;
  46.153 +  color: white;
  46.154 +  text-align: center;
  46.155 +  cursor: pointer;
  46.156 +}
    48.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    48.2 +++ b/doc/apache.sample.conf	Sun Oct 25 12:00:00 2009 +0100
    48.3 @@ -0,0 +1,37 @@
    48.4 +# Apache modules cgi_module, env, rewrite and alias must be loaded before
    48.5 +# Take a look in your main apache configuration!
    48.6 +
    48.7 +RewriteEngine on
    48.8 +# do not rewrite static URLs
    48.9 +RewriteRule ^/webmcp-demo/static/(.*)$ /webmcp-demo/static/$1
   48.10 +# base URL
   48.11 +RewriteRule ^/webmcp-demo/(\?(.*))?$ /webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=0&_webmcp_module=index&_webmcp_view=index&$2
   48.12 +# module base URLs
   48.13 +RewriteRule ^/webmcp-demo/([^/\?]+)/(\?(.*))?$ /webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=1&_webmcp_module=$1&_webmcp_view=index&$3
   48.14 +# actions
   48.15 +RewriteRule ^/webmcp-demo/([^/\?]+)/([^/\.\?]+)(\?(.*))?$ /webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=1&_webmcp_module=$1&_webmcp_action=$2&$4
   48.16 +# views without numeric id or string ident
   48.17 +RewriteRule ^/webmcp-demo/([^/\?]+)/([^/\.\?]+)\.([^/\?]+)(\?(.*))?$ "/webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=1&_webmcp_module=$1&_webmcp_view=$2&_webmcp_suffix=$3&$5
   48.18 +# views with numeric id or string ident
   48.19 +RewriteRule ^/webmcp-demo/([^/\?]+)/([^/\?]+)/([^/\.\?]+)\.([^/\?]+)(\?(.*))?$ /webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=2&_webmcp_module=$1&_webmcp_view=$2&_webmcp_id=$3&_webmcp_suffix=$4&$6
   48.20 +
   48.21 +# Directly serve static files
   48.22 +Alias /webmcp-demo/static /__INSERT_LOCAL_FILE_PATH_TO_DEMO_APPLICATION_HERE__/static
   48.23 +
   48.24 +# Connect extarnal path to the webmcp cgi interface
   48.25 +ScriptAlias /webmcp-demo/ /__INSERT_LOCAL_FILE_PATH_TO_WEBMCP_FRAMEWORK_HERE__/cgi-bin/
   48.26 +
   48.27 +# Allow CGI execution for the webmcp CGI interface
   48.28 +<Directory "/__INSERT_LOCAL_FILE_PATH_TO_WEBMCP_FRAMEWORK_HERE__/cgi-bin">
   48.29 +    AllowOverride None
   48.30 +    Options ExecCGI -MultiViews +SymLinksIfOwnerMatch
   48.31 +    Order allow,deny
   48.32 +    Allow from all
   48.33 +</Directory>
   48.34 +
   48.35 +# Configure environment for demo application    
   48.36 +<Location /webmcp-demo>
   48.37 +    SetEnv WEBMCP_APP_BASEPATH '/__INSERT_LOCAL_FILE_PATH_TO_DEMO_APPLICATION_HERE__'
   48.38 +    SetEnv WEBMCP_CONFIG_NAME 'demo'
   48.39 +</Location>
   48.40 +
    49.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    49.2 +++ b/doc/autodoc-footer.htmlpart	Sun Oct 25 12:00:00 2009 +0100
    49.3 @@ -0,0 +1,7 @@
    49.4 +    </ul>
    49.5 +    <hr style="margin-top: 3em;"/>
    49.6 +    <p style="text-align: right; font-style: italic;">
    49.7 +      Copyright (c) 2009 Public Software Group e. V., Berlin
    49.8 +    </p>
    49.9 +  </body>
   49.10 +</html>
    50.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    50.2 +++ b/doc/autodoc-header.htmlpart	Sun Oct 25 12:00:00 2009 +0100
    50.3 @@ -0,0 +1,306 @@
    50.4 +<html>
    50.5 +  <head>
    50.6 +    <style>
    50.7 +      body {
    50.8 +        font-family: "Liberation Sans", sans-serif;
    50.9 +        font-size: 11pt;
   50.10 +        padding-bottom: 5ex;
   50.11 +      }
   50.12 +      .warning {
   50.13 +        color: #ff0000;
   50.14 +      }
   50.15 +      h1, h2 {
   50.16 +        font-family: "Liberation Serif", Georgia, serif;
   50.17 +      }
   50.18 +      h2 {
   50.19 +        margin-bottom: 0.3ex;
   50.20 +      }
   50.21 +      p {
   50.22 +        margin: 0px;
   50.23 +        line-height: 130%;
   50.24 +      }
   50.25 +      tt, pre {
   50.26 +        font-size: 10pt;
   50.27 +      }
   50.28 +      tt {
   50.29 +        font-weight: bold;
   50.30 +        white-space: nowrap;
   50.31 +      }
   50.32 +      .autodoc_entry {
   50.33 +        margin-top: 1ex;
   50.34 +        margin-bottom: 1ex;
   50.35 +       }
   50.36 +      .autodoc_comment_tail {
   50.37 +        font-style: italic;
   50.38 +      }
   50.39 +      .autodoc_entry .short_synopsis {
   50.40 +        cursor: pointer;
   50.41 +      }
   50.42 +      .autodoc_details {
   50.43 +        padding-left: 1em;
   50.44 +        padding-right: 1em;
   50.45 +        border: 1px solid #777;
   50.46 +      }
   50.47 +      .autodoc_synopsis {
   50.48 +        font-weight: bold;
   50.49 +      }
   50.50 +      .autodoc_synopsis .autodoc_comment_tail {
   50.51 +        font-weight: normal;
   50.52 +        color: #008000;
   50.53 +      }
   50.54 +      .autodoc_entry .autodoc_comment {
   50.55 +        color: #400080;
   50.56 +      }
   50.57 +      .autodoc_source {
   50.58 +        color: #505050;
   50.59 +      }
   50.60 +    </style>
   50.61 +    <title>WebMCP 1.0.0 Documentation</title>
   50.62 +  </head>
   50.63 +  <body>
   50.64 +    <h1>WebMCP 1.0.0 Documentation</h1>
   50.65 +    <p>
   50.66 +      WebMCP is a completely new web development framework, and has not been extensively tested yet. The API might change at any time, but in future releases there will be a list of all changes, which break downward compatibility.
   50.67 +    </p>
   50.68 +    <h2>Requirements</h2>
   50.69 +    <p>
   50.70 +      WebMCP has been developed on Linux and FreeBSD. Using it with Mac&nbsp;OS&nbsp;X is completely untested; Microsoft Windows is not supported. Beside the operating system, the only mandatory dependencies for WebMCP are the programming language <a href="http://www.lua.org/">Lua</a> version 5.1, <a href="http://www.postgresql.org/">PostgreSQL</a> version 8.2 or higher, a C compiler, and a Webserver like Lighttpd or Apache.
   50.71 +    </p>
   50.72 +    <h2>Installation</h2>
   50.73 +    <p>
   50.74 +      After downloading the tar.gz package, unpack it, enter the unpacked directory and type <tt>make</tt>. If you use Mac OS X or if you experience problems during compilation, you need to edit the <tt>Makefile.options</tt> file prior to compilation. The framework itself will be available in the <tt>framework/</tt> directory, while a demo application is available in the <tt>demo-app/</tt> directory. The <tt>framework.precompiled/</tt> and <tt>demo-app.precompiled/</tt> directories will contain a version with all Lua files being byte-code pre-compiled, which can be used instead. You may copy these directories (with <tt>cp -L</tt> to follow links) to any other place you like. Use the files <tt>doc/lighttpd.sample.conf</tt> or <tt>doc/apache.sample.conf</tt> to setup your webserver appropriatly. Don't forget to setup a database, and make the <tt>tmp/</tt> directory of the application writable for the web server process. Good luck and have fun!
   50.75 +    </p>
   50.76 +    <h2>Using the atom library</h2>
   50.77 +    <p>
   50.78 +      Lua itself has only very few built-in data types. The atom library gives support for extra data types. Currently the following extra data types are provided:
   50.79 +    </p>
   50.80 +    <ul>
   50.81 +      <li>atom.fraction</li>
   50.82 +      <li>atom.date</li>
   50.83 +      <li>atom.time</li>
   50.84 +      <li>atom.timestamp (date and time combined in one data type)</li>
   50.85 +    </ul>
   50.86 +    <p>
   50.87 +      In addition the following pseudo-types are existent, corresponding to Lua's base types:
   50.88 +    </p>
   50.89 +    <ul>
   50.90 +      <li>atom.boolean</li>
   50.91 +      <li>atom.string</li>
   50.92 +      <li>atom.integer</li>
   50.93 +      <li>atom.number</li>
   50.94 +    </ul>
   50.95 +    <p>
   50.96 +      Both atom.integer and atom.number refer to Lua's base type &ldquo;number&rdquo;.
   50.97 +    </p>
   50.98 +    <p>
   50.99 +      New values of atom data types are created by either calling <tt>atom.<i>type</i>:load(string_representation)</tt> or by calling <tt>atom.<i>type</i>{...}</tt>, e.g. <tt>atom.date{year=1970, month=1, day=1}</tt>. You can dump any atom value as a string by calling <tt>atom.dump(value)</tt> and later reload it with <tt>atom.<i>type</i>:load(string)</tt>.
  50.100 +    </p>
  50.101 +    <h2>Using the Object-Relational Mapper &ldquo;mondelefant&rdquo;</h2>
  50.102 +    <p>
  50.103 +      The library &ldquo;mondelefant&rdquo; shipping with WebMCP can be used to access PostgreSQL databases. It also serves as an Object-Relational Mapper (ORM). Opening a connection to a database is usually done in a config file in the following way:
  50.104 +    </p>
  50.105 +    <pre>
  50.106 +db = assert( mondelefant.connect{ engine='postgresql', dbname='webmcp_demo' } )
  50.107 +at_exit(function() 
  50.108 +  db:close()
  50.109 +end)
  50.110 +function mondelefant.class_prototype:get_db_conn() return db end
  50.111 +
  50.112 +function db:sql_tracer(command)
  50.113 +  return function(error_info)
  50.114 +    local error_info = error_info or {}
  50.115 +    trace.sql{ command = command, error_position = error_info.position }
  50.116 +  end
  50.117 +end</pre>
  50.118 +    <p>
  50.119 +      Overwriting the <tt>sql_tracer</tt> method of the database handle is optional, but helpful for debugging. The parameters for <tt>mondelefant.connect</tt> are directly passed to PostgreSQL's client library libpq. See <a href="http://www.postgresql.org/docs/8.4/interactive/libpq-connect.html">PostgreSQL's documentation on PQconnect</a> for information about supported parameters.
  50.120 +    </p>
  50.121 +    <p>
  50.122 +      To define a model to be used within a WebMCP application, create a file named with the name of the model and <tt>.lua</tt> as extension in the <tt>model/</tt> directory of your application. The most basic definition of a model (named &ldquo;movie&rdquo; in this example) is:
  50.123 +    </p>
  50.124 +    <pre>
  50.125 +Movie = mondelefant.new_class()
  50.126 +Movie.table = 'movie'</pre>
  50.127 +    <p>
  50.128 +      Note: Model classes are always written CamelCase, while the name of the file in <tt>model/</tt> is written lower_case.
  50.129 +    </p>
  50.130 +    <p>
  50.131 +      To select objects from the database, the mondelefant library provides a selector framework:
  50.132 +    </p>
  50.133 +    <pre>
  50.134 +local s = Movie:new_selector()
  50.135 +s:add_where{ 'id = ?', param.get_id() }
  50.136 +s:single_object_mode()  -- return single object instead of list
  50.137 +local movie = s:exec()</pre>
  50.138 +    <p>
  50.139 +      A short form of the above query would be:
  50.140 +    </p>
  50.141 +    <pre>
  50.142 +local movie = Movie:new_selector():add_where{ 'id = ?', param.get_id() }:single_object_mode():exec()</pre>
  50.143 +    <p>
  50.144 +      For more examples about how to use the model system, please take a look at the demo application.
  50.145 +    </p>
  50.146 +    <h2>The Model-View-Action (MVA) concept</h2>
  50.147 +    <p>
  50.148 +      As opposed to other web application frameworks, WebMCP does not use a Model-View-Controller (MVC) concept, but a Model-View-Action (MVA) concept.
  50.149 +    </p>
  50.150 +    <h3>Models</h3>
  50.151 +    <p>
  50.152 +      The models in MVA are like the models in MVC; they are used to access data stored in a relational database (PostgreSQL) in an object oriented way. They can also be used to provide methods for working with objects representing the database entries.
  50.153 +    </p>
  50.154 +    <h3>Views</h3>
  50.155 +    <p>
  50.156 +      The views in the MVA concept are different from the views in the MVC concept. As WebMCP has no controllers, the views are responsible for processing the GET/POST parameters from the webbrowser, fetching the data to be displayed, and creating the output by directly writing HTML to slots in a layout or by calling helper functions for the user interface.
  50.157 +    </p>
  50.158 +    <h3>Actions</h3>
  50.159 +    <p>
  50.160 +      Actions are similar to views, but supposed to change data in the database, hence only callable by HTTP POST requests. They are also responsible for processing the POST parameters from the webbrowser. They can modify the database, but instead of rendering a page to be displayed, they just return a status code. Depending on the status code there will be an internal forward or an HTTP 303 redirect to a view. When calling an action via a POST request, additional POST parameters, which are usually added by hidden form fields, determine the view to be displayed for each status code returned by the action.
  50.161 +    </p>
  50.162 +    <h2>Directory structure of a WebMCP application</h2>
  50.163 +    <ul>
  50.164 +      <li>
  50.165 +        Base Directory
  50.166 +        <ul>
  50.167 +          <li>
  50.168 +            <tt>app/</tt>
  50.169 +            <ul>
  50.170 +              <li>
  50.171 +                <tt>main/</tt>
  50.172 +                <ul>
  50.173 +                  <li>
  50.174 +                    <tt>_filter/</tt>
  50.175 +                    <ul>
  50.176 +                      <li>
  50.177 +                        <tt>10_first_filter.lua</tt>
  50.178 +                      <li>
  50.179 +                      </li>
  50.180 +                        <tt>30_third_filter.lua</tt>
  50.181 +                      </li>
  50.182 +                      <li>&hellip;</li>
  50.183 +                    </ul>
  50.184 +                  </li>
  50.185 +                  <li>
  50.186 +                    <tt>_filter_action/</tt>
  50.187 +                    <ul>
  50.188 +                      <li>
  50.189 +                        <tt>20_second_filter.lua</tt>
  50.190 +                      </li>
  50.191 +                      <li>&hellip;</li>
  50.192 +                    </ul>
  50.193 +                  </li>
  50.194 +                  <li>
  50.195 +                    <tt>_filter_view/</tt>
  50.196 +                    <ul>
  50.197 +                      <li>&hellip;</li>
  50.198 +                    </ul>
  50.199 +                  </li>
  50.200 +                  <li>
  50.201 +                    <tt>_layout/</tt>
  50.202 +                    <ul>
  50.203 +                      <li>&hellip;</li>
  50.204 +                    </ul>
  50.205 +                  </li>
  50.206 +                  <li>
  50.207 +                    <tt>index/</tt>
  50.208 +                    <ul>
  50.209 +                      <li>
  50.210 +                        <tt>_action/</tt>
  50.211 +                        <ul>
  50.212 +                          <li>
  50.213 +                            <i>action_name</i><tt>.lua</tt>
  50.214 +                          </li>
  50.215 +                          <li>
  50.216 +                            <i>another_action_name</i><tt>.lua</tt>
  50.217 +                          </li>
  50.218 +                          <li>&hellip;</li>
  50.219 +                        </ul>
  50.220 +                      </li>
  50.221 +                      <li>
  50.222 +                        <tt>index.lua</tt>
  50.223 +                      </li>
  50.224 +                      <li>
  50.225 +                        <i>other_view_name</i><tt>.lua</tt>
  50.226 +                      </li>
  50.227 +                      <li>&hellip;</li>
  50.228 +                    </ul>
  50.229 +                  </li>
  50.230 +                  <li>
  50.231 +                    <i>other_module_name</i><tt>/</tt>
  50.232 +                    <ul>
  50.233 +                      <li>&hellip;</li>
  50.234 +                    </ul>
  50.235 +                  </li>
  50.236 +                </ul>
  50.237 +              </li>
  50.238 +              <li>
  50.239 +                <i>other_application_name</i><tt>/</tt>
  50.240 +                <ul>
  50.241 +                  <li>&hellip;</li>
  50.242 +                </ul>
  50.243 +              </li>
  50.244 +            </ul>
  50.245 +          </li>
  50.246 +          <li>
  50.247 +            <tt>config/</tt>
  50.248 +            <ul>
  50.249 +              <li>
  50.250 +                <tt>development.lua</tt>
  50.251 +              </li>
  50.252 +              <li>
  50.253 +                <tt>production.lua</tt>
  50.254 +              <li>
  50.255 +              <li>
  50.256 +                <i>other_config_name</i><tt>.lua</tt>
  50.257 +              </li>
  50.258 +              <li>&hellip;</li>
  50.259 +            </ul>
  50.260 +          </li>
  50.261 +          <li>
  50.262 +            <tt>db/</tt>
  50.263 +            <ul>
  50.264 +              <li>
  50.265 +                <tt>schema.sql</tt>
  50.266 +              </li>
  50.267 +            </ul>
  50.268 +          </li>
  50.269 +          <li>
  50.270 +            <tt>locale/</tt>
  50.271 +            <ul>
  50.272 +              <li>
  50.273 +                <tt>translations.de.lua</tt>
  50.274 +              </li>
  50.275 +              <li>
  50.276 +                <tt>translations.en.lua</tt>
  50.277 +              </li>
  50.278 +              <li>
  50.279 +                <tt>translations.</tt><i>languagecode</i><tt>.lua</tt>
  50.280 +              </li>
  50.281 +              <li>&hellip;</li>
  50.282 +            </ul>
  50.283 +          </li>
  50.284 +          <li>
  50.285 +            <tt>model/</tt>
  50.286 +            <ul>
  50.287 +              <li>
  50.288 +                <i>model_name</i><tt>.lua</tt>
  50.289 +              </li>
  50.290 +              <li>
  50.291 +                <i>another_model_name</i><tt>.lua</tt>
  50.292 +              </li>
  50.293 +              <li>&hellip;</li>
  50.294 +            </ul>
  50.295 +          </li>
  50.296 +          <li>
  50.297 +            <tt>static/</tt>
  50.298 +            <ul>
  50.299 +              <li>&hellip; (images, javascript, ...)</li>
  50.300 +            </ul>
  50.301 +          </li>
  50.302 +          <li>
  50.303 +            <tt>tmp/</tt> (writable by the web process)
  50.304 +          </li>
  50.305 +        </ul>
  50.306 +      </li>
  50.307 +    </ul>
  50.308 +    <h2>Automatically generated reference for the WebMCP environment</h2>
  50.309 +    <ul>
    51.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    51.2 +++ b/doc/lighttpd.sample.conf	Sun Oct 25 12:00:00 2009 +0100
    51.3 @@ -0,0 +1,60 @@
    51.4 +# Lighttpd modules needed by WebMCP
    51.5 +server.modules += (
    51.6 +  "mod_cgi",
    51.7 +  "mod_alias",
    51.8 +  "mod_setenv",
    51.9 +  "mod_rewrite",
   51.10 +  "mod_redirect",
   51.11 + )
   51.12 +
   51.13 +# Enable CGI-Execution of *.lua files through lua binary
   51.14 +cgi.assign += ( ".lua" => "/__INSERT_LOCAL_FILE_PATH_TO_LUA_BINARY_HERE__/lua" )
   51.15 +
   51.16 +# Connect external URLs to server static files and the webmcp cgi interface
   51.17 +alias.url += (
   51.18 +  "/webmcp-demo/static/" => "/__INSERT_LOCAL_FILE_PATH_TO_DEMO_APPLICATION_HERE__/static/",
   51.19 +  "/webmcp-demo/"        => "/__INSERT_LOCAL_FILE_PATH_TO_WEBMCP_FRAMEWORK_HERE__/cgi-bin/" )
   51.20 +
   51.21 +# Configure environment for demo application    
   51.22 +$HTTP["url"] =~ "^/webmcp-demo/" {
   51.23 +  setenv.add-environment += (
   51.24 +    "WEBMCP_APP_BASEPATH" => "/__INSERT_LOCAL_FILE_PATH_TO_DEMO_APPLICATION_HERE__",
   51.25 +    "WEBMCP_CONFIG_NAME"  => "demo")
   51.26 +}
   51.27 +
   51.28 +# URL beautification
   51.29 +url.rewrite-once += (
   51.30 +
   51.31 +  # do not rewrite static URLs
   51.32 +      "^/webmcp-demo/static/(.*)$" =>
   51.33 +      "/webmcp-demo/static/$1",
   51.34 +
   51.35 +  # base URL
   51.36 +      "^/webmcp-demo/(\?(.*))?$" =>
   51.37 +      "/webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=0&_webmcp_module=index&_webmcp_view=index&$2",
   51.38 +
   51.39 +  # module base URLs
   51.40 +      "^/webmcp-demo/([^/\?]+)/(\?(.*))?$" =>
   51.41 +      "/webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=1&_webmcp_module=$1&_webmcp_view=index&$3",
   51.42 +
   51.43 +  # actions
   51.44 +      "^/webmcp-demo/([^/\?]+)/([^/\.\?]+)(\?(.*))?$" =>
   51.45 +      "/webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=1&_webmcp_module=$1&_webmcp_action=$2&$4",
   51.46 +
   51.47 +  # views without numeric id or string ident
   51.48 +      "^/webmcp-demo/([^/\?]+)/([^/\.\?]+)\.([^/\?]+)(\?(.*))?$" =>
   51.49 +      "/webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=1&_webmcp_module=$1&_webmcp_view=$2&_webmcp_suffix=$3&$5",
   51.50 +
   51.51 +  # views with numeric id or string ident
   51.52 +      "^/webmcp-demo/([^/\?]+)/([^/\?]+)/([^/\.\?]+)\.([^/\?]+)(\?(.*))?$" =>
   51.53 +      "/webmcp-demo/webmcp-wrapper.lua?_webmcp_urldepth=2&_webmcp_module=$1&_webmcp_view=$2&_webmcp_id=$3&_webmcp_suffix=$4&$6",
   51.54 +
   51.55 +)
   51.56 +
   51.57 +# Redirects for URLs without trailing slashes
   51.58 +url.redirect += (
   51.59 +  # base URL without trailing slash
   51.60 +      "^/webmcp-demo$" => "/webmcp-demo/",
   51.61 +  # module base URL without trailing slash
   51.62 +      "^/webmcp-demo/([^/\?]+)$" => "/webmcp-demo/$1/",
   51.63 +)
    52.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    52.2 +++ b/framework/accelerator/Makefile	Sun Oct 25 12:00:00 2009 +0100
    52.3 @@ -0,0 +1,10 @@
    52.4 +include ../../Makefile.options
    52.5 +
    52.6 +webmcp_accelerator.so: webmcp_accelerator.o
    52.7 +	$(LD) $(LDFLAGS) -o webmcp_accelerator.$(SLIB_EXT) webmcp_accelerator.o
    52.8 +
    52.9 +webmcp_accelerator.o: webmcp_accelerator.c
   52.10 +	$(CC) -c $(CFLAGS) -o webmcp_accelerator.o webmcp_accelerator.c
   52.11 +
   52.12 +clean::
   52.13 +	rm -f webmcp_accelerator.so webmcp_accelerator.o
    53.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    53.2 +++ b/framework/accelerator/webmcp_accelerator.c	Sun Oct 25 12:00:00 2009 +0100
    53.3 @@ -0,0 +1,196 @@
    53.4 +#include <lua.h>
    53.5 +#include <lauxlib.h>
    53.6 +#include <stdlib.h>
    53.7 +#include <string.h>
    53.8 +
    53.9 +static int webmcp_encode_html(lua_State *L) {
   53.10 +  const char *input;
   53.11 +  size_t input_len;
   53.12 +  size_t i;
   53.13 +  luaL_Buffer buf;
   53.14 +  input = luaL_checklstring(L, 1, &input_len);
   53.15 +  for (i=0; i<input_len; i++) {
   53.16 +    char c = input[i];
   53.17 +    if ((c == '<') || (c == '>') || (c == '&') || (c == '"')) break;
   53.18 +  }
   53.19 +  if (i == input_len) {
   53.20 +    lua_settop(L, 1);
   53.21 +    return 1;
   53.22 +  }
   53.23 +  luaL_buffinit(L, &buf);
   53.24 +  for (i=0; i<input_len; i++) {
   53.25 +    char c;
   53.26 +    size_t j = i;
   53.27 +    do {
   53.28 +      c = input[j];
   53.29 +      if ((c == '<') || (c == '>') || (c == '&') || (c == '"')) break;
   53.30 +      else j++;
   53.31 +    } while (j<input_len);
   53.32 +    if (j != i) {
   53.33 +      luaL_addlstring(&buf, input+i, j-i);
   53.34 +      i = j;
   53.35 +    }
   53.36 +    if (i<input_len) {
   53.37 +      if      (c == '<') luaL_addstring(&buf, "&lt;");
   53.38 +      else if (c == '>') luaL_addstring(&buf, "&gt;");
   53.39 +      else if (c == '&') luaL_addstring(&buf, "&amp;");
   53.40 +      else if (c == '"') luaL_addstring(&buf, "&quot;");
   53.41 +      else abort();  // should not happen
   53.42 +    }
   53.43 +  }
   53.44 +  luaL_pushresult(&buf);
   53.45 +  return 1;
   53.46 +}
   53.47 +
   53.48 +static int webmcp_slot_put_into(lua_State *L) {
   53.49 +  int argc;
   53.50 +  int i;
   53.51 +  int j;
   53.52 +  luaL_checkany(L, 1);
   53.53 +  argc = lua_gettop(L);
   53.54 +  lua_getglobal(L, "slot");
   53.55 +  lua_getfield(L, -1, "_data");
   53.56 +  lua_pushvalue(L, 1);
   53.57 +  lua_gettable(L, -2);  // get table by reference passed as 1st argument
   53.58 +  lua_getfield(L, -1, "string_fragments");
   53.59 +  j = lua_objlen(L, -1);
   53.60 +  for (i=2; i<=argc; i++) {
   53.61 +    lua_pushvalue(L, i);
   53.62 +    lua_rawseti(L, -2, ++j);
   53.63 +  }
   53.64 +  return 0;
   53.65 +}
   53.66 +
   53.67 +static int webmcp_slot_put(lua_State *L) {
   53.68 +  int argc;
   53.69 +  int i;
   53.70 +  int j;
   53.71 +  argc = lua_gettop(L);
   53.72 +  lua_getglobal(L, "slot");
   53.73 +  lua_getfield(L, -1, "_data");
   53.74 +  lua_getfield(L, -2, "_active_slot");
   53.75 +  lua_gettable(L, -2);
   53.76 +  lua_getfield(L, -1, "string_fragments");
   53.77 +  j = lua_objlen(L, -1);
   53.78 +  for (i=1; i<=argc; i++) {
   53.79 +    lua_pushvalue(L, i);
   53.80 +    lua_rawseti(L, -2, ++j);
   53.81 +  }
   53.82 +  return 0;
   53.83 +}
   53.84 +
   53.85 +static int webmcp_ui_tag(lua_State *L) {
   53.86 +  int tag_given = 0;
   53.87 +  int j;
   53.88 +  lua_settop(L, 1);
   53.89 +  luaL_checktype(L, 1, LUA_TTABLE);
   53.90 +  lua_getglobal(L, "slot");       // 2
   53.91 +  lua_getfield(L, 2, "_data");    // 3
   53.92 +  lua_getfield(L, 2, "_active_slot");
   53.93 +  lua_gettable(L, 3);             // 4
   53.94 +  lua_getfield(L, 4, "string_fragments");  // 5
   53.95 +  lua_getfield(L, 1, "tag");      // 6
   53.96 +  lua_getfield(L, 1, "attr");     // 7
   53.97 +  lua_getfield(L, 1, "content");  // 8
   53.98 +  if (lua_toboolean(L, 7) && !lua_istable(L, 7)) {
   53.99 +    return luaL_error(L,
  53.100 +      "\"attr\" argument for ui.tag{...} must be nil or a table."
  53.101 +    );
  53.102 +  }
  53.103 +  if (lua_toboolean(L, 6)) {
  53.104 +    tag_given = 1;
  53.105 +  } else if (lua_toboolean(L, 7)) {
  53.106 +    lua_pushnil(L);
  53.107 +    if (lua_next(L, 7)) {
  53.108 +      lua_pop(L, 2);
  53.109 +      lua_pushliteral(L, "span");
  53.110 +      lua_replace(L, 6);
  53.111 +      tag_given = 1;
  53.112 +    }
  53.113 +  }
  53.114 +  j = lua_objlen(L, 5);
  53.115 +  if (tag_given) {
  53.116 +    lua_pushliteral(L, "<");
  53.117 +    lua_rawseti(L, 5, ++j);
  53.118 +    lua_pushvalue(L, 6);
  53.119 +    lua_rawseti(L, 5, ++j);
  53.120 +    if (lua_toboolean(L, 7)) {
  53.121 +      for (lua_pushnil(L); lua_next(L, 7); lua_pop(L, 1)) {
  53.122 +        // key at position 9
  53.123 +        // value at position 10
  53.124 +        lua_pushliteral(L, " ");
  53.125 +        lua_rawseti(L, 5, ++j);
  53.126 +        lua_pushvalue(L, 9);
  53.127 +        lua_rawseti(L, 5, ++j);
  53.128 +        lua_pushliteral(L, "=\"");
  53.129 +        lua_rawseti(L, 5, ++j);
  53.130 +        if (!strcmp(lua_tostring(L, 9), "class") && lua_istable(L, 10)) {  // NOTE: lua_tostring(...) is destructive, will cause errors for numeric keys
  53.131 +          lua_getglobal(L, "table");  // 11
  53.132 +          lua_getfield(L, 11, "concat");  // 12
  53.133 +          lua_replace(L, 11);  // 11
  53.134 +          lua_pushvalue(L, 10);  // 12
  53.135 +          lua_pushliteral(L, " ");  // 13
  53.136 +          lua_call(L, 2, 1);  // 11
  53.137 +          lua_rawseti(L, 5, ++j);
  53.138 +        } else {
  53.139 +          lua_pushcfunction(L, webmcp_encode_html);
  53.140 +          lua_pushvalue(L, 10);
  53.141 +          lua_call(L, 1, 1);
  53.142 +          lua_rawseti(L, 5, ++j);
  53.143 +        }
  53.144 +        lua_pushliteral(L, "\"");
  53.145 +        lua_rawseti(L, 5, ++j);
  53.146 +      }
  53.147 +    }
  53.148 +  }
  53.149 +  if (lua_toboolean(L, 8)) {
  53.150 +    if (tag_given) {
  53.151 +      lua_pushliteral(L, ">");
  53.152 +      lua_rawseti(L, 5, ++j);
  53.153 +    }
  53.154 +    if (lua_isfunction(L, 8)) {
  53.155 +      // content function should be on last stack position 8
  53.156 +      lua_call(L, 0, 0);
  53.157 +      // stack is now at position 7, but we don't care
  53.158 +      // we assume that the active slot hasn't been exchanged or resetted
  53.159 +      j = lua_objlen(L, 5);  // but it may include more elements now
  53.160 +    } else {
  53.161 +      lua_pushcfunction(L, webmcp_encode_html);  // 9
  53.162 +      lua_pushvalue(L, 8);  // 10
  53.163 +      lua_call(L, 1, 1);  // 9
  53.164 +      lua_rawseti(L, 5, ++j);
  53.165 +    }
  53.166 +    if (tag_given) {
  53.167 +      lua_pushliteral(L, "</");
  53.168 +      lua_rawseti(L, 5, ++j);
  53.169 +      lua_pushvalue(L, 6);
  53.170 +      lua_rawseti(L, 5, ++j);
  53.171 +      lua_pushliteral(L, ">");
  53.172 +      lua_rawseti(L, 5, ++j);
  53.173 +    }
  53.174 +  } else {
  53.175 +    if (tag_given) {
  53.176 +      lua_pushliteral(L, " />");
  53.177 +      lua_rawseti(L, 5, ++j);
  53.178 +    }
  53.179 +  }
  53.180 +  return 0;
  53.181 +}
  53.182 +
  53.183 +int luaopen_webmcp_accelerator(lua_State *L) {
  53.184 +  lua_settop(L, 0);
  53.185 +  lua_getglobal(L, "encode");  // 1
  53.186 +  lua_pushcfunction(L, webmcp_encode_html);
  53.187 +  lua_setfield(L, 1, "html");
  53.188 +  lua_settop(L, 0);
  53.189 +  lua_getglobal(L, "slot");  // 1
  53.190 +  lua_pushcfunction(L, webmcp_slot_put_into);
  53.191 +  lua_setfield(L, 1, "put_into");
  53.192 +  lua_pushcfunction(L, webmcp_slot_put);
  53.193 +  lua_setfield(L, 1, "put");
  53.194 +  lua_settop(L, 0);
  53.195 +  lua_getglobal(L, "ui");  // 1
  53.196 +  lua_pushcfunction(L, webmcp_ui_tag);
  53.197 +  lua_setfield(L, 1, "tag");
  53.198 +  return 0;
  53.199 +}
    54.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    54.2 +++ b/framework/bin/autodoc.lua	Sun Oct 25 12:00:00 2009 +0100
    54.3 @@ -0,0 +1,216 @@
    54.4 +#!/usr/bin/env lua
    54.5 +
    54.6 +local args = {...}
    54.7 +
    54.8 +if not args[1] or string.match(args[1], "^%-") then
    54.9 +  print("Usage: autodoc.lua srcdir/ > documentation.html")
   54.10 +  os.exit(1)
   54.11 +end
   54.12 +
   54.13 +local entries = {}
   54.14 +
   54.15 +for idx, srcdir in ipairs(args) do
   54.16 +  local find_proc = io.popen('find "' .. srcdir .. '" -name \\*.lua', "r")
   54.17 +  for filename in find_proc:lines() do
   54.18 +    local synopsis, comment, source
   54.19 +    local mode
   54.20 +    local function reset()
   54.21 +      synopsis, comment, source = {}, {}, {}
   54.22 +      mode = "idle"
   54.23 +    end
   54.24 +    reset()
   54.25 +    local function strip(tbl)
   54.26 +      while true do
   54.27 +        local line = tbl[#tbl]
   54.28 +        if line and string.find(line, "^%s*$") then
   54.29 +          tbl[#tbl] = nil
   54.30 +        else
   54.31 +          break
   54.32 +        end
   54.33 +      end
   54.34 +      if #tbl > 0 then
   54.35 +        local min_indent = math.huge
   54.36 +        for idx, line in ipairs(tbl) do
   54.37 +          local spaces = string.match(line, "^(%s*)")
   54.38 +          if min_indent > #spaces then
   54.39 +            min_indent = #spaces
   54.40 +          end
   54.41 +        end
   54.42 +        local pattern_parts = { "^" }
   54.43 +        for i = 1, min_indent do
   54.44 +          pattern_parts[#pattern_parts+1] = "%s"
   54.45 +        end
   54.46 +        pattern_parts[#pattern_parts+1] = "(.-)%s*$"
   54.47 +        local pattern = table.concat(pattern_parts)
   54.48 +        for idx, line in ipairs(tbl) do
   54.49 +          tbl[idx] = string.match(line, pattern)
   54.50 +        end
   54.51 +      end
   54.52 +    end
   54.53 +    local function entry_done()
   54.54 +      if #synopsis > 0 then
   54.55 +        strip(synopsis)
   54.56 +        strip(comment)
   54.57 +        strip(source)
   54.58 +        local stripped_synopsis = {}
   54.59 +        for idx, line in ipairs(synopsis) do
   54.60 +          local stripped_line = string.match(line, "^(.-)%-%-") or line
   54.61 +          stripped_line = string.match(stripped_line, "^(.-)%s*$")
   54.62 +          stripped_synopsis[#stripped_synopsis+1] = stripped_line
   54.63 +        end
   54.64 +        local concatted_synopsis = string.gsub(
   54.65 +          table.concat(stripped_synopsis, " "), "[%s]+", " "
   54.66 +        )
   54.67 +        local func_call = string.match(
   54.68 +          concatted_synopsis, "^[A-Za-z0-9_, ]+= ?(.-) ?$"
   54.69 +        )
   54.70 +        if not func_call then
   54.71 +          func_call = string.match(
   54.72 +            concatted_synopsis,
   54.73 +            "^ ?for[A-Za-z0-9_, ]+in (.-) ? do[ %.]+end ?$"
   54.74 +          )
   54.75 +        end
   54.76 +        if not func_call then
   54.77 +          func_call = string.match(concatted_synopsis, "^ ?(.-) ?$")
   54.78 +        end
   54.79 +        func_call = string.gsub(
   54.80 +          func_call,
   54.81 +          "^([^({]*)[({].*[,;].*[,;].*[,;].*[)}]$",
   54.82 +          function(base)
   54.83 +            return base .. "{ ... }"
   54.84 +          end
   54.85 +        )
   54.86 +        if entries[func_call] then
   54.87 +          error("Multiple occurrences of: " .. func_call)
   54.88 +        end
   54.89 +        entries[func_call] = {
   54.90 +          func_call   = func_call,
   54.91 +          synopsis    = synopsis,
   54.92 +          comment     = comment,
   54.93 +          source      = source
   54.94 +        }
   54.95 +      end
   54.96 +      reset()
   54.97 +    end
   54.98 +    for line in io.lines(filename, "r") do
   54.99 +      local function add_to(tbl)
  54.100 +        if #tbl > 0 or not string.match(line, "^%s*$") then
  54.101 +          tbl[#tbl+1] = line
  54.102 +        end
  54.103 +      end
  54.104 +      if mode == "idle" then
  54.105 +        if string.find(line, "^%s*%-%-%[%[%-%-%s*$") then
  54.106 +          mode = "synopsis"
  54.107 +        end
  54.108 +      elseif mode == "synopsis" then
  54.109 +        if string.find(line, "^%s*$") and #synopsis > 0 then
  54.110 +          mode = "comment"
  54.111 +        elseif string.find(line, "^%s*%-%-]]%-%-%s*$") then
  54.112 +          mode = "source"
  54.113 +        else
  54.114 +          add_to(synopsis)
  54.115 +        end
  54.116 +      elseif mode == "comment" then
  54.117 +        if string.find(line, "^%s*%-%-]]%-%-%s*$") then
  54.118 +          mode = "source"
  54.119 +        else
  54.120 +          add_to(comment)
  54.121 +        end
  54.122 +      elseif mode == "source" then
  54.123 +        if string.find(line, "^%s*%-%-//%-%-%s*$") then
  54.124 +          entry_done()
  54.125 +        else
  54.126 +          add_to(source)
  54.127 +        end
  54.128 +      end
  54.129 +    end
  54.130 +    entry_done()
  54.131 +  end
  54.132 +  find_proc:close()
  54.133 +end
  54.134 +
  54.135 +
  54.136 +function output(...)
  54.137 +  return io.stdout:write(...)
  54.138 +end
  54.139 +
  54.140 +function encode(text)
  54.141 +  return (
  54.142 +    string.gsub(
  54.143 +      text, '[<>&"]',
  54.144 +      function(char)
  54.145 +        if char == '<' then
  54.146 +          return "&lt;"
  54.147 +        elseif char == '>' then
  54.148 +          return "&gt;"
  54.149 +        elseif char == '&' then
  54.150 +          return "&amp;"
  54.151 +        elseif char == '"' then
  54.152 +          return "&quot;"
  54.153 +        end
  54.154 +      end
  54.155 +    )
  54.156 +  )
  54.157 +end
  54.158 +
  54.159 +function output_lines(tbl)
  54.160 +  for idx, line in ipairs(tbl) do
  54.161 +    if idx == 1 then
  54.162 +      output('<pre>')
  54.163 +    end
  54.164 +    local command, comment = string.match(line, "^(.-)(%-%-.*)$")
  54.165 +    if command then
  54.166 +      output(
  54.167 +        encode(command),
  54.168 +        '<span class="autodoc_comment_tail">', encode(comment), '</span>'
  54.169 +      )
  54.170 +    else
  54.171 +      output(encode(line))
  54.172 +    end
  54.173 +    if idx == #tbl then
  54.174 +      output('</pre>')
  54.175 +    end
  54.176 +    output('\n')
  54.177 +  end
  54.178 +end
  54.179 +
  54.180 +keys = {}
  54.181 +for key in pairs(entries) do
  54.182 +  keys[#keys+1] = key
  54.183 +end
  54.184 +table.sort(keys)
  54.185 +for idx, key in ipairs(keys) do
  54.186 +  local entry = entries[key]
  54.187 +  output('<li class="autodoc_entry">\n')
  54.188 +  output(
  54.189 +    '  <div class="short_synopsis"',
  54.190 +    ' onclick="document.getElementById(\'autodoc_details_',
  54.191 +    idx,
  54.192 +    '\').style.display = document.getElementById(\'autodoc_details_',
  54.193 +    idx,
  54.194 +    '\').style.display ? \'\' : \'none\';">\n'
  54.195 +  )
  54.196 +  output('    ', encode(entry.func_call), '\n')
  54.197 +  output('  </div>\n')
  54.198 +  output(
  54.199 +    '  <div id="autodoc_details_',
  54.200 +    idx,
  54.201 +    '" class="autodoc_details" style="display: none;">\n'
  54.202 +  )
  54.203 +  output('    <div class="autodoc_synopsis">\n')
  54.204 +  output_lines(entry.synopsis)
  54.205 +  output('    </div>\n')
  54.206 +  output('    <div class="autodoc_comment">')
  54.207 +  for idx, line in ipairs(entry.comment) do
  54.208 +    output(encode(line))
  54.209 +    if idx < #entry.comment then
  54.210 +      output('<br/>')
  54.211 +    end
  54.212 +  end
  54.213 +  output('    </div>\n')
  54.214 +  output('    <div class="autodoc_source">\n')
  54.215 +  output_lines(entry.source)
  54.216 +  output('    </div>\n')
  54.217 +  output('  </div>\n')
  54.218 +  output('</li>\n')
  54.219 +end
  54.220 \ No newline at end of file
    55.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    55.2 +++ b/framework/bin/langtool.lua	Sun Oct 25 12:00:00 2009 +0100
    55.3 @@ -0,0 +1,188 @@
    55.4 +#!/usr/bin/env lua
    55.5 +
    55.6 +if not pcall(
    55.7 +  function()
    55.8 +    require "extos"
    55.9 +  end
   55.10 +) then
   55.11 +  io.stderr:write('Could not load library "extos".\n')
   55.12 +  io.stderr:write('Hint: Set LUA_CPATH="/path_to_extos_library/?.so;;"\n')
   55.13 +end
   55.14 +
   55.15 +
   55.16 +local args = {...}
   55.17 +
   55.18 +if #args == 0 then
   55.19 +  print()
   55.20 +  print("This program creates translation files by traversing source directories.")
   55.21 +  print()
   55.22 +  print("Two formats are supported: lua files and po files.")
   55.23 +  print("At runtime a lua file is needed.")
   55.24 +  print("For use with po-file editors you may want to create po files first though.")
   55.25 +  print()
   55.26 +  print("Create or update a lua file: langtool.lua dir1/ dir2/ ... <basename>.lua")
   55.27 +  print("Create or update a po file:  langtool.lua dir1/ dir2/ ... <basename>.po")
   55.28 +  print("Convert po file to lua file: langtool.lua <basename>.po <basename>.lua")
   55.29 +  print("Convert lua file to po file: langtool.lua <basename>.lua <basename>.po")
   55.30 +  print()
   55.31 +end
   55.32 +
   55.33 +local in_filename, in_filetype, out_filename, out_filetype
   55.34 +local directories = {}
   55.35 +
   55.36 +for arg_num, arg in ipairs(args) do
   55.37 +  local function arg_error(msg)
   55.38 +    error("Illegal command line argument #" .. arg_num .. ": " .. msg)
   55.39 +  end
   55.40 +  local po = string.match(arg, "^po:(.*)$") or string.match(arg, "^(.*%.po)$")
   55.41 +  local lua = string.match(arg, "^lua:(.*)$") or string.match(arg, "^(.*%.lua)$")
   55.42 +  local filetype
   55.43 +  if po and not lua then filetype = "po"
   55.44 +    filetype = "po"
   55.45 +  elseif lua and not po then filetype = "lua"
   55.46 +    filetype = "lua"
   55.47 +  else
   55.48 +    filetype = "path"
   55.49 +  end
   55.50 +  if filetype == "path" then
   55.51 +    table.insert(directories, arg)
   55.52 +  elseif filetype == "po" or filetype == "lua" then
   55.53 +    if not out_filename then
   55.54 +      out_filename = arg
   55.55 +      out_filetype = filetype
   55.56 +    elseif not in_filename then
   55.57 +      in_filename = out_filename
   55.58 +      in_filetype = out_filetype
   55.59 +      out_filename = arg
   55.60 +      out_filetype = filetype
   55.61 +    else
   55.62 +      arg_error("Only two language files (one input and one output file) can be specified.")
   55.63 +    end
   55.64 +  else
   55.65 +    -- should not happen, as default type is "path"
   55.66 +    arg_error("Type not recognized")
   55.67 +  end
   55.68 +end
   55.69 +
   55.70 +if #directories > 0 and not os.listdir then
   55.71 +  io.stderr:write('Fatal: Cannot traverse directories without "extos" library -> Abort\n')
   55.72 +  os.exit(1)
   55.73 +end
   55.74 +
   55.75 +if out_filename and not in_filename then
   55.76 +  local file = io.open(out_filename, "r")
   55.77 +  if file then
   55.78 +    in_filename = out_filename
   55.79 +    in_filetype = out_filetype
   55.80 +    file:close()
   55.81 +  end
   55.82 +end
   55.83 +
   55.84 +local translations = { }
   55.85 +
   55.86 +local function traverse(path)
   55.87 +  local filenames = os.listdir(path)
   55.88 +  if not filenames then return false end
   55.89 +  for num, filename in ipairs(filenames) do
   55.90 +    if not string.find(filename, "^%.") then
   55.91 +      if string.find(filename, "%.lua$") then
   55.92 +        for line in io.lines(path .. "/" .. filename) do
   55.93 +          -- TODO: exact parsing of comments and escape characters
   55.94 +          for key in string.gmatch(line, "_%(?'([^'\]+)'") do
   55.95 +            if
   55.96 +              key ~= "([^" and
   55.97 +              (not string.find(key, "^%s*%.%.[^%.]")) and
   55.98 +              (not string.find(key, "^%s*,[^,]"))
   55.99 +            then
  55.100 +              translations[key] = false
  55.101 +            end
  55.102 +          end
  55.103 +          for key in string.gmatch(line, '_%(?"([^"\]+)"') do
  55.104 +            if
  55.105 +              key ~= "([^" and
  55.106 +              (not string.find(key, "^%s*%.%.[^%.]")) and
  55.107 +              (not string.find(key, "^%s*,[^,]"))
  55.108 +            then
  55.109 +              translations[key] = false
  55.110 +            end
  55.111 +          end
  55.112 +        end
  55.113 +      end
  55.114 +      traverse(path .. "/" .. filename)
  55.115 +    end
  55.116 +  end
  55.117 +  return true
  55.118 +end
  55.119 +for num, directory in ipairs(directories) do
  55.120 +  io.stderr:write('Parsing files in directory "', directory, '".\n')
  55.121 +  if not traverse(directory) then
  55.122 +    error('Could not read directory "' .. directory .. '".')
  55.123 +  end
  55.124 +end
  55.125 +
  55.126 +local function update_translation(key, value)
  55.127 +  if #directories > 0 then
  55.128 +    if translations[key] ~= nil then translations[key] = value end
  55.129 +  else
  55.130 +    translations[key] = value
  55.131 +  end
  55.132 +end
  55.133 +
  55.134 +if in_filetype == "po" then
  55.135 +  io.stderr:write('Reading translations from po file "', in_filename, '".\n')
  55.136 +  local next_line = io.lines(in_filename)
  55.137 +  for line in next_line do
  55.138 +    if not line then break end
  55.139 +    local key = string.match(line, '^msgid%s*"(.*)"%s*$')
  55.140 +    if key then
  55.141 +      local line = next_line()
  55.142 +      local value = string.match(line, '^msgstr%s*"(.*)"%s*$')
  55.143 +      if not value then
  55.144 +        error("Expected msgstr line in po file.")
  55.145 +      end
  55.146 +      if translations[key] then
  55.147 +        error("Duplicate key '" .. key .. "' in po file.")
  55.148 +      end
  55.149 +      if value == "" then value = false end
  55.150 +      update_translation(key, value)
  55.151 +    end
  55.152 +  end
  55.153 +elseif in_filetype == "lua" then
  55.154 +  io.stderr:write('Reading translations from lua file "', in_filename, '".\n')
  55.155 +  local func = assert(loadfile(in_filename))
  55.156 +  setfenv(func, {})
  55.157 +  local updates = func()
  55.158 +  for key, value in pairs(updates) do
  55.159 +    update_translation(key, value)
  55.160 +  end
  55.161 +end
  55.162 +
  55.163 +local translation_keys = {}
  55.164 +for key in pairs(translations) do
  55.165 +  table.insert(translation_keys, key)
  55.166 +end
  55.167 +table.sort(translation_keys)
  55.168 +
  55.169 +if out_filetype == "po" then
  55.170 +  io.stderr:write('Writing translations to po file "', out_filename, '".\n')
  55.171 +  local file = assert(io.open(out_filename, "w"))
  55.172 +  for num, key in ipairs(translation_keys) do
  55.173 +    local value = translations[key]
  55.174 +    file:write('msgid "', key, '"\nmsgstr "', value or "", '"\n\n')
  55.175 +  end
  55.176 +  io.close(file)
  55.177 +elseif out_filetype == "lua" then
  55.178 +  io.stderr:write('Writing translations to lua file "', out_filename, '".\n')
  55.179 +  local file = assert(io.open(out_filename, "w"))
  55.180 +  file:write("#!/usr/bin/env lua\n", "return {\n")
  55.181 +  for num, key in ipairs(translation_keys) do
  55.182 +    local value = translations[key]
  55.183 +    if value then
  55.184 +      file:write(string.format("[%q] = %q;\n", key, value))
  55.185 +    else
  55.186 +      file:write(string.format("[%q] = false;\n", key))
  55.187 +    end
  55.188 +  end
  55.189 +  file:write("}\n")
  55.190 +  io.close(file)
  55.191 +end
    56.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    56.2 +++ b/framework/bin/recursive-luac	Sun Oct 25 12:00:00 2009 +0100
    56.3 @@ -0,0 +1,14 @@
    56.4 +#!/bin/sh
    56.5 +src=$1
    56.6 +dst=$2
    56.7 +if [ "$src" = "" -o "$dst" = "" ]
    56.8 +then
    56.9 +  echo "Usage:  recursive-luac <source-dir> <destination-dir>"
   56.10 +  exit 1
   56.11 +fi
   56.12 +cp -RL "$src" "$dst"
   56.13 +# TODO: handle whitespace in directory or file names correctly
   56.14 +for file in `find "$dst" -name '*.lua'`
   56.15 +do
   56.16 +  luac -s -o "$file" "$file"
   56.17 +done
    57.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    57.2 +++ b/framework/bin/webmcp_shell	Sun Oct 25 12:00:00 2009 +0100
    57.3 @@ -0,0 +1,10 @@
    57.4 +#!/bin/sh
    57.5 +export WEBMCP_APP_BASEPATH="`pwd`"
    57.6 +export WEBMCP_CONFIG_NAME="$1"
    57.7 +export WEBMCP_INTERACTIVE="yes"
    57.8 +if [ "$WEBMCP_CONFIG_NAME" = "" ]; then
    57.9 +  echo "Error: No config name given." > /dev/stderr
   57.10 +  exit 1
   57.11 +fi
   57.12 +cd "`dirname "$0"`/../cgi-bin"
   57.13 +exec lua -i webmcp.lua
    58.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    58.2 +++ b/framework/cgi-bin/webmcp-wrapper.lua	Sun Oct 25 12:00:00 2009 +0100
    58.3 @@ -0,0 +1,23 @@
    58.4 +#!/usr/bin/env lua
    58.5 +
    58.6 +local func, errmsg = loadfile('webmcp.lua')
    58.7 +
    58.8 +if func then
    58.9 +  local result
   58.10 +  result, errmsg = pcall(func)
   58.11 +  if result then
   58.12 +    errmsg = nil
   58.13 +  end
   58.14 +end
   58.15 +
   58.16 +if errmsg and not (cgi and cgi.data_sent) then
   58.17 +  print('Status: 500 Internal Server Error')
   58.18 +  print('Content-type: text/plain')
   58.19 +  print()
   58.20 +  print('500 Internal Server Error')
   58.21 +  print()
   58.22 +  print(errmsg)
   58.23 +  print()
   58.24 +  print('(caught by webmcp-wrapper.lua)')
   58.25 +  os.exit(1)
   58.26 +end
    59.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    59.2 +++ b/framework/cgi-bin/webmcp.lua	Sun Oct 25 12:00:00 2009 +0100
    59.3 @@ -0,0 +1,449 @@
    59.4 +#!/usr/bin/env lua
    59.5 +
    59.6 +-- include "../lib/" in search path for libraries
    59.7 +do
    59.8 +  package.path = '../lib/?.lua;' .. package.path
    59.9 +  -- find out which file name extension shared libraries have
   59.10 +  local slib_exts = {}
   59.11 +  for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
   59.12 +    slib_exts[ext] = true
   59.13 +  end
   59.14 +  local paths = {}
   59.15 +  for ext in pairs(slib_exts) do
   59.16 +    paths[#paths+1] = "../accelerator/?." .. ext
   59.17 +  end
   59.18 +  for ext in pairs(slib_exts) do
   59.19 +    paths[#paths+1] = "../lib/?." .. ext
   59.20 +  end
   59.21 +  paths[#paths+1] = package.cpath
   59.22 +  package.cpath = table.concat(paths, ";")
   59.23 +end
   59.24 +
   59.25 +-- load os extensions for lua
   59.26 +-- (should happen as soon as possible due to run time measurement)
   59.27 +require 'extos'
   59.28 +
   59.29 +-- load nihil library
   59.30 +require 'nihil'
   59.31 +
   59.32 +-- load random generator library
   59.33 +require 'multirand'
   59.34 +
   59.35 +-- load rocketcgi library and map it to cgi
   59.36 +do
   59.37 +  local option = os.getenv("WEBMCP_INTERACTIVE")
   59.38 +  if option and option ~= "" and option ~= "0" then
   59.39 +    cgi = nil
   59.40 +  else
   59.41 +    require 'rocketcgi'
   59.42 +    cgi = rocketcgi
   59.43 +  end
   59.44 +end
   59.45 +
   59.46 +-- load database access library with object relational mapper
   59.47 +require 'mondelefant'
   59.48 +mondelefant.connection_prototype.error_objects = true
   59.49 +
   59.50 +-- load type system "atom"
   59.51 +require 'atom'
   59.52 +
   59.53 +-- load mondelefant atom connector
   59.54 +require 'mondelefant_atom_connector'
   59.55 +
   59.56 +--[[--
   59.57 +cloned_table =  -- newly generated table
   59.58 +table.new(
   59.59 +  table_or_nil  -- keys of a given table will be copied to the new table
   59.60 +)
   59.61 +
   59.62 +If a table is given, then a cloned table is returned.
   59.63 +If nil is given, then a new empty table is returned.
   59.64 +
   59.65 +--]]--
   59.66 +function table.new(tbl)
   59.67 +  new_tbl = {}
   59.68 +  if tbl then
   59.69 +    for key, value in pairs(tbl) do
   59.70 +      new_tbl[key] = value
   59.71 +    end
   59.72 +  end
   59.73 +  return new_tbl
   59.74 +end
   59.75 +--//--
   59.76 +
   59.77 +--[[--
   59.78 +at_exit(
   59.79 +  func  -- function to be called before the process is ending
   59.80 +)
   59.81 +
   59.82 +Registers a function to be called before the CGI process is exiting.
   59.83 +--]]--
   59.84 +do
   59.85 +  local exit_handlers = {}
   59.86 +  function at_exit(func)
   59.87 +    table.insert(exit_handlers, func)
   59.88 +  end
   59.89 +  function exit(code)
   59.90 +    for i = #exit_handlers, 1, -1 do
   59.91 +      exit_handlers[i]()
   59.92 +    end
   59.93 +    os.exit(code)
   59.94 +  end
   59.95 +end
   59.96 +--//--
   59.97 +
   59.98 +--[[--
   59.99 +app  -- table to store an application state
  59.100 +
  59.101 +'app' is a global table for storing any application state data
  59.102 +--]]--
  59.103 +app = {}
  59.104 +--//--
  59.105 +
  59.106 +--[[--
  59.107 +config  -- table to store application configuration
  59.108 +
  59.109 +'config' is a global table, which can be modified by a config file of an application to modify the behaviour of that application.
  59.110 +--]]--
  59.111 +config = {}
  59.112 +--//--
  59.113 +
  59.114 +-- autoloader system for WebMCP environment "../env/",
  59.115 +-- application environment extensions "$WEBMCP_APP_BASE/env/"
  59.116 +-- and models "$WEBMCP_APP_BASE/model/"
  59.117 +do
  59.118 +  local app_base = os.getenv("WEBMCP_APP_BASEPATH")
  59.119 +  if not app_base then
  59.120 +    error(
  59.121 +      "Failed to initialize autoloader " ..
  59.122 +      "due to unset WEBMCP_APP_BASEPATH environment variable."
  59.123 +    )
  59.124 +  end
  59.125 +  local weakkey_mt = { __mode = "k" }
  59.126 +  local autoloader_category = setmetatable({}, weakkey_mt)
  59.127 +  local autoloader_path     = setmetatable({}, weakkey_mt)
  59.128 +  local autoloader_mt       = {}
  59.129 +  local function install_autoloader(self, category, path)
  59.130 +    autoloader_category[self] = category
  59.131 +    autoloader_path[self]     = path
  59.132 +    setmetatable(self, autoloader_mt)
  59.133 +  end
  59.134 +  local function try_exec(filename)
  59.135 +    local file = io.open(filename, "r")
  59.136 +    if file then
  59.137 +      local filedata = file:read("*a")
  59.138 +      io.close(file)
  59.139 +      local func, errmsg = loadstring(filedata, "=" .. filename)
  59.140 +      if func then
  59.141 +        func()
  59.142 +        return true
  59.143 +      else
  59.144 +        error(errmsg, 0)
  59.145 +      end
  59.146 +    else
  59.147 +      return false
  59.148 +    end
  59.149 +  end
  59.150 +  local function compose_path_string(base, path, key)
  59.151 +    return string.gsub(
  59.152 +      base .. table.concat(path, "/") .. "/" .. key, "/+", "/"
  59.153 +    )
  59.154 +  end
  59.155 +  function autoloader_mt.__index(self, key)
  59.156 +    local category, base_path, merge_base_path, file_key
  59.157 +    local merge = false
  59.158 +    if
  59.159 +      string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
  59.160 +      not string.find(key, "^__")
  59.161 +    then
  59.162 +      category        = "env"
  59.163 +      base_path       = "../env/"
  59.164 +      merge           = true
  59.165 +      merge_base_path = app_base .. "/env/"
  59.166 +      file_key        = key
  59.167 +    elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
  59.168 +      category        = "model"
  59.169 +      base_path       = app_base .. "/model/"
  59.170 +      local first = true
  59.171 +      file_key = string.gsub(key, "[A-Z]",
  59.172 +        function(c)
  59.173 +          if first then
  59.174 +            first = false
  59.175 +            return string.lower(c)
  59.176 +          else
  59.177 +            return "_" .. string.lower(c)
  59.178 +          end
  59.179 +        end
  59.180 +      )
  59.181 +    else
  59.182 +      return
  59.183 +    end
  59.184 +    local required_category = autoloader_category[self]
  59.185 +    if required_category and required_category ~= category then return end
  59.186 +    local path = autoloader_path[self]
  59.187 +    local path_string = compose_path_string(base_path, path, file_key)
  59.188 +    local merge_path_string
  59.189 +    if merge then
  59.190 +      merge_path_string = compose_path_string(
  59.191 +        merge_base_path, path, file_key
  59.192 +      )
  59.193 +    end
  59.194 +    local function try_dir(dirname)
  59.195 +      local dir = io.open(dirname)
  59.196 +      if dir then
  59.197 +        io.close(dir)
  59.198 +        local obj = {}
  59.199 +        local sub_path = {}
  59.200 +        for i, v in ipairs(path) do sub_path[i] = v end
  59.201 +        table.insert(sub_path, file_key)
  59.202 +        install_autoloader(obj, category, sub_path)
  59.203 +        rawset(self, key, obj)
  59.204 +        try_exec(path_string .. "/__init.lua")
  59.205 +        if merge then try_exec(merge_path_string .. "/__init.lua") end
  59.206 +        return true
  59.207 +      else
  59.208 +        return false
  59.209 +      end
  59.210 +    end
  59.211 +    if merge and try_exec(merge_path_string .. ".lua") then
  59.212 +    elseif merge and try_dir(merge_path_string .. "/") then
  59.213 +    elseif try_exec(path_string .. ".lua") then
  59.214 +    elseif try_dir(path_string .. "/") then
  59.215 +    else end
  59.216 +    return rawget(self, key)
  59.217 +  end
  59.218 +  install_autoloader(_G, nil, {})
  59.219 +  try_exec("../env/__init.lua")
  59.220 +end
  59.221 +
  59.222 +-- interactive console mode
  59.223 +if not cgi then
  59.224 +  local config_name = request.get_config_name()
  59.225 +  if config_name then
  59.226 +    execute.config(config_name)
  59.227 +  end
  59.228 +  return
  59.229 +end
  59.230 +
  59.231 +local success, error_info = xpcall(
  59.232 +  function()
  59.233 +
  59.234 +    -- execute configuration file
  59.235 +    do
  59.236 +      local config_name = request.get_config_name()
  59.237 +      if config_name then
  59.238 +        execute.config(config_name)
  59.239 +      end
  59.240 +    end
  59.241 +
  59.242 +    -- restore slots if coming from http redirect
  59.243 +    if cgi.params.tempstore then
  59.244 +      trace.restore_slots{}
  59.245 +      local blob = tempstore.pop(cgi.params.tempstore)
  59.246 +      if blob then slot.restore_all(blob) end
  59.247 +    end
  59.248 +
  59.249 +    local function file_exists(filename)
  59.250 +      local file = io.open(filename, "r")
  59.251 +      if file then
  59.252 +        io.close(file)
  59.253 +        return true
  59.254 +      else
  59.255 +        return false
  59.256 +      end
  59.257 +    end
  59.258 +
  59.259 +    if cgi.params["_webmcp_404"] then
  59.260 +      request.force_absolute_baseurl()
  59.261 +      request.set_status("404 Not Found")
  59.262 +      if request.get_404_route() then
  59.263 +        request.forward(request.get_404_route())
  59.264 +      else
  59.265 +        error("No 404 page set.")
  59.266 +      end
  59.267 +    elseif request.get_action() then
  59.268 +      trace.request{
  59.269 +        module = request.get_module(),
  59.270 +        action = request.get_action()
  59.271 +      }
  59.272 +      if
  59.273 +        request.get_404_route() and
  59.274 +        not file_exists(
  59.275 +          encode.action_file_path{
  59.276 +            module = request.get_module(),
  59.277 +            action = request.get_action()
  59.278 +          }
  59.279 +        )
  59.280 +      then
  59.281 +        request.set_status("404 Not Found")
  59.282 +        request.forward(request.get_404_route())
  59.283 +      else
  59.284 +        if cgi.method ~= "POST" then
  59.285 +          request.set_status("405 Method Not Allowed")
  59.286 +          cgi.add_header("Allow: POST")
  59.287 +          error("Tried to invoke an action with a GET request.")
  59.288 +        end
  59.289 +        local action_status = execute.filtered_action{
  59.290 +          module = request.get_module(),
  59.291 +          action = request.get_action(),
  59.292 +        }
  59.293 +        if not request.is_rerouted() then
  59.294 +          local routing_mode, routing_module, routing_view
  59.295 +          routing_mode   = cgi.params["_webmcp_routing." .. action_status .. ".mode"]
  59.296 +          routing_module = cgi.params["_webmcp_routing." .. action_status .. ".module"]
  59.297 +          routing_view   = cgi.params["_webmcp_routing." .. action_status .. ".view"]
  59.298 +          if not (routing_mode or routing_module or routing_view) then
  59.299 +            action_status = "default"
  59.300 +            routing_mode   = cgi.params["_webmcp_routing.default.mode"]
  59.301 +            routing_module = cgi.params["_webmcp_routing.default.module"]
  59.302 +            routing_view   = cgi.params["_webmcp_routing.default.view"]
  59.303 +          end
  59.304 +          assert(routing_module, "Routing information has no module.")
  59.305 +          assert(routing_view,   "Routing information has no view.")
  59.306 +          if routing_mode == "redirect" then
  59.307 +            local routing_params = {}
  59.308 +            for key, value in pairs(cgi.params) do
  59.309 +              local status, stripped_key = string.match(
  59.310 +                key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$"
  59.311 +              )
  59.312 +              if status == action_status then
  59.313 +                routing_params[stripped_key] = value
  59.314 +              end
  59.315 +            end
  59.316 +            request.redirect{
  59.317 +              module = routing_module,
  59.318 +              view   = routing_view,
  59.319 +              id     = cgi.params["_webmcp_routing." .. action_status .. ".id"],
  59.320 +              params = routing_params
  59.321 +            }
  59.322 +          elseif routing_mode == "forward" then
  59.323 +            request.forward{ module = routing_module, view = routing_view }
  59.324 +          else
  59.325 +            error("Missing or unknown routing mode in request parameters.")
  59.326 +          end
  59.327 +        end
  59.328 +      end
  59.329 +    else
  59.330 +      -- no action
  59.331 +      trace.request{
  59.332 +        module = request.get_module(),
  59.333 +        view   = request.get_view()
  59.334 +      }
  59.335 +      if
  59.336 +        request.get_404_route() and
  59.337 +        not file_exists(
  59.338 +          encode.view_file_path{
  59.339 +            module = request.get_module(),
  59.340 +            view   = request.get_view()
  59.341 +          }
  59.342 +        )
  59.343 +      then
  59.344 +        request.set_status("404 Not Found")
  59.345 +        request.forward(request.get_404_route())
  59.346 +      end
  59.347 +    end
  59.348 +
  59.349 +    if not request.get_redirect_data() then
  59.350 +      request.process_forward()
  59.351 +      execute.filtered_view{
  59.352 +        module = request.get_module(),
  59.353 +        view = request.get_view(),
  59.354 +      }
  59.355 +    end
  59.356 +
  59.357 +    -- force error due to missing absolute base URL until its too late to display error message
  59.358 +    --if request.get_redirect_data() then
  59.359 +    --  request.get_absolute_baseurl()
  59.360 +    --end
  59.361 +
  59.362 +  end,
  59.363 +
  59.364 +  function(errobj)
  59.365 +    return {
  59.366 +      errobj = errobj,
  59.367 +      stacktrace = string.gsub(
  59.368 +        debug.traceback('', 2),
  59.369 +        "^\r?\n?stack traceback:\r?\n?", ""
  59.370 +      )
  59.371 +    }
  59.372 +  end
  59.373 +)
  59.374 +
  59.375 +if not success then trace.error{} end
  59.376 +
  59.377 +-- laufzeitermittlung
  59.378 +trace.exectime{ real = os.monotonic_hires_time(), cpu = os.clock() }
  59.379 +
  59.380 +slot.select('trace', trace.render)  -- render trace information
  59.381 +
  59.382 +local redirect_data = request.get_redirect_data()
  59.383 +
  59.384 +-- log error and switch to error layout, unless success
  59.385 +if not success then
  59.386 +  local errobj     = error_info.errobj
  59.387 +  local stacktrace = error_info.stacktrace
  59.388 +  if not request.get_status() then
  59.389 +    request.set_status("500 Internal Server Error")
  59.390 +  end
  59.391 +  slot.set_layout('system_error')
  59.392 +  slot.select('system_error', function()
  59.393 +    if getmetatable(errobj) == mondelefant.errorobject_metatable then
  59.394 +      slot.put(
  59.395 +        "<p>Database error of class <b>",
  59.396 +        encode.html(errobj.code),
  59.397 +        "</b> occured:<br/><b>",
  59.398 +        encode.html(errobj.message),
  59.399 +        "</b></p>"
  59.400 +      )
  59.401 +    else
  59.402 +      slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>")
  59.403 +    end
  59.404 +    slot.put("<p>Stack trace follows:<br/>")
  59.405 +    slot.put(encode.html_newlines(encode.html(stacktrace)))
  59.406 +    slot.put("</p>")
  59.407 +  end)
  59.408 +elseif redirect_data then
  59.409 +  local redirect_params = {}
  59.410 +  for key, value in pairs(redirect_data.params) do
  59.411 +    redirect_params[key] = value
  59.412 +  end
  59.413 +  local slot_dump = slot.dump_all()
  59.414 +  if slot_dump ~= "" then
  59.415 +    redirect_params.tempstore = tempstore.save(slot_dump)
  59.416 +  end
  59.417 +  cgi.redirect(
  59.418 +    encode.url{
  59.419 +      base   = request.get_absolute_baseurl(),
  59.420 +      module = redirect_data.module,
  59.421 +      view   = redirect_data.view,
  59.422 +      id     = redirect_data.id,
  59.423 +      params = redirect_params
  59.424 +    }
  59.425 +  )
  59.426 +  cgi.send_data()
  59.427 +end
  59.428 +
  59.429 +if not success or not redirect_data then
  59.430 +
  59.431 +  local http_status = request.get_status()
  59.432 +  if http_status then
  59.433 +    cgi.set_status(http_status)
  59.434 +  end
  59.435 +
  59.436 +  -- ajax
  59.437 +  if request.is_ajax() then
  59.438 +    cgi.set_content_type('text/html')
  59.439 +    for i, slot_ident in ipairs{'main', 'actions', 'title', 'topnav', 'sidenav', 'debug', 'notice', 'warning', 'error'} do
  59.440 +      local html = slot.get_content(slot_ident)
  59.441 +      if html then
  59.442 +        cgi.send_data("document.getElementById('" .. slot_ident .. "').innerHTML=" .. encode.json(html or '&nbsp;') .. ";")
  59.443 +      end
  59.444 +    end
  59.445 +  -- oder ganz herkoemmlich
  59.446 +  else
  59.447 +    cgi.set_content_type(slot.get_content_type())
  59.448 +    cgi.send_data(slot.render_layout())
  59.449 +  end
  59.450 +end
  59.451 +
  59.452 +exit()
    60.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    60.2 +++ b/framework/env/__init.lua	Sun Oct 25 12:00:00 2009 +0100
    60.3 @@ -0,0 +1,16 @@
    60.4 +function _(text, replacements)
    60.5 +  local text = locale._get_translation_table()[text] or text
    60.6 +  if replacements then
    60.7 +    return (
    60.8 +      string.gsub(
    60.9 +        text,
   60.10 +        "#{(.-)}",
   60.11 +        function (placeholder)
   60.12 +          return replacements[placeholder]
   60.13 +        end
   60.14 +      )
   60.15 +    )
   60.16 +  else
   60.17 +    return text
   60.18 +  end
   60.19 +end
    61.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    61.2 +++ b/framework/env/charset/__init.lua	Sun Oct 25 12:00:00 2009 +0100
    61.3 @@ -0,0 +1,1 @@
    61.4 +charset._current = "UTF-8"
    62.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    62.2 +++ b/framework/env/charset/data/utf_8.lua	Sun Oct 25 12:00:00 2009 +0100
    62.3 @@ -0,0 +1,10 @@
    62.4 +charset.data.utf_8 = {
    62.5 +  special_chars = {
    62.6 +    nobreak_space  = "\194\160",
    62.7 +    minus_sign     = "\226\136\146",
    62.8 +    inf_sign       = "\226\136\158",
    62.9 +    hyphen_sign    = "\226\128\144",
   62.10 +    nobreak_hyphen = "\226\128\145",
   62.11 +    figure_dash    = "\226\128\146",
   62.12 +  },
   62.13 +}
    63.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    63.2 +++ b/framework/env/charset/get.lua	Sun Oct 25 12:00:00 2009 +0100
    63.3 @@ -0,0 +1,11 @@
    63.4 +--[[--
    63.5 +current_charset =  -- currently selected character set to be used
    63.6 +charset.get()
    63.7 +
    63.8 +Returns the currently selected character set, which is used by the application. Defaults to "UTF-8" unless being changed.
    63.9 +
   63.10 +--]]--
   63.11 +
   63.12 +function charset.get()
   63.13 +  return charset._current
   63.14 +end
    64.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    64.2 +++ b/framework/env/charset/get_data.lua	Sun Oct 25 12:00:00 2009 +0100
    64.3 @@ -0,0 +1,13 @@
    64.4 +--[[--
    64.5 +charset_data =  -- table containing information about the current charset
    64.6 +charset.get_data()
    64.7 +
    64.8 +Returns a table with information about the currently selected charset. See framework/env/charset/data/ for more information.
    64.9 +
   64.10 +--]]--
   64.11 +
   64.12 +function charset.get_data()
   64.13 +  return charset.data[
   64.14 +    string.gsub(string.lower(charset._current), "%-", "_")
   64.15 +  ]
   64.16 +end
    65.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    65.2 +++ b/framework/env/charset/set.lua	Sun Oct 25 12:00:00 2009 +0100
    65.3 @@ -0,0 +1,12 @@
    65.4 +--[[--
    65.5 +charset.set(
    65.6 +  charset_ident  -- identifier of a charset, i.e. "UTF-8"
    65.7 +)
    65.8 +
    65.9 +Changes the currently used charset. Only "UTF-8" is supported, which is already set as a default, so calling this function is not really useful yet.
   65.10 +
   65.11 +--]]--
   65.12 +
   65.13 +function charset.set(charset_ident)
   65.14 +  charset._current = charset_ident
   65.15 +end
    66.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    66.2 +++ b/framework/env/convert/DEPRECATED	Sun Oct 25 12:00:00 2009 +0100
    66.3 @@ -0,0 +1,6 @@
    66.4 +framework/env/convert and framework/env/ui_deprecated are deprecated!
    66.5 +
    66.6 +They will be replaced by:
    66.7 +- framework/env/format
    66.8 +- framework/env/parse
    66.9 +- framework/env/ui
    67.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    67.2 +++ b/framework/env/convert/__init.lua	Sun Oct 25 12:00:00 2009 +0100
    67.3 @@ -0,0 +1,10 @@
    67.4 +convert._type_symbol_mappings = setmetatable({}, { __mode = "k" })
    67.5 +
    67.6 +convert.register_type(atom.boolean,   "boolean")
    67.7 +convert.register_type(atom.string,    "string")
    67.8 +convert.register_type(atom.integer,   "integer")
    67.9 +convert.register_type(atom.number,    "number")
   67.10 +convert.register_type(atom.fraction,  "fraction")
   67.11 +convert.register_type(atom.date,      "date")
   67.12 +convert.register_type(atom.timestamp, "timestamp")
   67.13 +convert.register_type(atom.time,      "time")
   67.14 \ No newline at end of file
    68.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    68.2 +++ b/framework/env/convert/_from_boolean_to_human.lua	Sun Oct 25 12:00:00 2009 +0100
    68.3 @@ -0,0 +1,3 @@
    68.4 +function convert._from_boolean_to_human(value)
    68.5 +  return atom.dump(value)
    68.6 +end
    69.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    69.2 +++ b/framework/env/convert/_from_date_to_human.lua	Sun Oct 25 12:00:00 2009 +0100
    69.3 @@ -0,0 +1,3 @@
    69.4 +function convert._from_date_to_human(value)
    69.5 +  return atom.dump(value)
    69.6 +end
    70.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    70.2 +++ b/framework/env/convert/_from_fraction_to_human.lua	Sun Oct 25 12:00:00 2009 +0100
    70.3 @@ -0,0 +1,3 @@
    70.4 +function convert._from_fraction_to_human(value)
    70.5 +  return atom.dump(value)
    70.6 +end
    71.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    71.2 +++ b/framework/env/convert/_from_human_to_boolean.lua	Sun Oct 25 12:00:00 2009 +0100
    71.3 @@ -0,0 +1,3 @@
    71.4 +function convert._from_human_to_boolean(str)
    71.5 +  return atom.boolean:load(str)
    71.6 +end
    72.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    72.2 +++ b/framework/env/convert/_from_human_to_date.lua	Sun Oct 25 12:00:00 2009 +0100
    72.3 @@ -0,0 +1,3 @@
    72.4 +function convert._from_human_to_date(str)
    72.5 +  return atom.date:load(str)
    72.6 +end
    73.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    73.2 +++ b/framework/env/convert/_from_human_to_fraction.lua	Sun Oct 25 12:00:00 2009 +0100
    73.3 @@ -0,0 +1,3 @@
    73.4 +function convert._from_human_to_fraction(str)
    73.5 +  return atom.fraction:load(str)
    73.6 +end
    74.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    74.2 +++ b/framework/env/convert/_from_human_to_integer.lua	Sun Oct 25 12:00:00 2009 +0100
    74.3 @@ -0,0 +1,3 @@
    74.4 +function convert._from_human_to_integer(str)
    74.5 +  return atom.integer:load(str)
    74.6 +end
    75.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    75.2 +++ b/framework/env/convert/_from_human_to_number.lua	Sun Oct 25 12:00:00 2009 +0100
    75.3 @@ -0,0 +1,3 @@
    75.4 +function convert._from_human_to_number(str)
    75.5 +  return atom.number:load(str)
    75.6 +end
    76.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    76.2 +++ b/framework/env/convert/_from_human_to_string.lua	Sun Oct 25 12:00:00 2009 +0100
    76.3 @@ -0,0 +1,3 @@
    76.4 +function convert._from_human_to_string(str)
    76.5 +  return atom.string:load(str)
    76.6 +end
    77.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    77.2 +++ b/framework/env/convert/_from_human_to_time.lua	Sun Oct 25 12:00:00 2009 +0100
    77.3 @@ -0,0 +1,3 @@
    77.4 +function convert._from_human_to_time(str)
    77.5 +  return atom.time:load(str)
    77.6 +end
    78.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    78.2 +++ b/framework/env/convert/_from_human_to_timestamp.lua	Sun Oct 25 12:00:00 2009 +0100
    78.3 @@ -0,0 +1,3 @@
    78.4 +function convert._from_human_to_timestamp(str)
    78.5 +  return atom.timestamp:load(str)
    78.6 +end
    79.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    79.2 +++ b/framework/env/convert/_from_integer_to_human.lua	Sun Oct 25 12:00:00 2009 +0100
    79.3 @@ -0,0 +1,3 @@
    79.4 +function convert._from_integer_to_human(value)
    79.5 +  return atom.dump(value)
    79.6 +end
    80.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    80.2 +++ b/framework/env/convert/_from_number_to_human.lua	Sun Oct 25 12:00:00 2009 +0100
    80.3 @@ -0,0 +1,3 @@
    80.4 +function convert._from_number_to_human(value)
    80.5 +  return atom.dump(value)
    80.6 +end
    81.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    81.2 +++ b/framework/env/convert/_from_string_to_human.lua	Sun Oct 25 12:00:00 2009 +0100
    81.3 @@ -0,0 +1,3 @@
    81.4 +function convert._from_string_to_human(value)
    81.5 +  return atom.dump(value)
    81.6 +end
    82.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    82.2 +++ b/framework/env/convert/_from_time_to_human.lua	Sun Oct 25 12:00:00 2009 +0100
    82.3 @@ -0,0 +1,3 @@
    82.4 +function convert._from_time_to_human(value)
    82.5 +  return atom.dump(value)
    82.6 +end
    83.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    83.2 +++ b/framework/env/convert/_from_timestamp_to_human.lua	Sun Oct 25 12:00:00 2009 +0100
    83.3 @@ -0,0 +1,3 @@
    83.4 +function convert._from_timestamp_to_human(value)
    83.5 +  return atom.dump(value)
    83.6 +end
    84.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    84.2 +++ b/framework/env/convert/from_human.lua	Sun Oct 25 12:00:00 2009 +0100
    84.3 @@ -0,0 +1,15 @@
    84.4 +function convert.from_human(str, typ)
    84.5 +  if not typ then
    84.6 +    error("Using convert.from_human(...) to convert a human readable string to an internal data type needs a type to be specified as second parameter.")
    84.7 +  end
    84.8 +  if not str then return nil end  -- TODO: decide, if an error should be raised instead
    84.9 +  local type_symbol = convert._type_symbol_mappings[typ]
   84.10 +  if not type_symbol then
   84.11 +    error("Unrecognized type reference passed to convert.from_human(...).")
   84.12 +  end
   84.13 +  local converter = convert["_from_human_to_" .. type_symbol]
   84.14 +  if not converter then
   84.15 +    error("Type reference passed to convert.from_human(...) was recognized, but the converter function is not existent.")
   84.16 +  end
   84.17 +  return converter(str)
   84.18 +end
   84.19 \ No newline at end of file
    85.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    85.2 +++ b/framework/env/convert/register_type.lua	Sun Oct 25 12:00:00 2009 +0100
    85.3 @@ -0,0 +1,3 @@
    85.4 +function convert.register_type(typ, name)
    85.5 +  convert._type_symbol_mappings[typ] = name
    85.6 +end
    86.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    86.2 +++ b/framework/env/convert/to_human.lua	Sun Oct 25 12:00:00 2009 +0100
    86.3 @@ -0,0 +1,21 @@
    86.4 +function convert.to_human(value, typ)
    86.5 +  if value == nil then return "" end  -- TODO: is this correct?
    86.6 +  if typ and not atom.has_type(value, typ) then
    86.7 +    error("The value passed to convert.to_human(...) has not the specified type.")
    86.8 +  end
    86.9 +  local type_symbol
   86.10 +  local value_type = type(value)
   86.11 +  if value_type ~= "table" and value_type ~= "userdata" then
   86.12 +    type_symbol = value_type
   86.13 +  else
   86.14 +    type_symbol = convert._type_symbol_mappings[getmetatable(value)]
   86.15 +  end
   86.16 +  if not type_symbol then
   86.17 +    error("Unrecognized type reference occurred in convert.to_human(...).")
   86.18 +  end
   86.19 +  local converter = convert["_from_" .. type_symbol .. "_to_human"]
   86.20 +  if not converter then
   86.21 +    error("Type reference in convert.from_human(...) could be recognized, but the converter function is not existent.")
   86.22 +  end
   86.23 +  return converter(value)
   86.24 +end
    87.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    87.2 +++ b/framework/env/encode/action_file_path.lua	Sun Oct 25 12:00:00 2009 +0100
    87.3 @@ -0,0 +1,21 @@
    87.4 +--[[--
    87.5 +path =                    -- string containing a path to an action
    87.6 +encode.action_file_path{
    87.7 +  module = module,        -- module name
    87.8 +  action = action         -- action name
    87.9 +}
   87.10 +
   87.11 +This function returns the file path of an action with a given module name and action name. Both module name and action name are mandatory arguments.
   87.12 +
   87.13 +--]]--
   87.14 +
   87.15 +function encode.action_file_path(args)
   87.16 +  return (encode.file_path(
   87.17 +    request.get_app_basepath(),
   87.18 +    'app',
   87.19 +    request.get_app_name(),
   87.20 +    args.module,
   87.21 +    '_action',
   87.22 +    args.action .. '.lua'
   87.23 +  ))
   87.24 +end
    88.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    88.2 +++ b/framework/env/encode/concat_file_path.lua	Sun Oct 25 12:00:00 2009 +0100
    88.3 @@ -0,0 +1,19 @@
    88.4 +--[[--
    88.5 +path =                    -- string containing a (file) path
    88.6 +encode.concat_file_path(
    88.7 +  element1,               -- first part of the path
    88.8 +  element2,               -- second part of the path
    88.9 +  ...                     -- more parts of the path
   88.10 +)
   88.11 +
   88.12 +This function takes a variable amount of strings as arguments and returns a concatenation with slashes as seperators. Multiple slashes following each other directly are transformed into a single slash.
   88.13 +
   88.14 +--]]--
   88.15 +
   88.16 +function encode.concat_file_path(...)
   88.17 +  return (
   88.18 +    string.gsub(
   88.19 +      table.concat({...}, "/"), "/+", "/"
   88.20 +    )
   88.21 +  )
   88.22 +end
    89.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    89.2 +++ b/framework/env/encode/file_path.lua	Sun Oct 25 12:00:00 2009 +0100
    89.3 @@ -0,0 +1,21 @@
    89.4 +--[[--
    89.5 +path =                    -- string containing a (file) path
    89.6 +encode.encode_file_path(
    89.7 +  base_path,
    89.8 +  element1,               -- next part of the path
    89.9 +  element2,               -- next part of the path
   89.10 +  ...
   89.11 +)
   89.12 +
   89.13 +This function does the same as encode.concat_file_path, except that all arguments but the first are encoded using the encode.file_path_element function.
   89.14 +
   89.15 +--]]--
   89.16 +
   89.17 +function encode.file_path(base, ...)  -- base argument is not encoded
   89.18 +  local raw_elements = {...}
   89.19 +  local encoded_elements = {}
   89.20 +  for i = 1, #raw_elements do
   89.21 +    encoded_elements[i] = encode.file_path_element(raw_elements[i])
   89.22 +  end
   89.23 +  return encode.concat_file_path(base, unpack(encoded_elements))
   89.24 +end
    90.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    90.2 +++ b/framework/env/encode/file_path_element.lua	Sun Oct 25 12:00:00 2009 +0100
    90.3 @@ -0,0 +1,22 @@
    90.4 +--[[--
    90.5 +encoded_path_element =     -- string which can't contain evil stuff like "/"
    90.6 +encode.file_path_element(
    90.7 +  path_element             -- string to be encoded
    90.8 +)
    90.9 +
   90.10 +This function is encoding a string in a way that it can be used as a file or directory name, without security risks. See the source for details.
   90.11 +
   90.12 +--]]--
   90.13 +
   90.14 +function encode.file_path_element(path_element)
   90.15 +  return (
   90.16 +    string.gsub(
   90.17 +      string.gsub(
   90.18 +        path_element, "[^0-9A-Za-z_%.-]",
   90.19 +        function(char)
   90.20 +          return string.format("%%%02x", string.byte(char))
   90.21 +        end
   90.22 +      ), "^%.", string.format("%%%%%02x", string.byte("."))
   90.23 +    )
   90.24 +  )
   90.25 +end
    91.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    91.2 +++ b/framework/env/encode/format_info.lua	Sun Oct 25 12:00:00 2009 +0100
    91.3 @@ -0,0 +1,14 @@
    91.4 +--[[--
    91.5 +string =             -- string to be used as __format information
    91.6 +encode.format_info(
    91.7 +  format,            -- name of format function
    91.8 +  params             -- arguments for format function
    91.9 +)
   91.10 +
   91.11 +The string returned by the function can be used as value in a hidden form field with a "__format" suffix. It will be used by the param.* functions to parse a string.
   91.12 +
   91.13 +--]]--
   91.14 +
   91.15 +function encode.format_info(format, params)
   91.16 +  return format .. encode.format_options(params)
   91.17 +end
    92.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    92.2 +++ b/framework/env/encode/format_options.lua	Sun Oct 25 12:00:00 2009 +0100
    92.3 @@ -0,0 +1,40 @@
    92.4 +--[[--
    92.5 +string =                -- part of string to be used as __format information
    92.6 +encode.format_options(
    92.7 +  params                -- arguments for format function
    92.8 +)
    92.9 +
   92.10 +This function is used by encode.format_info(...).
   92.11 +
   92.12 +--]]--
   92.13 +
   92.14 +function encode.format_options(params)
   92.15 +  local params = params or {}
   92.16 +  local result_parts = {}
   92.17 +  for key, value in pairs(params) do
   92.18 +    if type(key) == "string" then
   92.19 +      if string.find(key, "^[A-Za-z][A-Za-z0-9_]*$") then
   92.20 +        table.insert(result_parts, "-")
   92.21 +        table.insert(result_parts, key)
   92.22 +        table.insert(result_parts, "-")
   92.23 +        local t = type(value)
   92.24 +        if t == "string" then
   92.25 +          value = string.gsub(value, "\\", "\\\\")
   92.26 +          value = string.gsub(value, "'", "\\'")
   92.27 +          table.insert(result_parts, "'")
   92.28 +          table.insert(result_parts, value)
   92.29 +          table.insert(result_parts, "'")
   92.30 +        elseif t == "number" then
   92.31 +          table.insert(result_parts, tostring(value))
   92.32 +        elseif t == "boolean" then
   92.33 +          table.insert(result_parts, value and "true" or "false")
   92.34 +        else
   92.35 +          error("Format parameter table contained value of unsupported type " .. t .. ".")
   92.36 +        end
   92.37 +      else
   92.38 +        error('Format parameter table contained invalid key "' .. key .. '".')
   92.39 +      end
   92.40 +    end
   92.41 +  end
   92.42 +  return table.concat(result_parts)
   92.43 +end
    93.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    93.2 +++ b/framework/env/encode/html.lua	Sun Oct 25 12:00:00 2009 +0100
    93.3 @@ -0,0 +1,32 @@
    93.4 +--[[--
    93.5 +result =      -- encoded string
    93.6 +encode.html(
    93.7 +  str         -- original string
    93.8 +)
    93.9 +
   93.10 +This function replaces the special characters '<', '>', '&' and '"' by their HTML entities '&lt;', '&rt;', '&amp;' and '&quot;'.
   93.11 +
   93.12 +NOTE: ACCELERATED FUNCTION
   93.13 +Do not change unless also you also update webmcp_accelerator.c
   93.14 +
   93.15 +--]]--
   93.16 +
   93.17 +function encode.html(text)
   93.18 +  -- TODO: perhaps filter evil control characters?
   93.19 +  return (
   93.20 +    string.gsub(
   93.21 +      text, '[<>&"]',
   93.22 +      function(char)
   93.23 +        if char == '<' then
   93.24 +          return "&lt;"
   93.25 +        elseif char == '>' then
   93.26 +          return "&gt;"
   93.27 +        elseif char == '&' then
   93.28 +          return "&amp;"
   93.29 +        elseif char == '"' then
   93.30 +          return "&quot;"
   93.31 +        end
   93.32 +      end
   93.33 +    )
   93.34 +  )
   93.35 +end
    94.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    94.2 +++ b/framework/env/encode/html_newlines.lua	Sun Oct 25 12:00:00 2009 +0100
    94.3 @@ -0,0 +1,13 @@
    94.4 +--[[--
    94.5 +text_with_br_tags =                -- text with <br/> tags
    94.6 +encode.html_newlines(
    94.7 +  text_with_lf_control_characters  -- text with LF control characters
    94.8 +)
    94.9 +
   94.10 +This function transforms LF control characters (\n) into <br/> tags.
   94.11 +
   94.12 +--]]--
   94.13 +
   94.14 +function encode.html_newlines(text)
   94.15 +  return (string.gsub(text, '\n', '<br/>'))
   94.16 +end
    95.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    95.2 +++ b/framework/env/encode/json.lua	Sun Oct 25 12:00:00 2009 +0100
    95.3 @@ -0,0 +1,34 @@
    95.4 +--[[--
    95.5 +json_string =  -- JavaScript code representing the given datum (with quotes, if needed)
    95.6 +encode.json(
    95.7 +  obj          -- true, false, nil or a number or string
    95.8 +)
    95.9 +
   95.10 +This function encodes any native datatype or atom in JavaScript object notation (JSON).
   95.11 +
   95.12 +--]]--
   95.13 +
   95.14 +function encode.json(obj)
   95.15 +  if obj == nil then
   95.16 +    return "null";
   95.17 +  elseif atom.has_type(obj, atom.boolean) then
   95.18 +    return tostring(obj)
   95.19 +  elseif atom.has_type(obj, atom.number) then
   95.20 +    return tostring(obj)
   95.21 +  else
   95.22 +    return
   95.23 +      "'" ..
   95.24 +      string.gsub(atom.dump(obj), ".",
   95.25 +        function (char)
   95.26 +          if char == "\r" then return "\\r"  end
   95.27 +          if char == "\n" then return "\\n"  end
   95.28 +          if char == "\\" then return "\\\\" end
   95.29 +          if char == "'"  then return "\\'"  end
   95.30 +          if char == "/"  then return "\\/"  end  -- allowed according to RFC4627, needed for </script>
   95.31 +          local byte = string.byte(char)
   95.32 +          if byte < 32 then return string.format("\\u%04x", byte) end
   95.33 +        end
   95.34 +      ) ..
   95.35 +      "'"
   95.36 +  end
   95.37 +end
    96.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    96.2 +++ b/framework/env/encode/mime/atom_token.lua	Sun Oct 25 12:00:00 2009 +0100
    96.3 @@ -0,0 +1,41 @@
    96.4 +function encode.mime.atom_token(str)
    96.5 +  local charset = "UTF-8"  -- TODO: support other charsets via locale system
    96.6 +  if
    96.7 +    string.find(str, "^[0-9A-Za-z!#%$%%&'%*%+%-/=%?%^_`{|}~]+$")
    96.8 +  then
    96.9 +    return str
   96.10 +  elseif
   96.11 +    string.find(str, "^[\t 0-9A-Za-z!#%$%%&'%*%+%-/=%?%^_`{|}~]+$")
   96.12 +  then
   96.13 +    return '"' .. str .. '"'
   96.14 +  elseif
   96.15 +    string.find(str, "^[\t -~]*$")
   96.16 +  then
   96.17 +    local parts = { '"' }
   96.18 +    for char in string.gmatch(str, ".") do
   96.19 +      if char == '"' or char == "\\" then
   96.20 +        parts[#parts+1] = "\\"
   96.21 +      end
   96.22 +      parts[#parts+1] = char
   96.23 +    end
   96.24 +    parts[#parts+1] = '"'
   96.25 +    return table.concat(parts)
   96.26 +  else
   96.27 +    local parts = { "=?", charset, "?Q?" }
   96.28 +    for char in string.gmatch(str, ".") do
   96.29 +      local byte = string.byte(char)
   96.30 +      if string.find(char, "^[0-9A-Za-z%.%-]$") then
   96.31 +        parts[#parts+1] = char
   96.32 +      else
   96.33 +        local byte = string.byte(char)
   96.34 +        if byte == 32 then
   96.35 +          parts[#parts+1] = "_"
   96.36 +        else
   96.37 +          parts[#parts+1] = string.format("=%02X", byte)
   96.38 +        end
   96.39 +      end
   96.40 +    end
   96.41 +    parts[#parts+1] = "?="
   96.42 +    return table.concat(parts)
   96.43 +  end
   96.44 +end
   96.45 \ No newline at end of file
    97.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    97.2 +++ b/framework/env/encode/mime/base64.lua	Sun Oct 25 12:00:00 2009 +0100
    97.3 @@ -0,0 +1,45 @@
    97.4 +local alphabet = {
    97.5 +  "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
    97.6 +  "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
    97.7 +  "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
    97.8 +  "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
    97.9 +  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
   97.10 +}
   97.11 +
   97.12 +function encode.mime.base64(str)
   97.13 +  local parts = {}
   97.14 +  local pos = 1
   97.15 +  local block_count = 0
   97.16 +  while pos <= #str do
   97.17 +    local s = string.sub(str, pos, pos + 2)
   97.18 +    local n = 0
   97.19 +    for i = 1, 3 do
   97.20 +      n = n * 256
   97.21 +      if i <= #s then
   97.22 +        n = n + string.byte(string.sub(s, i, i))
   97.23 +      end
   97.24 +    end
   97.25 +    parts[#parts+1] = alphabet[math.floor(n / 262144) + 1]
   97.26 +    parts[#parts+1] = alphabet[math.floor(n / 4096) % 64 + 1]
   97.27 +    if #s > 1 then
   97.28 +      parts[#parts+1] = alphabet[math.floor(n / 64) % 64 + 1]
   97.29 +    else
   97.30 +      parts[#parts+1] = "="
   97.31 +    end
   97.32 +    if #s > 2 then
   97.33 +      parts[#parts+1] = alphabet[n % 64 + 1]
   97.34 +    else
   97.35 +      parts[#parts+1] = "="
   97.36 +    end
   97.37 +    block_count = block_count + 1
   97.38 +    if block_count == 19 then
   97.39 +      parts[#parts+1] = "\r\n"
   97.40 +      block_count = 0
   97.41 +    end
   97.42 +    pos = pos + #s
   97.43 +  end
   97.44 +  if block_count > 0 then
   97.45 +    parts[#parts+1] = "\r\n"
   97.46 +  end
   97.47 +  return table.concat(parts)
   97.48 +end
    98.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    98.2 +++ b/framework/env/encode/mime/mail.lua	Sun Oct 25 12:00:00 2009 +0100
    98.3 @@ -0,0 +1,66 @@
    98.4 +local un  = encode.mime.unstructured_header_line
    98.5 +local mbl = encode.mime.mailbox_list_header_line
    98.6 +
    98.7 +local function encode_container(parts, container)
    98.8 +  if container.multipart then
    98.9 +    local boundary
   98.10 +    boundary = "BOUNDARY--" .. multirand.string(
   98.11 +      24,
   98.12 +      "123456789BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz"
   98.13 +    )
   98.14 +    parts[#parts+1] = "Content-Type: "
   98.15 +    parts[#parts+1] = "multipart/"
   98.16 +    parts[#parts+1] = container.multipart
   98.17 +    parts[#parts+1] = "; boundary="
   98.18 +    parts[#parts+1] = boundary
   98.19 +    parts[#parts+1] = "\r\n\r\nMIME multipart\r\n"  -- last \r\n optional
   98.20 +    for idx, sub_container in ipairs(container) do
   98.21 +      parts[#parts+1] = "\r\n--"
   98.22 +      parts[#parts+1] = boundary
   98.23 +      parts[#parts+1] = "\r\n"
   98.24 +      encode_container(parts, sub_container)
   98.25 +    end
   98.26 +    parts[#parts+1] = "\r\n--"
   98.27 +    parts[#parts+1] = boundary
   98.28 +    parts[#parts+1] = "--\r\n"
   98.29 +  else
   98.30 +    parts[#parts+1] = "Content-Type: "
   98.31 +    parts[#parts+1] = container.content_type or "text/plain"
   98.32 +    parts[#parts+1] = "\r\n"
   98.33 +    if container.content_id then
   98.34 +      parts[#parts+1] = "Content-ID: <"
   98.35 +      parts[#parts+1] = container.content_id
   98.36 +      parts[#parts+1] = ">\r\n"
   98.37 +    end
   98.38 +    if container.attachment_filename then
   98.39 +      parts[#parts+1] = "Content-Disposition: attachment; filename="
   98.40 +      parts[#parts+1] = encode.mime.atom_token(
   98.41 +        container.attachment_filename
   98.42 +      )
   98.43 +      parts[#parts+1] = "\r\n"
   98.44 +    end
   98.45 +    if container.binary then
   98.46 +      parts[#parts+1] = "Content-Transfer-Encoding: base64\r\n\r\n"
   98.47 +      parts[#parts+1] = encode.mime.base64(container.content)
   98.48 +    else
   98.49 +      parts[#parts+1] =
   98.50 +        "Content-Transfer-Encoding: quoted-printable\r\n\r\n"
   98.51 +      parts[#parts+1] =
   98.52 +        encode.mime.quoted_printable_text_content(container.content)
   98.53 +    end
   98.54 +  end
   98.55 +end
   98.56 +
   98.57 +function encode.mime.mail(args)
   98.58 +  local parts = {}
   98.59 +  parts[#parts+1] = mbl("From",     args.from)
   98.60 +  parts[#parts+1] = mbl("Sender",   args.sender)
   98.61 +  parts[#parts+1] = mbl("Reply-To", args.reply_to)
   98.62 +  parts[#parts+1] = mbl("To",       args.to)
   98.63 +  parts[#parts+1] = mbl("Cc",       args.cc)
   98.64 +  parts[#parts+1] = mbl("Bcc",      args.bcc)
   98.65 +  parts[#parts+1] = un("Subject", args.subject)
   98.66 +  parts[#parts+1] = "MIME-Version: 1.0\r\n"
   98.67 +  encode_container(parts, args)
   98.68 +  return table.concat(parts)
   98.69 +end
    99.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    99.2 +++ b/framework/env/encode/mime/mailbox_list_header_line.lua	Sun Oct 25 12:00:00 2009 +0100
    99.3 @@ -0,0 +1,58 @@
    99.4 +function encode.mime.mailbox_list_header_line(key, mailboxes)
    99.5 +  local mailboxes = mailboxes
    99.6 +  if not mailboxes then
    99.7 +    mailboxes = {}
    99.8 +  elseif type(mailboxes) == "string" then
    99.9 +    mailboxes = { mailboxes }
   99.10 +  elseif mailboxes.address or mailboxes.name then
   99.11 +    table.insert(mailboxes, mailboxes)
   99.12 +  end
   99.13 +  local indentation = ""
   99.14 +  for i = 1, #key + #(": ") do
   99.15 +    indentation = indentation .. " "
   99.16 +  end
   99.17 +  local parts = { key, ": " }
   99.18 +  local first = true
   99.19 +  for idx, mailbox in ipairs(mailboxes) do
   99.20 +    local name, address
   99.21 +    if type(mailbox) == "string" then
   99.22 +      name, address = nil, mailbox
   99.23 +    else
   99.24 +      name, address = mailbox.name, mailbox.address
   99.25 +    end
   99.26 +    if address and not string.find(
   99.27 +      address,
   99.28 +      "^[0-9A-Za-z!#%$%%&'%*%+%-/=%?%^_`{|}~%.]+" ..
   99.29 +      "@[0-9A-Za-z!#%$%%&'%*%+%-/=%?%^_`{|}~%.]+$"
   99.30 +    )
   99.31 +    then
   99.32 +      if name then
   99.33 +        name, address = name .. " <" .. address .. ">", nil
   99.34 +      else
   99.35 +        name, address = address, nil
   99.36 +      end
   99.37 +    end
   99.38 +    if name or address then
   99.39 +      if not first then
   99.40 +        parts[#parts+1] = ",\r\n"
   99.41 +        parts[#parts+1] = indentation
   99.42 +      end
   99.43 +      if name then
   99.44 +        parts[#parts+1] = encode.mime.atom_token(name)
   99.45 +        parts[#parts+1] = " <"
   99.46 +        if address then
   99.47 +          parts[#parts+1] = address
   99.48 +        end
   99.49 +        parts[#parts+1] = ">"
   99.50 +      else
   99.51 +        parts[#parts+1] = address
   99.52 +      end
   99.53 +      first = false
   99.54 +    end
   99.55 +  end
   99.56 +  if first then
   99.57 +    return ""
   99.58 +  end
   99.59 +  parts[#parts+1] = "\r\n"
   99.60 +  return table.concat(parts)
   99.61 +end
   100.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   100.2 +++ b/framework/env/encode/mime/quoted_printable_text_content.lua	Sun Oct 25 12:00:00 2009 +0100
   100.3 @@ -0,0 +1,31 @@
   100.4 +function encode.mime.quoted_printable_text_content(str)
   100.5 +  local parts = {}
   100.6 +  for str_part in string.gmatch(str, "[^\r\n]+[\r\n]*") do
   100.7 +    local line, extra_gap = string.match(str_part, "^([^\r\n]+)\r?\n?(.*)$")
   100.8 +    local line_length = 0
   100.9 +    for char in string.gmatch(line, ".") do
  100.10 +      if string.find(char, "^[\t -<>-~]$") then
  100.11 +        if line_length + 1 > 75 then
  100.12 +          parts[#parts+1] = "=\r\n"
  100.13 +          line_length = 0
  100.14 +        end
  100.15 +        parts[#parts+1] = char
  100.16 +        line_length = line_length + 1
  100.17 +      else
  100.18 +        if line_length + 3 > 75 then
  100.19 +          parts[#parts+1] = "=\r\n"
  100.20 +          line_length = 0
  100.21 +        end
  100.22 +        parts[#parts+1] = string.format("=%02X", string.byte(char))
  100.23 +        line_length = line_length + 3
  100.24 +      end
  100.25 +    end
  100.26 +    for i = 1, #extra_gap do
  100.27 +      if string.sub(extra_gap, i, i) == "\n" then
  100.28 +        parts[#parts+1] = "\r\n"
  100.29 +      end
  100.30 +    end
  100.31 +    parts[#parts+1] = "\r\n"
  100.32 +  end
  100.33 +  return table.concat(parts)
  100.34 +end
   101.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   101.2 +++ b/framework/env/encode/mime/unstructured_header_line.lua	Sun Oct 25 12:00:00 2009 +0100
   101.3 @@ -0,0 +1,71 @@
   101.4 +function encode.mime.unstructured_header_line(key, value)
   101.5 +  if not value then
   101.6 +    return ""
   101.7 +  end
   101.8 +  local charset = "UTF-8"  -- TODO: support other charsets
   101.9 +  local key_length = #key + #(": ")
  101.10 +  if string.find(value, "^[\t -~]*$") then
  101.11 +    local need_encoding = false
  101.12 +    local parts = { key, ": " }
  101.13 +    local line_length = key_length
  101.14 +    local first_line = true
  101.15 +    for spaced_word in string.gmatch(value, "[\t ]*[^\t ]*") do
  101.16 +      if #spaced_word + line_length > 76 then
  101.17 +        if first_line or #spaced_word > 76 then
  101.18 +          need_encoding = true
  101.19 +          break
  101.20 +        end
  101.21 +        parts[#parts+1] = "\r\n"
  101.22 +        line_length = 0
  101.23 +      end
  101.24 +      parts[#parts+1] = spaced_word
  101.25 +      line_length = line_length + #spaced_word
  101.26 +      first_line = false
  101.27 +    end
  101.28 +    if not need_encoding then
  101.29 +      parts[#parts+1] = "\r\n"
  101.30 +      return table.concat(parts)
  101.31 +    end
  101.32 +    charset = "US-ASCII"
  101.33 +  end
  101.34 +  local parts = { key, ": " }
  101.35 +  local line_length
  101.36 +  local opening = "=?" .. charset .. "?Q?"
  101.37 +  local closing = "?="
  101.38 +  local indentation = ""
  101.39 +  for i = 1, key_length do
  101.40 +    indentation = indentation .. " "
  101.41 +  end
  101.42 +  local open = false
  101.43 +  for char in string.gmatch(value, ".") do
  101.44 +    local encoded_char
  101.45 +    if string.find(char, "^[0-9A-Za-z%.%-]$") then
  101.46 +      encoded_char = char
  101.47 +    else
  101.48 +      local byte = string.byte(char)
  101.49 +      if byte == 32 then
  101.50 +        encoded_char = "_"
  101.51 +      else
  101.52 +        encoded_char = string.format("=%02X", byte)
  101.53 +      end
  101.54 +    end
  101.55 +    if open and line_length + #encoded_char > 76 then
  101.56 +      parts[#parts+1] = closing
  101.57 +      parts[#parts+1] = "\r\n"
  101.58 +      parts[#parts+1] = indentation
  101.59 +      open = false
  101.60 +    end
  101.61 +    if not open then
  101.62 +      parts[#parts+1] = opening
  101.63 +      line_length = key_length + #opening + #closing
  101.64 +      open = true
  101.65 +    end
  101.66 +    parts[#parts+1] = encoded_char
  101.67 +    line_length = line_length + #encoded_char
  101.68 +  end
  101.69 +  if open then
  101.70 +    parts[#parts+1] = "?="
  101.71 +  end
  101.72 +  parts[#parts+1] = "\r\n"
  101.73 +  return table.concat(parts)
  101.74 +end
   102.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   102.2 +++ b/framework/env/encode/url.lua	Sun Oct 25 12:00:00 2009 +0100
   102.3 @@ -0,0 +1,89 @@
   102.4 +--[[--
   102.5 +url_string =              -- a string containing an URL
   102.6 +encode.url{
   102.7 +  external  = external,   -- external URL (instead of specifying base, module, etc. below)
   102.8 +  base      = base,       -- optional string containing a base URL of a WebMCP application
   102.9 +  static    = static,     -- an URL relative to the static file directory
  102.10 +  module    = module,     -- a module name of the WebMCP application
  102.11 +  view      = view,       -- a view name of the WebMCP application
  102.12 +  action    = action,     -- an action name of the WebMCP application
  102.13 +  id        = id,         -- optional id to be passed to the view or action to select a particular data record
  102.14 +  params    = params      -- optional parameters to be passed to the view or action
  102.15 +}
  102.16 +
  102.17 +This function creates URLs to external locations, to static files within the WebMCP application or to a certain view or action inside a module.
  102.18 +
  102.19 +--]]--
  102.20 +
  102.21 +function encode.url(args)
  102.22 +  local external  = args.external
  102.23 +  local base      = args.base or request.get_relative_baseurl()
  102.24 +  local static    = args.static
  102.25 +  local module    = args.module
  102.26 +  local view      = args.view
  102.27 +  local action    = args.action
  102.28 +  local id        = args.id
  102.29 +  local params    = args.params or {}
  102.30 +  local result    = {}
  102.31 +  local id_as_param = false
  102.32 +  local function add(...)
  102.33 +    for i = 1, math.huge do
  102.34 +      local v = select(i, ...)
  102.35 +      if v == nil then break end
  102.36 +      result[#result+1] = v
  102.37 +    end
  102.38 +  end
  102.39 +  if external then
  102.40 +    add(external)
  102.41 +  else
  102.42 +    add(base)
  102.43 +    if not string.find(base, "/$") then
  102.44 +      add("/")
  102.45 +    end
  102.46 +    if static then
  102.47 +      add("static/")
  102.48 +      add(static)
  102.49 +    elseif module or view or action or id then
  102.50 +      assert(module, "Module not specified.")
  102.51 +      add(encode.url_part(module), "/")
  102.52 +      if view and not action then
  102.53 +        local view_base, view_suffix = string.match(
  102.54 +          view,
  102.55 +          "^([^.]*)(.*)$"
  102.56 +        )
  102.57 +        add(encode.url_part(view_base))
  102.58 +        if args.id then
  102.59 +          add("/", encode.url_part(id))
  102.60 +        end
  102.61 +        if view_suffix == "" then
  102.62 +          add(".html")
  102.63 +        else
  102.64 +          add(view_suffix)  -- view_suffix includes dot as first character
  102.65 +        end
  102.66 +      elseif action and not view then
  102.67 +        add(encode.url_part(action))
  102.68 +        id_as_param = true
  102.69 +      elseif view and action then
  102.70 +        error("Both a view and an action was specified.")
  102.71 +      end
  102.72 +    end
  102.73 +    do
  102.74 +      local new_params = request.get_perm_params()
  102.75 +      for key, value in pairs(params) do
  102.76 +        new_params[key] = value
  102.77 +      end
  102.78 +      params = new_params
  102.79 +    end
  102.80 +  end
  102.81 +  if next(params) ~= nil or (id and id_as_param) then
  102.82 +    add("?")
  102.83 +    if id and id_as_param then
  102.84 +      add("_webmcp_id=", encode.url_part(id), "&")
  102.85 +    end
  102.86 +    for key, value in pairs(params) do
  102.87 +      add(encode.url_part(key), "=", encode.url_part(value), "&")
  102.88 +    end
  102.89 +    result[#result] = nil  -- remove last '&' or '?'
  102.90 +  end
  102.91 +  return table.concat(result)
  102.92 +end
   103.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   103.2 +++ b/framework/env/encode/url_part.lua	Sun Oct 25 12:00:00 2009 +0100
   103.3 @@ -0,0 +1,21 @@
   103.4 +--[[--
   103.5 +url_encoded_string =  -- URL-encoded string
   103.6 +encode.url_part(
   103.7 +  obj                 -- any native datatype or atom
   103.8 +)
   103.9 +
  103.10 +This function encodes any native datatype or atom in a way that it can be placed inside an URL. It is first dumped with atom.dump(...) and then url-encoded.
  103.11 +
  103.12 +--]]--
  103.13 +
  103.14 +function encode.url_part(obj)
  103.15 +  return (
  103.16 +    string.gsub(
  103.17 +      atom.dump(obj),
  103.18 +      "[^0-9A-Za-z_%.~-]",
  103.19 +      function (char)
  103.20 +        return string.format("%%%02x", string.byte(char))
  103.21 +      end
  103.22 +    )
  103.23 +  )
  103.24 +end
   104.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   104.2 +++ b/framework/env/encode/view_file_path.lua	Sun Oct 25 12:00:00 2009 +0100
   104.3 @@ -0,0 +1,17 @@
   104.4 +--[[--
   104.5 +path =                  -- string containing a path to a view
   104.6 +encode.view_file_path{
   104.7 +  module = module,      -- module name
   104.8 +  view   = view         -- view name
   104.9 +}
  104.10 +
  104.11 +This function returns the file path of a view with a given module name and view name. Both module name and view name are mandatory arguments.
  104.12 +
  104.13 +--]]--
  104.14 +
  104.15 +function encode.view_file_path(args)
  104.16 +  return (encode.file_path(
  104.17 +    request.get_app_basepath(),
  104.18 +    'app', request.get_app_name(), args.module, args.view .. '.lua'
  104.19 +  ))
  104.20 +end
   105.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   105.2 +++ b/framework/env/execute/__init.lua	Sun Oct 25 12:00:00 2009 +0100
   105.3 @@ -0,0 +1,1 @@
   105.4 +execute._wrap_stack = {}
   106.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   106.2 +++ b/framework/env/execute/_add_filters_by_path.lua	Sun Oct 25 12:00:00 2009 +0100
   106.3 @@ -0,0 +1,25 @@
   106.4 +function execute._add_filters_by_path(filter_list, ...)
   106.5 +  local full_path     = encode.file_path(request.get_app_basepath(), "app", ...)
   106.6 +  local relative_path = encode.file_path("", ...)
   106.7 +  local filter_names = os.listdir(full_path)
   106.8 +  if filter_names then
   106.9 +    table.sort(filter_names)  -- not really neccessary, due to sorting afterwards
  106.10 +    for i, filter_name in ipairs(filter_names) do
  106.11 +      if string.find(filter_name, "%.lua$") then
  106.12 +        if filter_list[filter_name] then
  106.13 +          error('More than one filter is named "' .. filter_name .. '".')
  106.14 +        end
  106.15 +        table.insert(filter_list, filter_name)
  106.16 +        filter_list[filter_name] = function()
  106.17 +          trace.enter_filter{
  106.18 +            path = encode.file_path(relative_path, filter_name)
  106.19 +          }
  106.20 +          execute.file_path{
  106.21 +            file_path = encode.file_path(full_path, filter_name)
  106.22 +          }
  106.23 +          trace.execution_return()
  106.24 +        end
  106.25 +      end
  106.26 +    end
  106.27 +  end
  106.28 +end
   107.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   107.2 +++ b/framework/env/execute/action.lua	Sun Oct 25 12:00:00 2009 +0100
   107.3 @@ -0,0 +1,29 @@
   107.4 +--[[--
   107.5 +action_status =     -- status code returned by the action (a string)
   107.6 +execute.action{
   107.7 +  module = module,  -- module name of the action to be executed
   107.8 +  action = action,  -- name of the action to be executed
   107.9 +  id     = id,      -- id to be returned by param.get_id(...) during execution
  107.10 +  params = params   -- parameters to be returned by param.get(...) during execution
  107.11 +}
  107.12 +
  107.13 +Executes an action without associated filters.
  107.14 +This function is only used by execute.filtered_action{...}, which itself is only used by the webmcp.lua file in the cgi-bin/ directory.
  107.15 +
  107.16 +--]]--
  107.17 +
  107.18 +function execute.action(args)
  107.19 +  local module = args.module
  107.20 +  local action = args.action
  107.21 +  trace.enter_action{ module = module, action = action }
  107.22 +  local action_status = execute.file_path{
  107.23 +    file_path = encode.file_path(
  107.24 +      request.get_app_basepath(),
  107.25 +      'app', request.get_app_name(), module, '_action', action .. '.lua'
  107.26 +    ),
  107.27 +    id     = args.id,
  107.28 +    params = args.params
  107.29 +  }
  107.30 +  trace.execution_return{ status = action_status }
  107.31 +  return action_status
  107.32 +end
   108.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   108.2 +++ b/framework/env/execute/config.lua	Sun Oct 25 12:00:00 2009 +0100
   108.3 @@ -0,0 +1,19 @@
   108.4 +--[[--
   108.5 +execute.config(
   108.6 +  name           -- name of the configuration to be loaded
   108.7 +)
   108.8 +
   108.9 +Executes a configuration file of the application.
  108.10 +This function is only used by by the webmcp.lua file in the cgi-bin/ directory.
  108.11 +
  108.12 +--]]--
  108.13 +
  108.14 +function execute.config(name)
  108.15 +  trace.enter_config{ name = name }
  108.16 +  execute.file_path{
  108.17 +    file_path = encode.file_path(
  108.18 +      request.get_app_basepath(), 'config', name .. '.lua'
  108.19 +    )
  108.20 +  }
  108.21 +  trace.execution_return()
  108.22 +end
   109.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   109.2 +++ b/framework/env/execute/file_path.lua	Sun Oct 25 12:00:00 2009 +0100
   109.3 @@ -0,0 +1,36 @@
   109.4 +--[[--
   109.5 +status_code =             -- status code returned by the executed lua file (a string)
   109.6 +execute.file_path{
   109.7 +  file_path = file_path,  -- path to a lua source or byte-code file
   109.8 +  id        = id,         -- id to be returned by param.get_id(...) during execution
   109.9 +  params    = params      -- parameters to be returned by param.get(...) during execution
  109.10 +}
  109.11 +
  109.12 +This function loads and executes a lua file specified by a given path. If an "id" or "params" are provided, the param.get_id(...) and/or param.get(...) functions will return the provided values during execution. The lua routine must return true, false, nil or a string. In case of true or nil, this function returns the string "ok", in case of false, this function returns "error", otherwise the string returned by the lua routine will be returned by this function as well.
  109.13 +
  109.14 +--]]--
  109.15 +
  109.16 +function execute.file_path(args)
  109.17 +  local file_path = args.file_path
  109.18 +  local id        = args.id
  109.19 +  local params    = args.params
  109.20 +  local func, load_errmsg = loadfile(file_path)
  109.21 +  if not func then
  109.22 +    error('Could not load file "' .. file_path .. '": ' .. load_errmsg)
  109.23 +  end
  109.24 +  if id or params then
  109.25 +    param.exchange(id, params)
  109.26 +  end
  109.27 +  local result = func()
  109.28 +  if result == nil or result == true then
  109.29 +    result = 'ok'
  109.30 +  elseif result == false then
  109.31 +    result = 'error'
  109.32 +  elseif type(result) ~= "string" then
  109.33 +    error("Unexpected type of result: " .. type(result))
  109.34 +  end
  109.35 +  if id or params then
  109.36 +    param.restore()
  109.37 +  end
  109.38 +  return result
  109.39 +end
   110.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   110.2 +++ b/framework/env/execute/filtered_action.lua	Sun Oct 25 12:00:00 2009 +0100
   110.3 @@ -0,0 +1,39 @@
   110.4 +--[[--
   110.5 +action_status =     -- status code returned by the action (a string)
   110.6 +execute.filtered_action{
   110.7 +  module = module,  -- module name of the action to be executed
   110.8 +  action = action,  -- name of the action to be executed
   110.9 +  id     = id,      -- id to be returned by param.get_id(...) during execution
  110.10 +  params = params   -- parameters to be returned by param.get(...) during execution
  110.11 +}
  110.12 +
  110.13 +Executes an action with associated filters.
  110.14 +This function is only used by by the webmcp.lua file in the cgi-bin/ directory.
  110.15 +
  110.16 +--]]--
  110.17 +
  110.18 +function execute.filtered_action(args)
  110.19 +  local filters = {}
  110.20 +  local function add_by_path(...)
  110.21 +    execute._add_filters_by_path(filters, ...)
  110.22 +  end
  110.23 +  add_by_path("_filter")
  110.24 +  add_by_path("_filter_action")
  110.25 +  add_by_path(request.get_app_name(), "_filter")
  110.26 +  add_by_path(request.get_app_name(), "_filter_action")
  110.27 +  add_by_path(request.get_app_name(), args.module, "_filter")
  110.28 +  add_by_path(request.get_app_name(), args.module, "_filter_action")
  110.29 +  table.sort(filters)
  110.30 +  for idx, filter_name in ipairs(filters) do
  110.31 +    filters[idx] = filters[filter_name]
  110.32 +    filters[filter_name] = nil
  110.33 +  end
  110.34 +  local result
  110.35 +  execute.multi_wrapped(
  110.36 +    filters,
  110.37 +    function()
  110.38 +      result = execute.action(args)
  110.39 +    end
  110.40 +  )
  110.41 +  return result
  110.42 +end
   111.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   111.2 +++ b/framework/env/execute/filtered_view.lua	Sun Oct 25 12:00:00 2009 +0100
   111.3 @@ -0,0 +1,34 @@
   111.4 +--[[--
   111.5 +execute.filtered_view{
   111.6 +  module = module,  -- module name of the view to be executed
   111.7 +  view   = view     -- name of the view to be executed
   111.8 +}
   111.9 +
  111.10 +Executes a view with associated filters.
  111.11 +This function is only used by by the webmcp.lua file in the cgi-bin/ directory.
  111.12 +
  111.13 +--]]--
  111.14 +
  111.15 +function execute.filtered_view(args)
  111.16 +  local filters = {}
  111.17 +  local function add_by_path(...)
  111.18 +    execute._add_filters_by_path(filters, ...)
  111.19 +  end
  111.20 +  add_by_path("_filter")
  111.21 +  add_by_path("_filter_view")
  111.22 +  add_by_path(request.get_app_name(), "_filter")
  111.23 +  add_by_path(request.get_app_name(), "_filter_view")
  111.24 +  add_by_path(request.get_app_name(), args.module, "_filter")
  111.25 +  add_by_path(request.get_app_name(), args.module, "_filter_view")
  111.26 +  table.sort(filters)
  111.27 +  for idx, filter_name in ipairs(filters) do
  111.28 +    filters[idx] = filters[filter_name]
  111.29 +    filters[filter_name] = nil
  111.30 +  end
  111.31 +  execute.multi_wrapped(
  111.32 +    filters,
  111.33 +    function()
  111.34 +      execute.view(args)
  111.35 +    end
  111.36 +  )
  111.37 +end
   112.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   112.2 +++ b/framework/env/execute/inner.lua	Sun Oct 25 12:00:00 2009 +0100
   112.3 @@ -0,0 +1,20 @@
   112.4 +--[[--
   112.5 +execute.inner()
   112.6 +
   112.7 +It is MANDATORY to call this function once in each filter of a WebMCP application. Calling execute.inner() calls the next filter in the filter chain, or the view or action, if there are no more filters following. Code executed BEFORE calling this function is executed BEFORE the view or action, while code executed AFTER calling this function is executed AFTER the view of action.
   112.8 +
   112.9 +--]]--
  112.10 +
  112.11 +function execute.inner()
  112.12 +  local stack = execute._wrap_stack
  112.13 +  local pos = #stack
  112.14 +  if pos == 0 then
  112.15 +    error("Unexpected call of execute.inner().")
  112.16 +  end
  112.17 +  local inner_func = stack[pos]
  112.18 +  if not inner_func then
  112.19 +    error("Repeated call of execute.inner().")
  112.20 +  end
  112.21 +  stack[pos] = false
  112.22 +  inner_func()
  112.23 +end
   113.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   113.2 +++ b/framework/env/execute/multi_wrapped.lua	Sun Oct 25 12:00:00 2009 +0100
   113.3 @@ -0,0 +1,26 @@
   113.4 +--[[--
   113.5 +execute.multi_wrapped(
   113.6 +  wrapper_funcs,   -- multiple wrapper functions (i.e. filters)
   113.7 +  inner_func       -- inner function (i.e. an action or view)
   113.8 +)
   113.9 +
  113.10 +This function does the same as execute.wrapped(...), but with multiple wrapper functions, instead of just one wrapper function. It is used by execute.filtered_view{...} and execute.filtered_action{...} to wrap multiple filters around the view or action.
  113.11 +
  113.12 +--]]--
  113.13 +
  113.14 +function execute.multi_wrapped(wrapper_funcs, inner_func)
  113.15 +  local function wrapped_execution(pos)
  113.16 +    local wrapper_func = wrapper_funcs[pos]
  113.17 +    if wrapper_func then
  113.18 +      return execute.wrapped(
  113.19 +        wrapper_func,
  113.20 +        function()
  113.21 +          wrapped_execution(pos+1)
  113.22 +        end
  113.23 +      )
  113.24 +    else
  113.25 +      return inner_func()
  113.26 +    end
  113.27 +  end
  113.28 +  return wrapped_execution(1)
  113.29 +end
   114.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   114.2 +++ b/framework/env/execute/view.lua	Sun Oct 25 12:00:00 2009 +0100
   114.3 @@ -0,0 +1,26 @@
   114.4 +--[[--
   114.5 +execute.view{
   114.6 +  module = module,  -- module name of the view to be executed
   114.7 +  view   = view,    -- name of the view to be executed
   114.8 +  id     = id,      -- id to be returned by param.get_id(...) during execution
   114.9 +  params = params   -- parameters to be returned by param.get(...) during execution
  114.10 +}
  114.11 +
  114.12 +Executes a view directly (without associated filters).
  114.13 +
  114.14 +--]]--
  114.15 +
  114.16 +function execute.view(args)
  114.17 +  local module = args.module
  114.18 +  local view = args.view
  114.19 +  trace.enter_view{ module = module, view = view }
  114.20 +  execute.file_path{
  114.21 +    file_path = encode.file_path(
  114.22 +      request.get_app_basepath(),
  114.23 +      'app', request.get_app_name(), module, view .. '.lua'
  114.24 +    ),
  114.25 +    id     = args.id,
  114.26 +    params = args.params
  114.27 +  }
  114.28 +  trace.execution_return()
  114.29 +end
   115.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   115.2 +++ b/framework/env/execute/wrapped.lua	Sun Oct 25 12:00:00 2009 +0100
   115.3 @@ -0,0 +1,26 @@
   115.4 +--[[--
   115.5 +execute.wrapped(
   115.6 +  wrapper_func,   -- function with an execute.inner() call inside
   115.7 +  inner_func      -- function which is executed when execute.inner() is called
   115.8 +)
   115.9 +
  115.10 +This function takes two functions as argument. The first function is executed, and must contain one call of execute.inner() during its execution. When execute.inner() is called, the second function is executed. After the second function finished, program flow continues in the first function.
  115.11 +
  115.12 +--]]--
  115.13 +
  115.14 +function execute.wrapped(wrapper_func, inner_func)
  115.15 +  if
  115.16 +    type(wrapper_func) ~= "function" or
  115.17 +    type(inner_func) ~= "function"
  115.18 +  then
  115.19 +    error("Two functions need to be passed to execute.wrapped(...).")
  115.20 +  end
  115.21 +  local stack = execute._wrap_stack
  115.22 +  local pos = #stack + 1
  115.23 +  stack[pos] = inner_func
  115.24 +  wrapper_func()
  115.25 +  -- if stack[pos] then
  115.26 +  --   error("Wrapper function did not call execute.inner().")
  115.27 +  -- end
  115.28 +  stack[pos] = nil
  115.29 +end
   116.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   116.2 +++ b/framework/env/format/boolean.lua	Sun Oct 25 12:00:00 2009 +0100
   116.3 @@ -0,0 +1,30 @@
   116.4 +--[[--
   116.5 +text =                      -- human text representation of the boolean
   116.6 +format.boolean(
   116.7 +  value,                    -- true, false or nil
   116.8 +  {
   116.9 +    true_as  = true_text,   -- text representing true
  116.10 +    false_as = false_text,  -- text representing false
  116.11 +    nil_as   = nil_text     -- text representing nil
  116.12 +  }
  116.13 +)
  116.14 +
  116.15 +Returns a human readable text representation of a boolean value. Additional parameters should be given, unless you like the defaults for false and true, which are "0" and "1".
  116.16 +
  116.17 +--]]--
  116.18 +
  116.19 +function format.boolean(value, options)
  116.20 +  local options = options or {}
  116.21 +  local true_text  = options.true_as or "Yes"  -- TODO: localization?
  116.22 +  local false_text = options.false_as or "No"  -- TODO: localization?
  116.23 +  local nil_text   = options.nil_as or ""
  116.24 +  if value == nil then
  116.25 +    return nil_text
  116.26 +  elseif value == false then
  116.27 +    return false_text
  116.28 +  elseif value == true then
  116.29 +    return true_text
  116.30 +  else
  116.31 +    error("Value passed to format.boolean(...) is neither a boolean nor nil.")
  116.32 +  end
  116.33 +end
   117.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   117.2 +++ b/framework/env/format/currency.lua	Sun Oct 25 12:00:00 2009 +0100
   117.3 @@ -0,0 +1,50 @@
   117.4 +--[[--
   117.5 +text =
   117.6 +format.currency(
   117.7 +  value,
   117.8 +  {
   117.9 +    nil_as                 = nil_text,                -- text to be returned for a nil value
  117.10 +    digits                 = digits,                  -- number of digits before the decimal point
  117.11 +    currency_precision     = currency_precision,      -- number of digits after decimal point
  117.12 +    currency_prefix        = currency_prefix,         -- prefix string, i.e. "$ "
  117.13 +    currency_decimal_point = currency_decimal_point,  -- string to be used as decimal point
  117.14 +    currency_suffix        = currency_suffix,         -- suffix string, i.e. " EUR"
  117.15 +    hide_unit              = hide_unit,               -- hide the currency unit, if true
  117.16 +    decimal_point          = decimal_point            -- used instead of 'currency_decimal_point', if 'hide_unit' is true
  117.17 +  }
  117.18 +)
  117.19 +
  117.20 +Formats a (floating point) number or a fraction as a decimal number. If a 'digits' option is set, the number of digits before the decimal point is increased up to the given count by preceding it with zeros. The digits after the decimal point are adjusted by the 'precision' parameter. The 'decimal_shift' parameter is useful, when fixed precision decimal numbers are stored as integers, as the given value will be divided by 10 to the power of the 'decimal_shift' value prior to formatting. Setting 'decimal_shift' to true will use the 'precision' value as 'decimal_shift'.
  117.21 +
  117.22 +--]]--
  117.23 +
  117.24 +function format.currency(value, options)
  117.25 +  local options = table.new(options)
  117.26 +  local prefix
  117.27 +  local suffix
  117.28 +  if options.hide_unit then
  117.29 +    prefix = ""
  117.30 +    suffix = ""
  117.31 +    options.decimal_point =
  117.32 +      options.decimal_point or locale.get("decimal_point")
  117.33 +    options.precision =
  117.34 +      options.currency_precision or locale.get("currency_precision") or 2
  117.35 +  elseif
  117.36 +    options.currency_prefix or options.currency_suffix or
  117.37 +    options.currency_precision or options.currency_decimal_point
  117.38 +  then
  117.39 +    prefix                = options.currency_prefix or ''
  117.40 +    suffix                = options.currency_suffix or ''
  117.41 +    options.decimal_point = options.currency_decimal_point
  117.42 +    options.precision     = options.currency_precision or 2
  117.43 +  else
  117.44 +    prefix                = locale.get("currency_prefix") or ''
  117.45 +    suffix                = locale.get("currency_suffix") or ''
  117.46 +    options.decimal_point = locale.get("currency_decimal_point")
  117.47 +    options.precision     = locale.get("currency_precision") or 2
  117.48 +  end
  117.49 +  if value == nil then
  117.50 +    return options.nil_as or ''
  117.51 +  end
  117.52 +  return prefix .. format.decimal(value, options) .. suffix
  117.53 +end
   118.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   118.2 +++ b/framework/env/format/date.lua	Sun Oct 25 12:00:00 2009 +0100
   118.3 @@ -0,0 +1,51 @@
   118.4 +--[[--
   118.5 +text =                 -- text with the value formatted as a date, according to the locale settings
   118.6 +format.date(
   118.7 +  value,               -- a date, a timestamp or nil
   118.8 +  {
   118.9 +    nil_as = nil_text  -- text to be returned for a nil value
  118.10 +  }
  118.11 +)
  118.12 +
  118.13 +Formats a date or timestamp as a date, according to the locale settings.
  118.14 +
  118.15 +--]]--
  118.16 +
  118.17 +function format.date(value, options)
  118.18 +  local options = options or {}
  118.19 +  if value == nil then
  118.20 +    return options.nil_as or ""
  118.21 +  end
  118.22 +  if not (
  118.23 +    atom.has_type(value, atom.date) or
  118.24 +    atom.has_type(value, atom.timestamp)
  118.25 +  ) then
  118.26 +    error("Value passed to format.date(...) is neither a date, a timestamp, nor nil.")
  118.27 +  end
  118.28 +  if value.invalid then
  118.29 +    return "invalid"
  118.30 +  end
  118.31 +  local result = locale.get("date_format") or "YYYY-MM-DD"
  118.32 +  result = string.gsub(result, "YYYY", function()
  118.33 +    return format.decimal(value.year, { digits = 4 })
  118.34 +  end)
  118.35 +  result = string.gsub(result, "YY", function()
  118.36 +    return format.decimal(value.year % 100, { digits = 2 })
  118.37 +  end)
  118.38 +  result = string.gsub(result, "Y", function()
  118.39 +    return format.decimal(value.year)
  118.40 +  end)
  118.41 +  result = string.gsub(result, "MM", function()
  118.42 +    return format.decimal(value.month, { digits = 2 })
  118.43 +  end)
  118.44 +  result = string.gsub(result, "M", function()
  118.45 +    return format.decimal(value.month)
  118.46 +  end)
  118.47 +  result = string.gsub(result, "DD", function()
  118.48 +    return format.decimal(value.day, { digits = 2 })
  118.49 +  end)
  118.50 +  result = string.gsub(result, "D", function()
  118.51 +    return format.decimal(value.day)
  118.52 +  end)
  118.53 +  return result
  118.54 +end
   119.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   119.2 +++ b/framework/env/format/decimal.lua	Sun Oct 25 12:00:00 2009 +0100
   119.3 @@ -0,0 +1,89 @@
   119.4 +--[[--
   119.5 +text =                             -- text with the value formatted as decimal number
   119.6 +format.decimal(
   119.7 +  value,                           -- a number, a fraction or nil
   119.8 +  {
   119.9 +    nil_as        = nil_text,      -- text to be returned for a nil value
  119.10 +    digits        = digits,        -- digits before decimal point
  119.11 +    precision     = precision,     -- digits after decimal point
  119.12 +    decimal_shift = decimal_shift  -- divide the value by 10^decimal_shift (setting true uses precision)
  119.13 +  }
  119.14 +)
  119.15 +
  119.16 +Formats a (floating point) number or a fraction as a decimal number. If a 'digits' option is set, the number of digits before the decimal point is increased up to the given count by preceding it with zeros. The digits after the decimal point are adjusted by the 'precision' parameter. The 'decimal_shift' parameter is useful, when fixed precision decimal numbers are stored as integers, as the given value will be divided by 10 to the power of the 'decimal_shift' value prior to formatting. Setting 'decimal_shift' to true will copy the value for 'precision'.
  119.17 +
  119.18 +--]]--
  119.19 +
  119.20 +function format.decimal(value, options)
  119.21 +  -- TODO: more features
  119.22 +  local options = options or {}
  119.23 +  local special_chars = charset.get_data().special_chars
  119.24 +  local f
  119.25 +  if value == nil then
  119.26 +    return options.nil_as or ""
  119.27 +  elseif atom.has_type(value, atom.number) then
  119.28 +    f = value
  119.29 +  elseif atom.has_type(value, atom.fraction) then
  119.30 +    f = value.float
  119.31 +  else
  119.32 +    error("Value passed to format.decimal(...) is neither a number nor a fraction nor nil.")
  119.33 +  end
  119.34 +  local digits = options.digits
  119.35 +  local precision = options.precision or 0
  119.36 +  local decimal_shift = options.decimal_shift or 0
  119.37 +  if decimal_shift == true then
  119.38 +    decimal_shift = precision
  119.39 +  end
  119.40 +  f = f / 10 ^ decimal_shift
  119.41 +  local negative
  119.42 +  local absolute
  119.43 +  if f < 0 then
  119.44 +    absolute = -f
  119.45 +    negative = true
  119.46 +  else
  119.47 +    absolute = f
  119.48 +    negative = false
  119.49 +  end
  119.50 +  absolute = absolute + 0.5 / 10 ^ precision
  119.51 +  local int = math.floor(absolute)
  119.52 +  if not atom.is_integer(int) then
  119.53 +    if f > 0 then
  119.54 +      return "+" .. special_chars.inf_sign
  119.55 +    elseif f < 0 then
  119.56 +      return minus_sign .. special_chars.inf_sign
  119.57 +    else
  119.58 +      return "NaN"
  119.59 +    end
  119.60 +  end
  119.61 +  local int_str = tostring(int)
  119.62 +  if digits then
  119.63 +    while #int_str < digits do
  119.64 +      int_str = "0" .. int_str
  119.65 +    end
  119.66 +  end
  119.67 +  if precision > 0 then
  119.68 +    local decimal_point =
  119.69 +      options.decimal_point or
  119.70 +      locale.get('decimal_point') or '.'
  119.71 +    local frac_str = tostring(math.floor((absolute - int) * 10 ^ precision))
  119.72 +    while #frac_str < precision do
  119.73 +      frac_str = "0" .. frac_str
  119.74 +    end
  119.75 +    assert(#frac_str == precision, "Assertion failed in format.float(...).")  -- should not happen
  119.76 +    if negative then
  119.77 +      return special_chars.minus_sign .. int_str .. decimal_point .. frac_str
  119.78 +    elseif options.show_plus then
  119.79 +      return "+" .. int_str .. decimal_point .. frac_str
  119.80 +    else
  119.81 +      return int_str .. decimal_point .. frac_str
  119.82 +    end
  119.83 +  else
  119.84 +    if negative then
  119.85 +      return special_chars.minus_sign .. int
  119.86 +    elseif options.show_plus then
  119.87 +      return "+" .. int_str
  119.88 +    else
  119.89 +      return int_str
  119.90 +    end
  119.91 +  end
  119.92 +end
   120.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   120.2 +++ b/framework/env/format/percentage.lua	Sun Oct 25 12:00:00 2009 +0100
   120.3 @@ -0,0 +1,35 @@
   120.4 +--[[--
   120.5 +text =                             -- text with the value formatted as a percentage
   120.6 +format.percentage(
   120.7 +  value,                           -- a number, a fraction or nil
   120.8 +  {
   120.9 +    nil_as        = nil_text       -- text to be returned for a nil value
  120.10 +    digits        = digits,        -- digits before decimal point (of the percentage value)
  120.11 +    precision     = precision,     -- digits after decimal point (of the percentage value)
  120.12 +    decimal_shift = decimal_shift  -- divide the value by 10^decimal_shift (setting true uses precision + 2)
  120.13 +  }
  120.14 +)
  120.15 +
  120.16 +Formats a number or fraction as a percentage.
  120.17 +
  120.18 +--]]--
  120.19 +
  120.20 +function format.percentage(value, options)
  120.21 +  local options = table.new(options)
  120.22 +  local f
  120.23 +  if value == nil then
  120.24 +    return options.nil_as or ""
  120.25 +  elseif atom.has_type(value, atom.number) then
  120.26 +    f = value
  120.27 +  elseif atom.has_type(value, atom.fraction) then
  120.28 +    f = value.float
  120.29 +  else
  120.30 +    error("Value passed to format.percentage(...) is neither a number nor a fraction nor nil.")
  120.31 +  end
  120.32 +  options.precision = options.precision or 0
  120.33 +  if options.decimal_shift == true then
  120.34 +    options.decimal_shift = options.precision + 2
  120.35 +  end
  120.36 +  local suffix = options.hide_unit and "" or " %"
  120.37 +  return format.decimal(f * 100, options) .. suffix
  120.38 +end
   121.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   121.2 +++ b/framework/env/format/string.lua	Sun Oct 25 12:00:00 2009 +0100
   121.3 @@ -0,0 +1,21 @@
   121.4 +--[[--
   121.5 +text =                 -- a string
   121.6 +format.string(
   121.7 +  value,               -- any value where tostring(value) gives a reasonable result
   121.8 +  {
   121.9 +    nil_as = nil_text  -- text to be returned for a nil value
  121.10 +  }
  121.11 +)
  121.12 +
  121.13 +Formats a value as a text by calling tostring(...), unless the value is nil, in which case the text returned is chosen by the 'nil_as' option.
  121.14 +
  121.15 +--]]--
  121.16 +
  121.17 +function format.string(str, options)
  121.18 +  local options = options or {}
  121.19 +  if str == nil then
  121.20 +    return options.nil_as or ""
  121.21 +  else
  121.22 +    return tostring(str)
  121.23 +  end
  121.24 +end
   122.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   122.2 +++ b/framework/env/format/time.lua	Sun Oct 25 12:00:00 2009 +0100
   122.3 @@ -0,0 +1,60 @@
   122.4 +--[[--
   122.5 +text =                 -- text with the value formatted as a time, according to the locale settings
   122.6 +format.time(
   122.7 +  value,               -- a time, a timestamp or nil
   122.8 +  {
   122.9 +    nil_as = nil_text  -- text to be returned for a nil value
  122.10 +  }
  122.11 +)
  122.12 +
  122.13 +Formats a time or timestamp as a time, according to the locale settings.
  122.14 +
  122.15 +--]]--
  122.16 +
  122.17 +function format.time(value, options)
  122.18 +  local options = options or {}
  122.19 +  if value == nil then
  122.20 +    return options.nil_as or ""
  122.21 +  end
  122.22 +  if not (
  122.23 +    atom.has_type(value, atom.time) or
  122.24 +    atom.has_type(value, atom.timestamp)
  122.25 +  ) then
  122.26 +    error("Value passed to format.time(...) is neither a time, a timestamp, nor nil.")
  122.27 +  end
  122.28 +  if value.invalid then
  122.29 +    return "invalid"
  122.30 +  end
  122.31 +  local result = locale.get("time_format") or "HH:MM{:SS}"
  122.32 +  if options.hide_seconds then
  122.33 +    result = string.gsub(result, "{[^{|}]*}", "")
  122.34 +  else
  122.35 +    result = string.gsub(result, "{([^|]*)}", "%1")
  122.36 +  end
  122.37 +  local am_pm
  122.38 +  local hour = value.hour
  122.39 +  result = string.gsub(result, "{([^{}]*)|([^{}]*)}", function(am, pm)
  122.40 +    if hour > 12 then
  122.41 +      am_pm = pm
  122.42 +    else
  122.43 +      am_pm = am
  122.44 +    end
  122.45 +    return "{|}"
  122.46 +  end)
  122.47 +  if am_pm and hour > 12 then
  122.48 +    hour = hour - 12
  122.49 +  end
  122.50 +  result = string.gsub(result, "HH", function()
  122.51 +    return format.decimal(hour, { digits = 2 })
  122.52 +  end)
  122.53 +  result = string.gsub(result, "MM", function()
  122.54 +    return format.decimal(value.minute, { digits = 2 })
  122.55 +  end)
  122.56 +  result = string.gsub(result, "SS", function()
  122.57 +    return format.decimal(value.second, { digits = 2 })
  122.58 +  end)
  122.59 +  if am_pm then
  122.60 +    result = string.gsub(result, "{|}", am_pm)
  122.61 +  end
  122.62 +  return result
  122.63 +end
   123.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   123.2 +++ b/framework/env/format/timestamp.lua	Sun Oct 25 12:00:00 2009 +0100
   123.3 @@ -0,0 +1,19 @@
   123.4 +--[[--
   123.5 +text =                 -- text with the given timestamp value formatted according to the locale settings
   123.6 +format.timestamp(
   123.7 +  value,               -- a timestamp or nil
   123.8 +  {
   123.9 +    nil_as = nil_text  -- text to be returned for a nil value
  123.10 +  }
  123.11 +)
  123.12 +
  123.13 +Formats a timestamp according to the locale settings.
  123.14 +
  123.15 +--]]--
  123.16 +
  123.17 +function format.timestamp(value, options)
  123.18 +  if value == nil then
  123.19 +    return options.nil_as or ""
  123.20 +  end
  123.21 +  return format.date(value, options) .. " " .. format.time(value, options)
  123.22 +end
   124.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   124.2 +++ b/framework/env/locale/__init.lua	Sun Oct 25 12:00:00 2009 +0100
   124.3 @@ -0,0 +1,4 @@
   124.4 +locale._current_data = {}
   124.5 +locale._translation_tables = {}
   124.6 +locale._empty_translation_table = {}
   124.7 +
   125.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   125.2 +++ b/framework/env/locale/_get_translation_table.lua	Sun Oct 25 12:00:00 2009 +0100
   125.3 @@ -0,0 +1,23 @@
   125.4 +function locale._get_translation_table()
   125.5 +  local language_code = locale.get("lang")
   125.6 +  if language_code then
   125.7 +    if type(language_code) ~= "string" then
   125.8 +      error('locale.get("lang") does not return a string.')
   125.9 +    end
  125.10 +    local translation_table = locale._translation_tables[language_code]
  125.11 +    if translation_table then 
  125.12 +      return translation_table
  125.13 +    end
  125.14 +    local filename = encode.file_path(request.get_app_basepath(), "locale", "translations." .. language_code .. ".lua")
  125.15 +    local func = assert(loadfile(filename))
  125.16 +    setfenv(func, {})
  125.17 +    translation_table = func()
  125.18 +    if type(translation_table) ~= "table" then
  125.19 +      error("Translation file did not return a table.")
  125.20 +    end
  125.21 +    locale._translation_tables[language_code] = translation_table
  125.22 +    return translation_table
  125.23 +  else
  125.24 +    return locale._empty_translation_table
  125.25 +  end
  125.26 +end
   126.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   126.2 +++ b/framework/env/locale/do_with.lua	Sun Oct 25 12:00:00 2009 +0100
   126.3 @@ -0,0 +1,22 @@
   126.4 +--[[--
   126.5 +locale.do_with(
   126.6 +  locale_options,  -- table with locale information (as if passed to locale.set(...))
   126.7 +  function()
   126.8 +    ...            -- code to be executed with the given locale settings
   126.9 +  end
  126.10 +)
  126.11 +
  126.12 +This function executes code with temporarily changed locale settings. See locale.set(...) for correct usage of 'locale_options'.
  126.13 +
  126.14 +--]]--
  126.15 +
  126.16 +function locale.do_with(locale_options, block)
  126.17 +  local old_data = {}
  126.18 +  for key, value in pairs(locale._current_data) do
  126.19 +    old_data[key] = value
  126.20 +  end
  126.21 +  locale.set(locale_options)
  126.22 +  block()
  126.23 +  old_data.reset = true
  126.24 +  locale.set(old_data)
  126.25 +end
   127.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   127.2 +++ b/framework/env/locale/get.lua	Sun Oct 25 12:00:00 2009 +0100
   127.3 @@ -0,0 +1,13 @@
   127.4 +--[[--
   127.5 +locale_setting =  -- setting for the given localization category (could be of any type, depending on the category)
   127.6 +locale.get(
   127.7 +  category        -- string selecting a localization category, e.g. "lang" or "time", etc...
   127.8 +)
   127.9 +
  127.10 +This function is used to read locale settings, which have been set with locale.set(...) or locale.do_with(...).
  127.11 +
  127.12 +--]]--
  127.13 +
  127.14 +function locale.get(category)
  127.15 +  return locale._current_data[category]
  127.16 +end
   128.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   128.2 +++ b/framework/env/locale/set.lua	Sun Oct 25 12:00:00 2009 +0100
   128.3 @@ -0,0 +1,19 @@
   128.4 +--[[--
   128.5 +locale.set(
   128.6 +  locale_options  -- table with locale categories as keys and their settings as values
   128.7 +)
   128.8 +
   128.9 +This function is used to set locale settings. The table given as first and only argument contains locale categories (e.g. "lang" or "time") as keys, and their settings as values. If there is a key 'reset' with a true value, then all non mentioned categories will be reset to nil.
  128.10 +
  128.11 +--]]--
  128.12 +
  128.13 +function locale.set(locale_options)
  128.14 +  if locale_options.reset then
  128.15 +    locale._current_data = {}
  128.16 +  end
  128.17 +  for key, value in pairs(locale_options) do
  128.18 +    if key ~= "reset" then
  128.19 +      locale._current_data[key] = value
  128.20 +    end
  128.21 +  end
  128.22 +end
   129.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   129.2 +++ b/framework/env/net/send_mail.lua	Sun Oct 25 12:00:00 2009 +0100
   129.3 @@ -0,0 +1,57 @@
   129.4 +--[[--
   129.5 +net.send_mail{
   129.6 +  envelope_from = envelope_from,   -- envelope from address, not part of mail headers
   129.7 +  from          = from,            -- From     header address or table with 'name' and 'address' fields
   129.8 +  sender        = sender,          -- Sender   header address or table with 'name' and 'address' fields
   129.9 +  reply_to      = reply_to,        -- Reply-To header address or table with 'name' and 'address' fields
  129.10 +  to            = to,              -- To       header address or table with 'name' and 'address' fields
  129.11 +  cc            = cc,              -- Cc       header address or table with 'name' and 'address' fields
  129.12 +  bcc           = bcc,             -- Bcc      header address or table with 'name' and 'address' fields
  129.13 +  subject       = subject,         -- subject of e-mail
  129.14 +  multipart     = multipart_type,  -- "alternative", "mixed", "related", or nil
  129.15 +  content_type  = content_type,    -- only for multipart == nil, defaults to "text/plain"
  129.16 +  binary        = binary,          -- allow full 8-bit content
  129.17 +  content       = content or {     -- content as lua-string, or table in case of multipart
  129.18 +    {
  129.19 +      multipart = multipart_type,
  129.20 +      ...,
  129.21 +      content   = content or {
  129.22 +        {...}, ...
  129.23 +      }
  129.24 +    }, {
  129.25 +      ...
  129.26 +    },
  129.27 +    ...
  129.28 +  }
  129.29 +}
  129.30 +
  129.31 +This function sends a mail using the /usr/sbin/sendmail command.
  129.32 +
  129.33 +--]]--
  129.34 +
  129.35 +function net.send_mail(args)
  129.36 +  local mail
  129.37 +  if type(args) == "string" then
  129.38 +    mail = args
  129.39 +  else
  129.40 +    mail = encode.mime.mail(args)
  129.41 +  end
  129.42 +  local envelope_from = args.envelope_from
  129.43 +  local command
  129.44 +  if
  129.45 +    envelope_from and
  129.46 +    string.find(envelope_from, "^[0-9A-Za-z%.-_@0-9A-Za-z%.-_]+$")
  129.47 +  then
  129.48 +    command =
  129.49 +      "/usr/sbin/sendmail -t -i -f " ..
  129.50 +      envelope_from ..
  129.51 +      " > /dev/null 2> /dev/null"
  129.52 +  else
  129.53 +    command = "/usr/sbin/sendmail -t -i > /dev/null 2> /dev/null"
  129.54 +  end
  129.55 +  trace.debug(command)
  129.56 +  -- TODO: use pfilter
  129.57 +  local sendmail = assert(io.popen(command, "w"))
  129.58 +  sendmail:write(mail)
  129.59 +  sendmail:close()
  129.60 +end
   130.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   130.2 +++ b/framework/env/param/__init.lua	Sun Oct 25 12:00:00 2009 +0100
   130.3 @@ -0,0 +1,2 @@
   130.4 +param._exchanged = false  -- important to be false, not nil
   130.5 +param._saved = {}  -- stack
   131.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   131.2 +++ b/framework/env/param/_get_parser.lua	Sun Oct 25 12:00:00 2009 +0100
   131.3 @@ -0,0 +1,72 @@
   131.4 +function param._get_parser(format_info, param_type)
   131.5 +  if format_info == nil or format_info == "" then
   131.6 +    return function(str)
   131.7 +      return param_type:load(str)
   131.8 +    end
   131.9 +  else
  131.10 +    local format_options = {}
  131.11 +    local format_type, rest = string.match(
  131.12 +      format_info,
  131.13 +      "^([A-Za-z][A-Za-z0-9_]*)(.*)$"
  131.14 +    )
  131.15 +    if format_type then
  131.16 +      rest = string.gsub(rest, "\\\\", "\\e")
  131.17 +      rest = string.gsub(rest, "\\'", "\\q")
  131.18 +      if
  131.19 +        string.find(rest, "\\$") or
  131.20 +        string.find(rest, "\\[^eq]")
  131.21 +      then
  131.22 +        format_type = nil
  131.23 +      else
  131.24 +        while rest ~= "" do
  131.25 +          local key, value, new_rest
  131.26 +          key, value, new_rest = string.match(
  131.27 +            rest,
  131.28 +            "^-([A-Za-z][A-Za-z0-9_]*)-'([^']*)'(.*)$"
  131.29 +          )
  131.30 +          if value then
  131.31 +            value = string.gsub(value, "\\q", "'")
  131.32 +            value = string.gsub(value, "\\e", "\\")
  131.33 +            format_options[key] = value
  131.34 +          else
  131.35 +            key, value, new_rest = string.match(
  131.36 +              rest,
  131.37 +              "^-([A-Za-z][A-Za-z0-9_]*)-([^-]*)(.*)$"
  131.38 +            )
  131.39 +            if value then
  131.40 +              if string.find(value, "^[0-9.Ee+-]+$") then
  131.41 +                local num = tonumber(value)
  131.42 +                if not num then
  131.43 +                  format_type = nil
  131.44 +                  break
  131.45 +                end
  131.46 +                format_options[key] = num
  131.47 +              elseif value == "t" then
  131.48 +                format_options[key] = true
  131.49 +              elseif value == "f" then
  131.50 +                format_options[key] = false
  131.51 +              else
  131.52 +                format_type = nil
  131.53 +                break
  131.54 +              end
  131.55 +            else
  131.56 +              format_type = nil
  131.57 +              break
  131.58 +            end
  131.59 +          end
  131.60 +          rest = new_rest
  131.61 +        end
  131.62 +      end
  131.63 +    end
  131.64 +    if not format_type then
  131.65 +      error("Illegal format string in GET/POST parameters found.")
  131.66 +    end
  131.67 +    local parse_func = parse[format_type]
  131.68 +    if not parse_func then
  131.69 +      error("Unknown format identifier in GET/POST parameters encountered.")
  131.70 +    end
  131.71 +    return function(str)
  131.72 +      return parse_func(str, param_type, format_options)
  131.73 +    end
  131.74 +  end
  131.75 +end
   132.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   132.2 +++ b/framework/env/param/exchange.lua	Sun Oct 25 12:00:00 2009 +0100
   132.3 @@ -0,0 +1,14 @@
   132.4 +--[[--
   132.5 +param.exchange(
   132.6 +  id,
   132.7 +  params
   132.8 +)
   132.9 +
  132.10 +This function exchanges the id and/or parameters which are returned by param.get_id(...), param.get(...), etc. It should not be called explicitly, but is used implicitly, if you pass an 'id' or 'params' option to execute.view{...} or execute.action{...}. The function param.restore() is used to revert the exchange.
  132.11 +
  132.12 +--]]--
  132.13 +
  132.14 +function param.exchange(id, params)
  132.15 +  table.insert(param._saved, param._exchanged)
  132.16 +  param._exchanged = { id = id, params = params }
  132.17 +end
   133.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   133.2 +++ b/framework/env/param/get.lua	Sun Oct 25 12:00:00 2009 +0100
   133.3 @@ -0,0 +1,31 @@
   133.4 +--[[--
   133.5 +value =       -- value of the parameter casted to the chosen param_type
   133.6 +param.get(
   133.7 +  key,        -- name of the parameter
   133.8 +  param_type  -- desired type of the returned value
   133.9 +)
  133.10 +
  133.11 +Either a GET or POST request parameter is returned by this function, or if param.exchange(...) was called before, one of the exchanged parameters is returned. You can specify which type the returned value shall have. If an external request parameter was used and there is another GET or POST parameter with the same name but a "__format" suffix, the parser with the name of the specified format will be automatically used to parse and convert the input value.
  133.12 +
  133.13 +--]]--
  133.14 +
  133.15 +function param.get(key, param_type)
  133.16 +  local param_type = param_type or atom.string
  133.17 +  if param._exchanged then
  133.18 +    local value = param._exchanged.params[key]
  133.19 +    if value ~= nil and not atom.has_type(value, param_type) then
  133.20 +      error("Parameter has unexpected type.")
  133.21 +    end
  133.22 +    return value
  133.23 +  else
  133.24 +    local str         = cgi.params[key]
  133.25 +    local format_info = cgi.params[key .. "__format"]
  133.26 +    if not str then
  133.27 +      if not format_info then
  133.28 +        return nil
  133.29 +      end
  133.30 +      str = ""
  133.31 +    end
  133.32 +    return param._get_parser(format_info, param_type)(str)
  133.33 +  end
  133.34 +end
   134.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   134.2 +++ b/framework/env/param/get_all_cgi.lua	Sun Oct 25 12:00:00 2009 +0100
   134.3 @@ -0,0 +1,20 @@
   134.4 +--[[--
   134.5 +params =         -- table with all non-list parameters
   134.6 +param.get_all_cgi()
   134.7 +
   134.8 +This function returns a table with all non-list GET/POST parameters (except internal parameters like "_webmcp_id").
   134.9 +
  134.10 +--]]--
  134.11 +
  134.12 +function param.get_all_cgi()
  134.13 +  local result = {}
  134.14 +  for key, value in pairs(cgi.params) do  -- TODO: exchanged params too?
  134.15 +    if
  134.16 +      (not string.match(key, "^_webmcp_")) and
  134.17 +      (not string.match(key, "%[%]$"))
  134.18 +    then
  134.19 +      result[key] = value
  134.20 +    end
  134.21 +  end
  134.22 +  return result
  134.23 +end
   135.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   135.2 +++ b/framework/env/param/get_id.lua	Sun Oct 25 12:00:00 2009 +0100
   135.3 @@ -0,0 +1,22 @@
   135.4 +--[[--
   135.5 +value =        -- value of the id casted to the chosen param_type
   135.6 +param.get_id(
   135.7 +  param_type   -- desired type of the returned value
   135.8 +)
   135.9 +
  135.10 +Same as param.get(...), but operates on a special id parameter. An id is set via a __webmcp_id GET or POST parameter or an 'id' option to execute.view{...} or execute.action{...}. In a normal setup a beauty URL of the form http://www.example.com/example-application/example-module/example-view/<id>.html will cause the id to be set.
  135.11 +
  135.12 +--]]--
  135.13 +
  135.14 +function param.get_id(param_type)
  135.15 +  local param_type = param_type or atom.integer
  135.16 +  if param._exchanged then
  135.17 +    local value = param._exchanged.id
  135.18 +    if value ~= nil and not atom.has_type(value, param_type) then
  135.19 +      error("Parameter has unexpected type.")
  135.20 +    end
  135.21 +    return value
  135.22 +  else
  135.23 +    return param.get("_webmcp_id", param_type)
  135.24 +  end
  135.25 +end
   136.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   136.2 +++ b/framework/env/param/get_id_cgi.lua	Sun Oct 25 12:00:00 2009 +0100
   136.3 @@ -0,0 +1,11 @@
   136.4 +--[[--
   136.5 +value =             -- id string or nil
   136.6 +param.get_id_cgi()
   136.7 +
   136.8 +This function returns the string value of the _webmcp_id GET/POST parameter.
   136.9 +
  136.10 +--]]--
  136.11 +
  136.12 +function param.get_id_cgi()
  136.13 +  return cgi.params._webmcp_id
  136.14 +end
   137.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   137.2 +++ b/framework/env/param/get_list.lua	Sun Oct 25 12:00:00 2009 +0100
   137.3 @@ -0,0 +1,37 @@
   137.4 +--[[--
   137.5 +values =         -- list of values casted to the chosen param_type
   137.6 +param.get_list(
   137.7 +  key,           -- name of the parameter without "[]" suffix
   137.8 +  param_type,    -- desired type of the returned values
   137.9 +)
  137.10 +
  137.11 +Same as param.get(...), but used for parameters which contain a list of values. For external GET/POST parameters the parameter name gets suffixed with "[]".
  137.12 +
  137.13 +--]]--
  137.14 +
  137.15 +function param.get_list(key, param_type)
  137.16 +  local param_type = param_type or atom.string
  137.17 +  if param._exchanged then
  137.18 +    local values = param._exchanged.params[key] or {}
  137.19 +    if type(values) ~= "table" then
  137.20 +      error("Parameter has unexpected type.")
  137.21 +    end
  137.22 +    for idx, value in ipairs(values) do
  137.23 +      if not atom.has_type(value, param_type) then
  137.24 +        error("Element of parameter list has unexpected type.")
  137.25 +      end
  137.26 +    end
  137.27 +    return values
  137.28 +  else
  137.29 +    local format_info = cgi.params[key .. "__format"]
  137.30 +    local parser = param._get_parser(format_info, param_type)
  137.31 +    local raw_values = cgi.params[key .. "[]"]
  137.32 +    local values = {}
  137.33 +    if raw_values then
  137.34 +      for idx, value in ipairs(raw_values) do
  137.35 +        values[idx] = parser(raw_values[idx])
  137.36 +      end
  137.37 +    end
  137.38 +    return values
  137.39 +  end
  137.40 +end
   138.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   138.2 +++ b/framework/env/param/iterate.lua	Sun Oct 25 12:00:00 2009 +0100
   138.3 @@ -0,0 +1,29 @@
   138.4 +--[[--
   138.5 +for
   138.6 +  index,          -- index variable counting up from 1
   138.7 +  prefix          -- prefix string with index in square brackets to be used as a prefix for a key passed to param.get or param.get_list
   138.8 +in
   138.9 +  param.iterate(
  138.10 +    prefix        -- prefix to be followed by an index in square brackets and another key
  138.11 +  )
  138.12 +do
  138.13 +  ...
  138.14 +end
  138.15 +
  138.16 +This function returns an interator function to be used in a for loop. The CGI GET/POST parameter (or internal parameter) with the name "prefix[len]" is read, where 'prefix' is the prefix passed as the argument and 'len' ist just the literal string "len". For each index from 1 to the read length the returned iterator function returns the index and a string consisting of the given prefix followed by the index in square brackets to be used as a prefix for keys passed to param.get(...) or param.get_list(...).
  138.17 +
  138.18 +--]]--
  138.19 +
  138.20 +function param.iterate(prefix)
  138.21 +  local length = param.get(prefix .. "[len]", atom.integer) or 0
  138.22 +  if not atom.is_integer(length) then
  138.23 +    error("List length is not a valid integer or nil.")
  138.24 +  end
  138.25 +  local index = 0
  138.26 +  return function()
  138.27 +    index = index + 1
  138.28 +    if index <= length then
  138.29 +      return index, prefix .. "[" .. index .. "]"
  138.30 +    end
  138.31 +  end
  138.32 +end
   139.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   139.2 +++ b/framework/env/param/restore.lua	Sun Oct 25 12:00:00 2009 +0100
   139.3 @@ -0,0 +1,16 @@
   139.4 +--[[--
   139.5 +param.restore()
   139.6 +
   139.7 +Calling this function reverts the changes of param.exchange(...). It should not be called explicitly, but is used implicitly, if you pass an 'id' or 'params' option to execute.view{...} or execute.action{...}.
   139.8 +
   139.9 +--]]--
  139.10 +
  139.11 +function param.restore()
  139.12 +  local saved = param._saved
  139.13 +  local previous = saved[#saved]
  139.14 +  saved[#saved] = nil
  139.15 +  if previous == nil then
  139.16 +    error("Tried to restore id and params without having exchanged it before.")
  139.17 +  end
  139.18 +  param._exchanged = previous
  139.19 +end
   140.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   140.2 +++ b/framework/env/param/update.lua	Sun Oct 25 12:00:00 2009 +0100
   140.3 @@ -0,0 +1,40 @@
   140.4 +--[[--
   140.5 +param.update(
   140.6 +  record,               -- database record to be updated
   140.7 +  key_and_field_name1,  -- name of CGI parameter and record field
   140.8 +  key_and_field_name2,  -- another name of a CGI parameter and record field
   140.9 +  {
  140.10 +    key3,               -- name of CGI parameter
  140.11 +    field_name3         -- name of record field
  140.12 +  }
  140.13 +)
  140.14 +
  140.15 +This function can update several fields of a database record using GET/POST request parameters (or internal/exchanged parameters). The type of each parameter is automatically determined by the class of the record (_class:get_colums()[field].type).
  140.16 +--]]--
  140.17 +
  140.18 +function param.update(record, mapping_info, ...)
  140.19 +  if not mapping_info then
  140.20 +    return
  140.21 +  end
  140.22 +  assert(record, "No record given for param.update(...).")
  140.23 +  assert(record._class, "Record passed to param.update(...) has no _class attribute.")
  140.24 +  local key, field_name
  140.25 +  if type(mapping_info) == "string" then
  140.26 +    key        = mapping_info
  140.27 +    field_name = mapping_info
  140.28 +  else
  140.29 +    key        = mapping_info[1]
  140.30 +    field_name = mapping_info[2]
  140.31 +  end
  140.32 +  assert(key, "No key given in parameter of param.update(...).")
  140.33 +  assert(field_name, "No field name given in parameter of param.update(...).")
  140.34 +  local column_info = record._class:get_columns()[field_name]
  140.35 +  if not column_info then
  140.36 +    error('Type of column "' .. field_name .. '" is unknown.')
  140.37 +  end
  140.38 +  local new_value = param.get(key, column_info.type)
  140.39 +  if new_value ~= record[field_name] then
  140.40 +    record[field_name] = new_value
  140.41 +  end
  140.42 +  return param.update(record, ...)  -- recursivly process following arguments
  140.43 +end
   141.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   141.2 +++ b/framework/env/param/update_relationship.lua	Sun Oct 25 12:00:00 2009 +0100
   141.3 @@ -0,0 +1,57 @@
   141.4 +--[[--
   141.5 +param.update_relationship{
   141.6 +  param_name        = param_name,        -- name of request GET/POST request parameters containing primary keys for model B
   141.7 +  id                = id,                -- value of the primary key for model A
   141.8 +  connecting_model  = connecting_model,  -- model used for creating/deleting entries referencing both model A and B
   141.9 +  own_reference     = own_reference,     -- field name for foreign key in the connecting model referencing model A 
  141.10 +  foreign_reference = foreign_reference  -- field name for foreign key in the connecting model referencing model B
  141.11 +}
  141.12 +
  141.13 +This function updates a many-to-many relationship using a specified 'connecting_model' referencing both models.
  141.14 +
  141.15 +--]]--
  141.16 +
  141.17 +function param.update_relationship(args)
  141.18 +  local param_name        = args.param_name
  141.19 +  local id                = args.id
  141.20 +  local connecting_model  = args.connecting_model
  141.21 +  local own_reference     = args.own_reference
  141.22 +  local foreign_reference = args.foreign_reference
  141.23 +  local selected_ids = param.get_list(param_name, atom.integer)  -- TODO: support other types than integer too
  141.24 +  local db    = connecting_model:get_db_conn()
  141.25 +  local table = connecting_model:get_qualified_table()
  141.26 +  if #selected_ids == 0 then
  141.27 +    db:query{
  141.28 +      'DELETE FROM ' .. table .. ' WHERE "' .. own_reference .. '" = ?',
  141.29 +      args.id
  141.30 +    }
  141.31 +  else
  141.32 +    local selected_ids_sql = { sep = ", " }
  141.33 +    for idx, value in ipairs(selected_ids) do
  141.34 +      selected_ids_sql[idx] = {"?::int8", value}
  141.35 +    end
  141.36 +    db:query{
  141.37 +      'DELETE FROM ' .. table ..
  141.38 +      ' WHERE "' .. own_reference .. '" = ?' ..
  141.39 +      ' AND NOT "' .. foreign_reference .. '" IN ($)',
  141.40 +      args.id,
  141.41 +      selected_ids_sql
  141.42 +    }
  141.43 +    -- TODO: use VALUES SQL command, instead of this dirty array trick
  141.44 +    db:query{
  141.45 +      'INSERT INTO ' .. table ..
  141.46 +      ' ("' .. own_reference .. '", "' .. foreign_reference .. '")' ..
  141.47 +      ' SELECT ?, "subquery"."foreign" FROM (' ..
  141.48 +        'SELECT (ARRAY[$])[i] AS "foreign"' ..
  141.49 +        ' FROM generate_series(1, ?) AS "dummy"("i")' ..
  141.50 +        ' EXCEPT SELECT "' .. foreign_reference .. '" AS "foreign"' ..
  141.51 +        ' FROM ' .. table ..
  141.52 +        ' WHERE "' .. own_reference .. '" = ?' ..
  141.53 +      ') AS "subquery"',
  141.54 +      args.id,
  141.55 +      selected_ids_sql,
  141.56 +      #selected_ids,
  141.57 +      args.id
  141.58 +    }
  141.59 +  end
  141.60 +end
   142.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   142.2 +++ b/framework/env/parse/_pre_fold.lua	Sun Oct 25 12:00:00 2009 +0100
   142.3 @@ -0,0 +1,21 @@
   142.4 +function parse._pre_fold(str)
   142.5 +  local str = str
   142.6 +  local special_chars = charset.get_data().special_chars
   142.7 +  local function replace(name, dst)
   142.8 +    local src = special_chars[name]
   142.9 +    if src then
  142.10 +      local pattern = string.gsub(src, "[][()^$%%]", "%%%1")
  142.11 +      str = string.gsub(str, pattern, dst)
  142.12 +    end
  142.13 +  end
  142.14 +  replace("nobreak_space",  " ")
  142.15 +  replace("minus_sign",     "-")
  142.16 +  replace("hyphen_sign",    "-")
  142.17 +  replace("nobreak_hyphen", "-")
  142.18 +  replace("figure_dash",    "-")
  142.19 +  str = string.gsub(str, "\t+", " ")
  142.20 +  str = string.gsub(str, "^ +", "")
  142.21 +  str = string.gsub(str, " +$", "")
  142.22 +  str = string.gsub(str, " +", " ")
  142.23 +  return str
  142.24 +end
   143.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   143.2 +++ b/framework/env/parse/boolean.lua	Sun Oct 25 12:00:00 2009 +0100
   143.3 @@ -0,0 +1,29 @@
   143.4 +function parse.boolean(str, dest_type, options)
   143.5 +  if dest_type ~= atom.boolean then
   143.6 +    error("parse.boolean(...) can only return booleans, but a different destination type than atom.boolean was given.")
   143.7 +  end
   143.8 +  local options = options or {}
   143.9 +  local trimmed_str = string.match(str or "", "^%s*(.-)%s*$")
  143.10 +  if options.true_as or options.false_as or options.nil_as then
  143.11 +    if trimmed_str == options.true_as then
  143.12 +      return true
  143.13 +    elseif trimmed_str == options.false_as then
  143.14 +      return false
  143.15 +    elseif trimmed_str == options.nil_as or trimmed_str == "" then
  143.16 +      return nil
  143.17 +    else
  143.18 +      error("Boolean value not recognized.")
  143.19 +    end
  143.20 +  else
  143.21 +    local char = string.upper(string.sub(trimmed_str, 1, 1))
  143.22 +    if char == "1" or char == "T" or char == "Y" then
  143.23 +      return true
  143.24 +    elseif char == "0" or char == "F" or char == "N" then
  143.25 +      return false
  143.26 +    elseif char == "" then
  143.27 +      return nil
  143.28 +    else
  143.29 +      error("Boolean value not recognized.")
  143.30 +    end
  143.31 +  end
  143.32 +end
   144.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   144.2 +++ b/framework/env/parse/currency.lua	Sun Oct 25 12:00:00 2009 +0100
   144.3 @@ -0,0 +1,29 @@
   144.4 +local function extract_numbers(str)
   144.5 +  local result = {}
   144.6 +  for char in string.gmatch(str, "[0-9]") do
   144.7 +    result[#result+1] = char
   144.8 +  end
   144.9 +  return table.concat(result)
  144.10 +end
  144.11 +
  144.12 +function parse.currency(str, dest_type, options)
  144.13 +  local str = parse._pre_fold(str)
  144.14 +  local dest_type = dest_type or atom.number
  144.15 +  local options = options or {}
  144.16 +  local currency_decimal_point = locale.get("currency_decimal_point")
  144.17 +  local decimal_point = locale.get("decimal_point") or "."
  144.18 +  local pos1, pos2
  144.19 +  if currency_decimal_point then
  144.20 +    pos1, pos2 = string.find(str, currency_decimal_point, 1, true)
  144.21 +  end
  144.22 +  if not pos1 then
  144.23 +    pos1, pos2 = string.find(str, decimal_point, 1, true)
  144.24 +  end
  144.25 +  if pos1 then
  144.26 +    local p1 = extract_numbers(string.sub(str, 1, pos1 - 1))
  144.27 +    local p2 = extract_numbers(string.sub(str, pos2 + 1, #str))
  144.28 +    return parse.decimal(p1 .. decimal_point .. p2, dest_type, options)
  144.29 +  else
  144.30 +    return parse.decimal(extract_numbers(str), dest_type, options)
  144.31 +  end
  144.32 +end
   145.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   145.2 +++ b/framework/env/parse/date.lua	Sun Oct 25 12:00:00 2009 +0100
   145.3 @@ -0,0 +1,101 @@
   145.4 +local function map_2digit_year(y2)
   145.5 +  local current_year = atom.date:get_current().year
   145.6 +  local guess2 = math.floor(current_year / 100) * 100 + tonumber(y2)
   145.7 +  local guess1 = guess2 - 100
   145.8 +  local guess3 = guess2 + 100
   145.9 +  if guess1 >= current_year - 80 and guess1 <= current_year + 10 then
  145.10 +    return guess1
  145.11 +  elseif guess2 >= current_year - 80 and guess2 <= current_year + 10 then
  145.12 +    return guess2
  145.13 +  elseif guess3 >= current_year - 80 and guess3 <= current_year + 10 then
  145.14 +    return guess3
  145.15 +  end
  145.16 +end
  145.17 +
  145.18 +function parse.date(str, dest_type, options)
  145.19 +  if dest_type ~= atom.date then
  145.20 +    error("parse.date(...) can only return dates, but a different destination type than atom.date was given.")
  145.21 +  end
  145.22 +  local date_format = locale.get("date_format")
  145.23 +  if date_format and string.find(date_format, "Y+%-D+%-M+") then
  145.24 +    error("Date format collision with ISO standard.")
  145.25 +  end
  145.26 +  if string.match(str, "^%s*$") then
  145.27 +    return nil
  145.28 +  end
  145.29 +  -- first try ISO format
  145.30 +  local year, month, day = string.match(
  145.31 +    str, "^%s*([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9])%s*$"
  145.32 +  )
  145.33 +  if year then
  145.34 +    return atom.date{
  145.35 +      year = tonumber(year),
  145.36 +      month = tonumber(month),
  145.37 +      day = tonumber(day)
  145.38 +    }
  145.39 +  end
  145.40 +  if not date_format then
  145.41 +    return atom.date.invalid
  145.42 +  end
  145.43 +  local format_parts = {}
  145.44 +  local numeric_parts = {}
  145.45 +  for part in string.gmatch(date_format, "[YMD]+") do
  145.46 +    format_parts[#format_parts+1] = part
  145.47 +  end
  145.48 +  for part in string.gmatch(str, "[0-9]+") do
  145.49 +    numeric_parts[#numeric_parts+1] = part
  145.50 +  end
  145.51 +  if #format_parts ~= #numeric_parts then
  145.52 +    return atom.date.invalid
  145.53 +  end
  145.54 +  local year, month, day
  145.55 +  local function process_part(format_part, numeric_part)
  145.56 +    if string.find(format_part, "^Y+$") then
  145.57 +      if #numeric_part == 4 then
  145.58 +        year = tonumber(numeric_part)
  145.59 +      elseif #numeric_part == 2 then
  145.60 +        year = map_2digit_year(numeric_part)
  145.61 +      else
  145.62 +        return atom.date.invalid
  145.63 +      end
  145.64 +    elseif string.find(format_part, "^M+$") then
  145.65 +      month = tonumber(numeric_part)
  145.66 +    elseif string.find(format_part, "^D+$") then
  145.67 +      day = tonumber(numeric_part)
  145.68 +    else
  145.69 +      if not #format_part == #numeric_part then
  145.70 +        return atom.date.invalid
  145.71 +      end
  145.72 +      local year_str  = ""
  145.73 +      local month_str = ""
  145.74 +      local day_str   = ""
  145.75 +      for i = 1, #format_part do
  145.76 +        local format_char = string.sub(format_part, i, i)
  145.77 +        local number_char = string.sub(numeric_part, i, i)
  145.78 +        if format_char == "Y" then
  145.79 +          year_str = year_str .. number_char
  145.80 +        elseif format_char == "M" then
  145.81 +          month_str = month_str .. number_char
  145.82 +        elseif format_char == "D" then
  145.83 +          day_str = day_str .. number_char
  145.84 +        else
  145.85 +          error("Assertion failed.")
  145.86 +        end
  145.87 +      end
  145.88 +      if #year_str == 2 then
  145.89 +        year = map_2digit_year(year_str)
  145.90 +      else
  145.91 +        year = tonumber(year_str)
  145.92 +      end
  145.93 +      month = tonumber(month_str)
  145.94 +      day = tonumber(day_str)
  145.95 +    end
  145.96 +  end
  145.97 +  for i = 1, #format_parts do
  145.98 +    process_part(format_parts[i], numeric_parts[i])
  145.99 +  end
 145.100 +  if not year or not month or not day then
 145.101 +    error("Date parser did not determine year, month and day. Maybe the 'date_format' locale is erroneous?")
 145.102 +  end
 145.103 +  return atom.date{ year = year, month = month, day = day }
 145.104 +end
   146.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   146.2 +++ b/framework/env/parse/decimal.lua	Sun Oct 25 12:00:00 2009 +0100
   146.3 @@ -0,0 +1,75 @@
   146.4 +local digit_set = {
   146.5 +  ["0"] = true, ["1"] = true, ["2"] = true, ["3"] = true, ["4"] = true,
   146.6 +  ["5"] = true, ["6"] = true, ["7"] = true, ["8"] = true, ["9"] = true
   146.7 +}
   146.8 +
   146.9 +function parse.decimal(str, dest_type, options)
  146.10 +  local str = parse._pre_fold(str)
  146.11 +  local dest_type = dest_type or atom.number
  146.12 +  local options = options or {}
  146.13 +  if str == "" then
  146.14 +    return nil
  146.15 +  else
  146.16 +    local decimal_shift = options.decimal_shift or 0
  146.17 +    if decimal_shift == true then
  146.18 +      decimal_shift = options.precision
  146.19 +    end
  146.20 +    local decimal_point = locale.get("decimal_point") or "."
  146.21 +    local negative    = nil
  146.22 +    local int         = 0
  146.23 +    local frac        = 0
  146.24 +    local precision   = 0
  146.25 +    local after_point = false
  146.26 +    for char in string.gmatch(str, ".") do
  146.27 +      local skip = false
  146.28 +      if negative == nil then
  146.29 +        if char == "+" then
  146.30 +          negative = false
  146.31 +          skip = true
  146.32 +        elseif char == "-" then  -- real minus sign already replaced by _pre_fold
  146.33 +          negative = true
  146.34 +          skip = true
  146.35 +        end
  146.36 +      end
  146.37 +      if not skip then
  146.38 +        if digit_set[char] then
  146.39 +          if after_point then
  146.40 +            if decimal_shift > 0 then
  146.41 +              int = 10 * int + tonumber(char)
  146.42 +              decimal_shift = decimal_shift - 1
  146.43 +            else
  146.44 +              frac = 10 * frac + tonumber(char)
  146.45 +              precision = precision + 1
  146.46 +            end
  146.47 +          else
  146.48 +            int = 10 * int + tonumber(char)
  146.49 +          end
  146.50 +        elseif char == decimal_point then
  146.51 +          if after_point then
  146.52 +            return dest_type.invalid
  146.53 +          else
  146.54 +            after_point = true
  146.55 +          end
  146.56 +        elseif char ~= " " then  -- TODO: ignore thousand seperator too, when supported by format.decimal
  146.57 +          return dest_type.invalid
  146.58 +        end
  146.59 +      end
  146.60 +    end
  146.61 +    int = int * 10 ^ decimal_shift
  146.62 +    if dest_type == atom.number or dest_type == atom.integer then
  146.63 +      if dest_type == atom.integer and frac ~= 0 then
  146.64 +        return atom.not_a_number
  146.65 +      else
  146.66 +        local f = int + frac / 10 ^ precision
  146.67 +        if negative then
  146.68 +          f = -f
  146.69 +        end
  146.70 +        return f
  146.71 +      end
  146.72 +    elseif dest_type == atom.fraction then
  146.73 +      return atom.fraction(int * 10 ^ precision + frac, 10 ^ precision)
  146.74 +    else
  146.75 +      error("Missing or invalid destination type for parsing.")
  146.76 +    end
  146.77 +  end
  146.78 +end
   147.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   147.2 +++ b/framework/env/parse/percentage.lua	Sun Oct 25 12:00:00 2009 +0100
   147.3 @@ -0,0 +1,30 @@
   147.4 +function parse.percentage(str, dest_type, options)
   147.5 +  local str = parse._pre_fold(str)
   147.6 +  local dest_type = dest_type or atom.number
   147.7 +  local options = table.new(options)
   147.8 +  options.precision = options.precision or 0
   147.9 +  if options.decimal_shift == true then
  147.10 +    options.decimal_shift = options.precision + 2
  147.11 +  end
  147.12 +  local f = parse.decimal(string.match(str, "^ *([^%%]*) *%%? *$"), dest_type, options)
  147.13 +  if dest_type == atom.number then
  147.14 +    if f then
  147.15 +      return f / 100
  147.16 +    end
  147.17 +  elseif dest_type == atom.integer then
  147.18 +    if f then
  147.19 +      f = f / 100
  147.20 +      if atom.is_integer(f) then
  147.21 +        return f
  147.22 +      else
  147.23 +        return atom.integer.invalid
  147.24 +      end
  147.25 +    end
  147.26 +  elseif dest_type == atom.fraction then
  147.27 +    if f then
  147.28 +      return f / 100
  147.29 +    end
  147.30 +  else
  147.31 +    error("Missing or invalid destination type for parsing.")
  147.32 +  end
  147.33 +end
   148.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   148.2 +++ b/framework/env/parse/time.lua	Sun Oct 25 12:00:00 2009 +0100
   148.3 @@ -0,0 +1,3 @@
   148.4 +function parse.time(str, dest_type, options)
   148.5 +  error("Not implemented.")  -- TODO
   148.6 +end
   149.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   149.2 +++ b/framework/env/parse/timestamp.lua	Sun Oct 25 12:00:00 2009 +0100
   149.3 @@ -0,0 +1,3 @@
   149.4 +function parse.timestamp(str, dest_type, options)
   149.5 +  error("Not implemented.")  -- TODO
   149.6 +end
   150.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   150.2 +++ b/framework/env/request/__init.lua	Sun Oct 25 12:00:00 2009 +0100
   150.3 @@ -0,0 +1,31 @@
   150.4 +request._status = nil
   150.5 +request._forward = nil
   150.6 +request._forward_processed = false
   150.7 +request._redirect = nil
   150.8 +request._absolute_baseurl = nil
   150.9 +request._404_route = nil
  150.10 +request._force_absolute_baseurl = false
  150.11 +request._perm_params = {}
  150.12 +request._csrf_secret = nil
  150.13 +
  150.14 +local depth
  150.15 +if cgi then  -- if-clause to support interactive mode
  150.16 +  depth = tonumber(cgi.params._webmcp_urldepth)
  150.17 +end
  150.18 +if depth and depth > 0 then
  150.19 +  local elements = {}
  150.20 +  for i = 1, depth do
  150.21 +    elements[#elements+1] = "../"
  150.22 +  end
  150.23 +  request._relative_baseurl = table.concat(elements)
  150.24 +else
  150.25 +  request._relative_baseurl = "./"
  150.26 +end
  150.27 +
  150.28 +request._app_basepath = assert(
  150.29 +  os.getenv("WEBMCP_APP_BASEPATH"),
  150.30 +  'WEBMCP_APP_BASEPATH is not set.'
  150.31 +)
  150.32 +if not string.find(request._app_basepath, "/$") then
  150.33 +  request._app_basebase = request._app_basepath .. "/"
  150.34 +end
   151.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   151.2 +++ b/framework/env/request/force_absolute_baseurl.lua	Sun Oct 25 12:00:00 2009 +0100
   151.3 @@ -0,0 +1,10 @@
   151.4 +--[[--
   151.5 +request.force_absolute_baseurl()
   151.6 +
   151.7 +Calling this function causes subsequent calls of request.get_relative_baseurl() to return absolute URLs instead.
   151.8 +
   151.9 +--]]--
  151.10 +
  151.11 +function request.force_absolute_baseurl()
  151.12 +  request._force_absolute_baseurl = true
  151.13 +end
   152.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   152.2 +++ b/framework/env/request/forward.lua	Sun Oct 25 12:00:00 2009 +0100
   152.3 @@ -0,0 +1,17 @@
   152.4 +--[[--
   152.5 +request.forward{
   152.6 +  module = module,  -- module name
   152.7 +  view   = view     -- view name
   152.8 +}
   152.9 +
  152.10 +This function is called automatically to forward to another view, after an action and all its filters have finished execution, if routing mode "forward" has been chosen. Calling request.forward{...} (or request.redirect{...}) explicitly inside an action will cause routing information from the browser to be ignored. Calling request.forward{...} causes all GET/POST parameters of the action to be preserved for the given view.
  152.11 +
  152.12 +--]]--
  152.13 +
  152.14 +function request.forward(args)
  152.15 +  if request.is_rerouted() then
  152.16 +    error("Tried to forward after another forward or redirect.")
  152.17 +  end
  152.18 +  request._forward = args
  152.19 +  trace.forward { module = args.module, view = args.view }
  152.20 +end
   153.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   153.2 +++ b/framework/env/request/get_404_route.lua	Sun Oct 25 12:00:00 2009 +0100
   153.3 @@ -0,0 +1,11 @@
   153.4 +--[[--
   153.5 +route_info =             -- table with 'module' and 'view' field
   153.6 +request.get_404_route()
   153.7 +
   153.8 +Returns the data passed to a previous call of request.set_404_route{...}, or nil.
   153.9 +
  153.10 +--]]--
  153.11 +
  153.12 +function request.get_404_route()
  153.13 +  return request._404_route
  153.14 +end
   154.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   154.2 +++ b/framework/env/request/get_absolute_baseurl.lua	Sun Oct 25 12:00:00 2009 +0100
   154.3 @@ -0,0 +1,7 @@
   154.4 +function request.get_absolute_baseurl()
   154.5 +  if request._absolute_baseurl then
   154.6 +    return request._absolute_baseurl
   154.7 +  else
   154.8 +    error("Absolute base URL is unknown. It should be set in the configuration by calling request.set_absolute_baseurl(...).")
   154.9 +  end
  154.10 +end
   155.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   155.2 +++ b/framework/env/request/get_action.lua	Sun Oct 25 12:00:00 2009 +0100
   155.3 @@ -0,0 +1,15 @@
   155.4 +--[[--
   155.5 +action_name =
   155.6 +request.get_action()
   155.7 +
   155.8 +Returns the name of the currently requested action, or nil in case of a view.
   155.9 +
  155.10 +--]]--
  155.11 +
  155.12 +function request.get_action()
  155.13 +  if request._forward_processed then
  155.14 +    return nil
  155.15 +  else
  155.16 +    return cgi.params._webmcp_action
  155.17 +  end
  155.18 +end
  155.19 \ No newline at end of file
   156.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   156.2 +++ b/framework/env/request/get_app_basepath.lua	Sun Oct 25 12:00:00 2009 +0100
   156.3 @@ -0,0 +1,11 @@
   156.4 +--[[--
   156.5 +path =                      -- path to directory of application with trailing slash
   156.6 +request.get_app_basepath()
   156.7 +
   156.8 +This function returns the path to the base directory of the application. A trailing slash is always included.
   156.9 +
  156.10 +--]]--
  156.11 +
  156.12 +function request.get_app_basepath()
  156.13 +  return request._app_basepath
  156.14 +end
   157.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   157.2 +++ b/framework/env/request/get_app_name.lua	Sun Oct 25 12:00:00 2009 +0100
   157.3 @@ -0,0 +1,11 @@
   157.4 +--[[--
   157.5 +app_name =
   157.6 +request.get_app_name()
   157.7 +
   157.8 +Returns the application name set by the environment variable 'WEBMCP_APP_NAME', or "main" as default application name, if the environment variable is unset.
   157.9 +
  157.10 +--]]--
  157.11 +
  157.12 +function request.get_app_name()
  157.13 +  return os.getenv("WEBMCP_APP_NAME") or 'main'
  157.14 +end
   158.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   158.2 +++ b/framework/env/request/get_config_name.lua	Sun Oct 25 12:00:00 2009 +0100
   158.3 @@ -0,0 +1,11 @@
   158.4 +--[[--
   158.5 +config_name =
   158.6 +request.get_config_name()
   158.7 +
   158.8 +Returns the name of the configuration selected by the environment variable 'WEBMCP_CONFIG_NAME', or nil if the environment variable is not set.
   158.9 +
  158.10 +--]]--
  158.11 +
  158.12 +function request.get_config_name()
  158.13 +  return os.getenv("WEBMCP_CONFIG_NAME")
  158.14 +end
  158.15 \ No newline at end of file
   159.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   159.2 +++ b/framework/env/request/get_csrf_secret.lua	Sun Oct 25 12:00:00 2009 +0100
   159.3 @@ -0,0 +1,11 @@
   159.4 +--[[--
   159.5 +secret =                   -- secret string, previously set with request.set_csrf_secret(...)
   159.6 +request.get_csrf_secret()
   159.7 +
   159.8 +Returns the secret string being previously set with request.set_csrf_secret(...) for inclusion in web forms (nil if none is set). This function is automatically used by the ui.form{...} helper.
   159.9 +
  159.10 +--]]--
  159.11 +
  159.12 +function request.get_csrf_secret(secret)
  159.13 +  return request._csrf_secret
  159.14 +end
   160.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   160.2 +++ b/framework/env/request/get_module.lua	Sun Oct 25 12:00:00 2009 +0100
   160.3 @@ -0,0 +1,15 @@
   160.4 +--[[--
   160.5 +module_name =
   160.6 +request.get_module()
   160.7 +
   160.8 +Returns the name of the module of the currently requested view or action.
   160.9 +
  160.10 +--]]--
  160.11 +
  160.12 +function request.get_module()
  160.13 +  if request._forward_processed then
  160.14 +    return request._forward.module or cgi.params._webmcp_module or 'index'
  160.15 +  else
  160.16 +    return cgi.params._webmcp_module or 'index'
  160.17 +  end
  160.18 +end
   161.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   161.2 +++ b/framework/env/request/get_perm_params.lua	Sun Oct 25 12:00:00 2009 +0100
   161.3 @@ -0,0 +1,12 @@
   161.4 +--[[--
   161.5 +perm_params =              -- table containing permanent parameters
   161.6 +request.get_perm_params()
   161.7 +
   161.8 +This function returns a table containing all permanent paremeters set with request.set_perm_param(...). Modifications of the returned table have no side effects.
   161.9 +
  161.10 +--]]--
  161.11 +
  161.12 +function request.get_perm_params()
  161.13 +  -- NOTICE: it's important to return a copy here
  161.14 +  return table.new(request._perm_params)
  161.15 +end
   162.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   162.2 +++ b/framework/env/request/get_redirect_data.lua	Sun Oct 25 12:00:00 2009 +0100
   162.3 @@ -0,0 +1,11 @@
   162.4 +--[[--
   162.5 +redirect_data =
   162.6 +request.get_redirect_data()
   162.7 +
   162.8 +This function returns redirect information from a previous call of request.redirect{...}, or nil if no redirect was requested.
   162.9 +
  162.10 +--]]--
  162.11 +
  162.12 +function request.get_redirect_data()
  162.13 +  return request._redirect
  162.14 +end
   163.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   163.2 +++ b/framework/env/request/get_relative_baseurl.lua	Sun Oct 25 12:00:00 2009 +0100
   163.3 @@ -0,0 +1,15 @@
   163.4 +--[[--
   163.5 +baseurl =
   163.6 +request.get_relative_baseurl()
   163.7 +
   163.8 +This function returns a relative base URL of the application. If request.force_absolute_baseurl() has been called before, an absolute URL is returned.
   163.9 +
  163.10 +--]]--
  163.11 +
  163.12 +function request.get_relative_baseurl()
  163.13 +  if request._force_absolute_baseurl then
  163.14 +    return (request.get_absolute_baseurl())
  163.15 +  else
  163.16 +    return request._relative_baseurl
  163.17 +  end
  163.18 +end
   164.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   164.2 +++ b/framework/env/request/get_status.lua	Sun Oct 25 12:00:00 2009 +0100
   164.3 @@ -0,0 +1,11 @@
   164.4 +--[[--
   164.5 +status_string =
   164.6 +request.get_status()
   164.7 +
   164.8 +Returns a HTTP status previously set with request.set_status(...).
   164.9 +
  164.10 +--]]--
  164.11 +
  164.12 +function request.get_status()
  164.13 +  return request._status
  164.14 +end
   165.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   165.2 +++ b/framework/env/request/get_view.lua	Sun Oct 25 12:00:00 2009 +0100
   165.3 @@ -0,0 +1,26 @@
   165.4 +--[[--
   165.5 +view_name =
   165.6 +request.get_view()
   165.7 +
   165.8 +Returns the name of the currently requested view, or nil in case of an action.
   165.9 +
  165.10 +--]]--
  165.11 +
  165.12 +function request.get_view()
  165.13 +  if request._forward_processed then
  165.14 +    return request._forward.view or 'index'
  165.15 +  else
  165.16 +    if cgi.params._webmcp_view then
  165.17 +      local suffix = cgi.params._webmcp_suffix or "html"
  165.18 +      if suffix == "html" then
  165.19 +        return cgi.params._webmcp_view
  165.20 +      else
  165.21 +        return cgi.params._webmcp_view .. "." .. suffix
  165.22 +      end
  165.23 +    elseif not cgi.params._webmcp_action then
  165.24 +      return 'index'
  165.25 +    else
  165.26 +      return nil
  165.27 +    end
  165.28 +  end
  165.29 +end
   166.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   166.2 +++ b/framework/env/request/is_ajax.lua	Sun Oct 25 12:00:00 2009 +0100
   166.3 @@ -0,0 +1,3 @@
   166.4 +function request.is_ajax()
   166.5 +  return cgi.params['ajax'] == '1'
   166.6 +end
   166.7 \ No newline at end of file
   167.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   167.2 +++ b/framework/env/request/is_post.lua	Sun Oct 25 12:00:00 2009 +0100
   167.3 @@ -0,0 +1,15 @@
   167.4 +--[[--
   167.5 +bool =             -- true, if the current request is a POST request
   167.6 +request.is_post()
   167.7 +
   167.8 +This function can be used to check, if the current request is a POST request.
   167.9 +
  167.10 +--]]--
  167.11 +
  167.12 +function request.is_post()
  167.13 +  if request._forward_processed then
  167.14 +    return false
  167.15 +  else
  167.16 +    return cgi.method == "POST"
  167.17 +  end
  167.18 +end
   168.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   168.2 +++ b/framework/env/request/is_rerouted.lua	Sun Oct 25 12:00:00 2009 +0100
   168.3 @@ -0,0 +1,20 @@
   168.4 +--[[--
   168.5 +
   168.6 +bool =                 -- true, if request.forard{...} or request.redirect{...} has been called before.
   168.7 +request_is_rerouted()
   168.8 +
   168.9 +This function returns true, if request.forward{...} or request.redirect{...} has been called before. In a new request caused by a redirect the function returns false. After a forward has been processed, this function also returns false.
  168.10 +
  168.11 +--]]--
  168.12 +
  168.13 +
  168.14 +function request.is_rerouted()
  168.15 +  if
  168.16 +    (request._forward and not request._forward_processed) or
  168.17 +    request._redirect
  168.18 +  then
  168.19 +    return true
  168.20 +  else
  168.21 +    return false
  168.22 +  end
  168.23 +end
   169.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   169.2 +++ b/framework/env/request/process_forward.lua	Sun Oct 25 12:00:00 2009 +0100
   169.3 @@ -0,0 +1,16 @@
   169.4 +--[[--
   169.5 +request.process_forward()
   169.6 +
   169.7 +This function causes a previous call of request.forward{...} to be in effect. It is called automatically when neccessary, and must not be called explicitly by any application.
   169.8 +
   169.9 +--]]--
  169.10 +
  169.11 +function request.process_forward()
  169.12 +  if request._forward then
  169.13 +    request._forward_processed = true
  169.14 +    trace.request{
  169.15 +      module = request.get_module(),
  169.16 +      view   = request.get_view()
  169.17 +    }
  169.18 +  end
  169.19 +end
   170.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   170.2 +++ b/framework/env/request/redirect.lua	Sun Oct 25 12:00:00 2009 +0100
   170.3 @@ -0,0 +1,39 @@
   170.4 +--[[--
   170.5 +request.redirect{
   170.6 +  module = module,  -- module name
   170.7 +  view   = view,    -- view name
   170.8 +  id     = id,      -- optional id for view
   170.9 +  params = params   -- optional view parameters
  170.10 +}
  170.11 +
  170.12 +Calling this function causes the WebMCP to do a 303 HTTP redirect after the current view or action and all filters have finished execution. If routing mode "redirect" has been chosen, then this function is called automatically after an action and all its filters have finished execution. Calling request.redirect{...} (or request.forward{...}) explicitly inside an action will cause routing information from the browser to be ignored. To preserve GET/POST parameters of an action, use request.forward{...} instead. Currently no redirects to external (absolute) URLs are possible, there will be an implementation in future though.
  170.13 +
  170.14 +--]]--
  170.15 +
  170.16 +function request.redirect(args)
  170.17 +  -- TODO: support redirects to external URLs too
  170.18 +  --       (needs fixes in the trace system as well)
  170.19 +  local module = args.module
  170.20 +  local view   = args.view
  170.21 +  local id     = args.id
  170.22 +  local params = args.params or {}
  170.23 +  if type(module) ~= "string" then
  170.24 +    error("No module string passed to request.redirect{...}.")
  170.25 +  end
  170.26 +  if type(view) ~= "string" then
  170.27 +    error("No view string passed to request.redirect{...}.")
  170.28 +  end
  170.29 +  if type(params) ~= "table" then
  170.30 +    error("Params array passed to request.redirect{...} is not a table.")
  170.31 +  end
  170.32 +  if request.is_rerouted() then
  170.33 +    error("Tried to redirect after another forward or redirect.")
  170.34 +  end
  170.35 +  request._redirect = {
  170.36 +    module = module,
  170.37 +    view   = view,
  170.38 +    id     = id,
  170.39 +    params = params
  170.40 +  }
  170.41 +  trace.redirect{ module = args.module, view = args.view }
  170.42 +end
   171.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   171.2 +++ b/framework/env/request/set_404_route.lua	Sun Oct 25 12:00:00 2009 +0100
   171.3 @@ -0,0 +1,13 @@
   171.4 +--[[--
   171.5 +request.set_404_route{
   171.6 +  module = module,  -- module name
   171.7 +  view   = view     -- view name
   171.8 +}
   171.9 +
  171.10 +In case a view or action is not found, the given module and view will be used to display an error page.
  171.11 +
  171.12 +--]]--
  171.13 +
  171.14 +function request.set_404_route(tbl)
  171.15 +  request._404_route = tbl
  171.16 +end
   172.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   172.2 +++ b/framework/env/request/set_absolute_baseurl.lua	Sun Oct 25 12:00:00 2009 +0100
   172.3 @@ -0,0 +1,16 @@
   172.4 +--[[--
   172.5 +request.set_absolute_baseurl(
   172.6 +  url                          -- Base URL of the application
   172.7 +)
   172.8 +
   172.9 +Calling this function is neccessary for every configuration, because an absolute URL is needed for HTTP redirects. If the URL of the application is volatile, and if you don't bother violating the HTTP standard, you might want to execute request.set_absolute_baseurl(request.get_relative_baseurl()) in your application configuration.
  172.10 +
  172.11 +--]]--
  172.12 +
  172.13 +function request.set_absolute_baseurl(url)
  172.14 +  if string.find(url, "/$") then
  172.15 +    request._absolute_baseurl = url
  172.16 +  else
  172.17 +    request._absolute_baseurl = url .. "/"
  172.18 +  end
  172.19 +end
   173.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   173.2 +++ b/framework/env/request/set_csrf_secret.lua	Sun Oct 25 12:00:00 2009 +0100
   173.3 @@ -0,0 +1,18 @@
   173.4 +--[[--
   173.5 +request.set_csrf_secret(
   173.6 +  secret                 -- secret random string
   173.7 +)
   173.8 +
   173.9 +Sets a secret string to be used as protection against cross-site request forgery attempts. This string will be transmitted to each action via a hidden form field named "_webmcp_csrf_secret". If this function is called during an action, and there is no CGI GET/POST parameter "_webmcp_csrf_secret" already being set to the given secret, then an error will be thrown to prohibit execution of the action.
  173.10 +
  173.11 +--]]--
  173.12 +
  173.13 +function request.set_csrf_secret(secret)
  173.14 +  if
  173.15 +    request.get_action() and
  173.16 +    cgi.params._webmcp_csrf_secret ~= secret
  173.17 +  then
  173.18 +    error("Cross-Site Request Forgery attempt detected");
  173.19 +  end
  173.20 +  request._csrf_secret = secret
  173.21 +end
   174.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   174.2 +++ b/framework/env/request/set_perm_param.lua	Sun Oct 25 12:00:00 2009 +0100
   174.3 @@ -0,0 +1,13 @@
   174.4 +--[[--
   174.5 +request.set_perm_param(
   174.6 +  key,                   -- name of parameter
   174.7 +  value                  -- value of parameter
   174.8 +)
   174.9 +
  174.10 +Setting a so-called "permanent parameter" with this function will cause a key value pair to be included in every HTTP link inside the application. It is used as a cookie replacement, where cookies are not suitable, e.g. because you want multiple browser windows to not interfere with each other. 
  174.11 +
  174.12 +--]]--
  174.13 +
  174.14 +function request.set_perm_param(key, value)
  174.15 +  request._perm_params[key] = value
  174.16 +end
   175.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   175.2 +++ b/framework/env/request/set_status.lua	Sun Oct 25 12:00:00 2009 +0100
   175.3 @@ -0,0 +1,22 @@
   175.4 +--[[--
   175.5 +request.set_status(
   175.6 +  str                -- string containing a HTTP status code, e.g. "404 Not Found"
   175.7 +)
   175.8 +
   175.9 +Calling this function causes a HTTP status different from 200 OK (or in case of error different from 500 Internal Server Error) to be sent to the browser.
  175.10 +
  175.11 +--]]--
  175.12 +
  175.13 +function request.set_status(str)
  175.14 +  if str then
  175.15 +    local t = type(str)
  175.16 +    if type(str) == "number" then
  175.17 +      str = tostring(str)
  175.18 +    elseif type(str) ~= "string" then
  175.19 +      error("request.set_status(...) must be called with a string as parameter.")
  175.20 +    end
  175.21 +    request._status = str
  175.22 +  else
  175.23 +    request._status = nil
  175.24 +  end
  175.25 +end
   176.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   176.2 +++ b/framework/env/slot/__init.lua	Sun Oct 25 12:00:00 2009 +0100
   176.3 @@ -0,0 +1,10 @@
   176.4 +slot._data_metatable = {}
   176.5 +function slot._data_metatable:__index(key)
   176.6 +  self[key] = { string_fragments = {}, state_table = {} }
   176.7 +  return self[key]
   176.8 +end
   176.9 +slot._active_slot = 'default'
  176.10 +slot._current_layout = 'default'
  176.11 +slot._content_type = nil
  176.12 +
  176.13 +slot.reset_all()
   177.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   177.2 +++ b/framework/env/slot/dump_all.lua	Sun Oct 25 12:00:00 2009 +0100
   177.3 @@ -0,0 +1,36 @@
   177.4 +--[[--
   177.5 +blob =           -- string for later usage with slot.restore_all(...)
   177.6 +slot.dump_all()
   177.7 +
   177.8 +Returns a single string, containing all slot contents. The result of this function can be used to restore all slots after a 303 redirect. This is done automatically by the WebMCP using slot.restore_all(...). If the result of this function is an empty string, then all slots are empty.
   177.9 +
  177.10 +--]]--
  177.11 +
  177.12 +local function encode(str)
  177.13 +  return (
  177.14 +    string.gsub(
  177.15 +      str,
  177.16 +      "[=;%[%]]",
  177.17 +      function(char)
  177.18 +        if char == "=" then return "[eq]"
  177.19 +        elseif char == ";" then return "[s]"
  177.20 +        elseif char == "[" then return "[o]"
  177.21 +        elseif char == "]" then return "[c]"
  177.22 +        else end
  177.23 +      end
  177.24 +    )
  177.25 +  )
  177.26 +end
  177.27 +
  177.28 +function slot.dump_all()
  177.29 +  local blob_parts = {}
  177.30 +  for key in pairs(slot._data) do
  177.31 +    if type(key) == "string" then
  177.32 +      local value = slot.get_content(key)
  177.33 +      if value ~= "" then
  177.34 +        blob_parts[#blob_parts + 1] = encode(key) .. "=" .. encode(value)
  177.35 +      end
  177.36 +    end
  177.37 +  end
  177.38 +  return table.concat(blob_parts, ";")
  177.39 +end
   178.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   178.2 +++ b/framework/env/slot/get_content.lua	Sun Oct 25 12:00:00 2009 +0100
   178.3 @@ -0,0 +1,20 @@
   178.4 +--[[--
   178.5 +content =
   178.6 +slot.get_content(
   178.7 +  slot_ident       -- name of the slot
   178.8 +)
   178.9 +
  178.10 +This function returns the content of a chosen slot as a single string.
  178.11 +
  178.12 +--]]--
  178.13 +
  178.14 +function slot.get_content(slot_ident)
  178.15 +  local slot_data = slot._data[slot_ident]
  178.16 +  if #slot_data.string_fragments > 1 then
  178.17 +    local str = table.concat(slot_data.string_fragments)
  178.18 +    slot_data.string_fragments = { str }
  178.19 +    return str
  178.20 +  else
  178.21 +    return slot_data.string_fragments[1] or ""
  178.22 +  end
  178.23 +end
   179.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   179.2 +++ b/framework/env/slot/get_content_type.lua	Sun Oct 25 12:00:00 2009 +0100
   179.3 @@ -0,0 +1,11 @@
   179.4 +--[[--
   179.5 +content_type =           -- content-type as selected with slot.set_layout(...)
   179.6 +slot.get_content_type()
   179.7 +
   179.8 +This function returns the content-type to be sent to the browser. It may be changed by calling slot.set_layout(...). The default content-type is "text/html; charset=UTF-8".
   179.9 +
  179.10 +--]]--
  179.11 +
  179.12 +function slot.get_content_type()
  179.13 +  return slot._content_type or 'text/html; charset=UTF-8'
  179.14 +end
   180.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   180.2 +++ b/framework/env/slot/get_state_table.lua	Sun Oct 25 12:00:00 2009 +0100
   180.3 @@ -0,0 +1,11 @@
   180.4 +--[[--
   180.5 +state_table =           -- table for saving a slot's state
   180.6 +slot.get_state_table()
   180.7 +
   180.8 +This function returns a table, holding state information of the currently active slot. To change the state information the returned table may be modified.
   180.9 +
  180.10 +--]]--
  180.11 +
  180.12 +function slot.get_state_table()
  180.13 +  return slot.get_state_table_of(slot._active_slot)
  180.14 +end
   181.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   181.2 +++ b/framework/env/slot/get_state_table_of.lua	Sun Oct 25 12:00:00 2009 +0100
   181.3 @@ -0,0 +1,13 @@
   181.4 +--[[--
   181.5 +state_table =             -- table for saving the slot's state
   181.6 +slot.get_state_table_of(
   181.7 +  slot_ident              -- name of a slot
   181.8 +)
   181.9 +
  181.10 +This function returns a table, holding state information of the named slot. To change the state information the returned table may be modified.
  181.11 +
  181.12 +--]]--
  181.13 +
  181.14 +function slot.get_state_table_of(slot_ident)
  181.15 +  return slot._data[slot_ident].state_table
  181.16 +end
   182.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   182.2 +++ b/framework/env/slot/put.lua	Sun Oct 25 12:00:00 2009 +0100
   182.3 @@ -0,0 +1,17 @@
   182.4 +--[[--
   182.5 +slot.put(
   182.6 +  string1,  -- string to be written into the active slot
   182.7 +  string2,  -- another string to be written into the active slot
   182.8 +  ...
   182.9 +)
  182.10 +
  182.11 +This function is used to write strings into the active slot.
  182.12 +
  182.13 +-- NOTE: ACCELERATED FUNCTION
  182.14 +-- Do not change unless also you also update webmcp_accelerator.c
  182.15 +
  182.16 +--]]--
  182.17 +
  182.18 +function slot.put(...)
  182.19 +  return slot.put_into(slot._active_slot, ...)
  182.20 +end
  182.21 \ No newline at end of file
   183.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   183.2 +++ b/framework/env/slot/put_into.lua	Sun Oct 25 12:00:00 2009 +0100
   183.3 @@ -0,0 +1,23 @@
   183.4 +--[[--
   183.5 +slot.put(
   183.6 +  slot_ident  -- name of a slot
   183.7 +  string1,    -- string to be written into the named slot
   183.8 +  string2,    -- another string to be written into the named slot
   183.9 +  ...
  183.10 +)
  183.11 +
  183.12 +This function is used to write strings into a named slot.
  183.13 +
  183.14 +-- NOTE: ACCELERATED FUNCTION
  183.15 +-- Do not change unless also you also update webmcp_accelerator.c
  183.16 +
  183.17 +--]]--
  183.18 +
  183.19 +function slot.put_into(slot_ident, ...)
  183.20 +  local t = slot._data[slot_ident].string_fragments
  183.21 +  for i = 1, math.huge do
  183.22 +    local v = select(i, ...)
  183.23 +    if v == nil then break end
  183.24 +    t[#t + 1] = v
  183.25 +  end
  183.26 +end
  183.27 \ No newline at end of file
   184.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   184.2 +++ b/framework/env/slot/render_layout.lua	Sun Oct 25 12:00:00 2009 +0100
   184.3 @@ -0,0 +1,48 @@
   184.4 +--[[--
   184.5 +output =              -- document/data to be sent to the web browser
   184.6 +slot.render_layout()
   184.7 +
   184.8 +This function returns the selected layout after replacing all slot placeholders with the respective slot contents. If slot.set_layout(...) was called with nil as first argument, then no layout will be used, but only the contents of the slot named "data" are returned.
   184.9 +
  184.10 +--]]--
  184.11 +
  184.12 +function slot.render_layout()
  184.13 +  if slot._current_layout then
  184.14 +    local layout_file = assert(io.open(
  184.15 +      encode.file_path(
  184.16 +        request.get_app_basepath(),
  184.17 +        'app',
  184.18 +        request.get_app_name(),
  184.19 +        '_layout',
  184.20 +        slot._current_layout .. '.html'
  184.21 +      ),
  184.22 +      'r'
  184.23 +    ))
  184.24 +    local layout = layout_file:read("*a")
  184.25 +    io.close(layout_file)
  184.26 +
  184.27 +    -- render layout
  184.28 +    layout = string.gsub(layout, "__BASEURL__/?", request.get_relative_baseurl())  -- TODO: find a better placeholder than __BASEURL__ ?
  184.29 +    layout = string.gsub(layout, '<!%-%- *WEBMCP +SLOT +([^ ]+) *%-%->', 
  184.30 +      function(slot_ident)
  184.31 +        if #slot.get_content(slot_ident) > 0 then
  184.32 +          return '<div class="slot_' .. slot_ident .. '" id="slot_' .. slot_ident .. '">' .. slot.get_content(slot_ident).. '</div>'
  184.33 +        else
  184.34 +          return ''
  184.35 +        end
  184.36 +      end
  184.37 +    )
  184.38 +    layout = string.gsub(layout, '<!%-%- *WEBMCP +SLOTNODIV +([^ ]+) *%-%->', 
  184.39 +      function(slot_ident)
  184.40 +        if #slot.get_content(slot_ident) > 0 then
  184.41 +          return slot.get_content(slot_ident)
  184.42 +        else
  184.43 +          return ''
  184.44 +        end
  184.45 +      end
  184.46 +    )
  184.47 +    return layout
  184.48 +  else
  184.49 +    return slot.get_content("data")
  184.50 +  end
  184.51 +end
   185.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   185.2 +++ b/framework/env/slot/reset.lua	Sun Oct 25 12:00:00 2009 +0100
   185.3 @@ -0,0 +1,12 @@
   185.4 +--[[--
   185.5 +slot.reset(
   185.6 +  slot_ident  -- name of a slot to be emptied
   185.7 +)
   185.8 +
   185.9 +Calling this function reset the named slot to be empty.
  185.10 +
  185.11 +--]]--
  185.12 +
  185.13 +function slot.reset(slot_ident)
  185.14 +  slot._data[slot_ident] = nil
  185.15 +end
   186.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   186.2 +++ b/framework/env/slot/reset_all.lua	Sun Oct 25 12:00:00 2009 +0100
   186.3 @@ -0,0 +1,10 @@
   186.4 +--[[--
   186.5 +slot.reset_all()
   186.6 +
   186.7 +Calling this function resets all slots to be empty.
   186.8 +
   186.9 +--]]--
  186.10 +
  186.11 +function slot.reset_all()
  186.12 +  slot._data = setmetatable({}, slot._data_metatable)
  186.13 +end
   187.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   187.2 +++ b/framework/env/slot/restore_all.lua	Sun Oct 25 12:00:00 2009 +0100
   187.3 @@ -0,0 +1,32 @@
   187.4 +--[[--
   187.5 +slot.restore_all(
   187.6 +  blob             -- string as returned by slot.dump_all()
   187.7 +)
   187.8 +
   187.9 +Restores all slots using a string created by slot.dump_all().
  187.10 +
  187.11 +--]]--
  187.12 +
  187.13 +local function decode(str)
  187.14 +  return (
  187.15 +    string.gsub(
  187.16 +      str,
  187.17 +      "%[[a-z]+%]",
  187.18 +      function(char)
  187.19 +        if char == "[eq]" then return "="
  187.20 +        elseif char == "[s]" then return ";"
  187.21 +        elseif char == "[o]" then return "["
  187.22 +        elseif char == "[c]" then return "]"
  187.23 +        else end
  187.24 +      end
  187.25 +    )
  187.26 +  )
  187.27 +end
  187.28 +
  187.29 +function slot.restore_all(blob)
  187.30 +  slot.reset_all()
  187.31 +  for encoded_key, encoded_value in string.gmatch(blob, "([^=;]*)=([^=;]*)") do
  187.32 +    local key, value = decode(encoded_key), decode(encoded_value)
  187.33 +    slot._data[key].string_fragments = { value }
  187.34 +  end
  187.35 +end
   188.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   188.2 +++ b/framework/env/slot/select.lua	Sun Oct 25 12:00:00 2009 +0100
   188.3 @@ -0,0 +1,18 @@
   188.4 +--[[--
   188.5 +slot.select(
   188.6 +  slot_ident,  -- name of a slot
   188.7 +  function()
   188.8 +    ...        -- code to be executed using the named slot
   188.9 +  end
  188.10 +)
  188.11 +
  188.12 +This function executes code in a way that slot.put(...) and other functions write into the slot with the given name. Calls of slot.select may be nested.
  188.13 +
  188.14 +--]]--
  188.15 +
  188.16 +function slot.select(slot_ident, block)
  188.17 +  local old_slot = slot._active_slot
  188.18 +  slot._active_slot = slot_ident
  188.19 +  block()
  188.20 +  slot._active_slot = old_slot
  188.21 +end
   189.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   189.2 +++ b/framework/env/slot/set_layout.lua	Sun Oct 25 12:00:00 2009 +0100
   189.3 @@ -0,0 +1,14 @@
   189.4 +--[[--
   189.5 +slot.set_layout(
   189.6 +  layout_ident,   -- name of layout or nil for binary data in slot named "data"
   189.7 +  content_type    -- content-type to be sent to the browser, or nil for default
   189.8 +)
   189.9 +
  189.10 +This function selects which layout should be used when calling slot.render_layout(). If no layout is selected by passing nil as first argument, then no layout will be used, and the slot named "data" is used plainly. The second argument to slot.set_layout is the content-type which is sent to the browser.
  189.11 +
  189.12 +--]]--
  189.13 +
  189.14 +function slot.set_layout(layout_ident, content_type)
  189.15 +  slot._current_layout = layout_ident
  189.16 +  slot._content_type = content_type
  189.17 +end
   190.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   190.2 +++ b/framework/env/slot/use_temporary.lua	Sun Oct 25 12:00:00 2009 +0100
   190.3 @@ -0,0 +1,22 @@
   190.4 +--[[--
   190.5 +slot_content =
   190.6 +slot.use_temporary(
   190.7 +  function()
   190.8 +    ...
   190.9 +  end
  190.10 +)
  190.11 +
  190.12 +This function creates a temporary slot and executes code in a way that slot.put(...) and other functions will write into the temporary slot. Afterwards the contents of the temporary slot are returned as a single string.
  190.13 +
  190.14 +--]]--
  190.15 +
  190.16 +function slot.use_temporary(block)
  190.17 +  local old_slot = slot._active_slot
  190.18 +  local temp_slot_reference = {}  -- just a unique reference
  190.19 +  slot._active_slot = temp_slot_reference
  190.20 +  block()
  190.21 +  slot._active_slot = old_slot
  190.22 +  local result = slot.get_content(temp_slot_reference)
  190.23 +  slot.reset(temp_slot_reference)
  190.24 +  return result
  190.25 +end
   191.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   191.2 +++ b/framework/env/tempstore/pop.lua	Sun Oct 25 12:00:00 2009 +0100
   191.3 @@ -0,0 +1,21 @@
   191.4 +--[[--
   191.5 +blob =          -- loaded string
   191.6 +tempstore.pop(
   191.7 +  key           -- key as returned by tempstore.save(...)
   191.8 +)
   191.9 +
  191.10 +This function restores data, which had been stored temporarily by tempstore.save(...). After loading the data, it is deleted from the tempstore automatically.
  191.11 +
  191.12 +--]]--
  191.13 +
  191.14 +function tempstore.pop(key)
  191.15 +  local filename = encode.file_path(
  191.16 +    request.get_app_basepath(), 'tmp', "tempstore-" .. key .. ".tmp"
  191.17 +  )
  191.18 +  local file = io.open(filename, "r")
  191.19 +  if not file then return nil end
  191.20 +  local blob = file:read("*a")
  191.21 +  io.close(file)
  191.22 +  os.remove(filename)
  191.23 +  return blob
  191.24 +end
   192.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   192.2 +++ b/framework/env/tempstore/save.lua	Sun Oct 25 12:00:00 2009 +0100
   192.3 @@ -0,0 +1,20 @@
   192.4 +--[[--
   192.5 +key =            -- key to be used with tempstore.pop(...) to regain the stored string
   192.6 +tempstore.save(
   192.7 +  blob           -- string to be stored
   192.8 +)
   192.9 +
  192.10 +This function stores data temporarily. It is used to restore slot information after a 303 HTTP redirect. It returns a key, which can be passed to tempstore.pop(...) to regain the stored data.
  192.11 +
  192.12 +--]]--
  192.13 +
  192.14 +function tempstore.save(blob)
  192.15 +  local key = multirand.string(26, "123456789bcdfghjklmnpqrstvwxyz");
  192.16 +  local filename = encode.file_path(
  192.17 +    request.get_app_basepath(), 'tmp', "tempstore-" .. key .. ".tmp"
  192.18 +  )
  192.19 +  local file = assert(io.open(filename, "w"))
  192.20 +  file:write(blob)
  192.21 +  io.close(file)
  192.22 +  return key
  192.23 +end
  192.24 \ No newline at end of file
   193.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   193.2 +++ b/framework/env/trace/__init.lua	Sun Oct 25 12:00:00 2009 +0100
   193.3 @@ -0,0 +1,2 @@
   193.4 +trace._tree = { type = "root" }
   193.5 +trace._stack = { trace._tree }
   194.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   194.2 +++ b/framework/env/trace/_close_section.lua	Sun Oct 25 12:00:00 2009 +0100
   194.3 @@ -0,0 +1,9 @@
   194.4 +function trace._close_section()
   194.5 +  local pos = #trace._stack
   194.6 +  local closed_section = trace._stack[pos]
   194.7 +  if not closed_section then
   194.8 +    error("All trace sections have been closed already.")
   194.9 +  end
  194.10 +  trace._stack[pos] = nil
  194.11 +  return closed_section
  194.12 +end
   195.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   195.2 +++ b/framework/env/trace/_new_entry.lua	Sun Oct 25 12:00:00 2009 +0100
   195.3 @@ -0,0 +1,5 @@
   195.4 +function trace._new_entry(node)
   195.5 +  local node = node or {}
   195.6 +  table.insert(trace._stack[#trace._stack], node)
   195.7 +  return node
   195.8 +end
   196.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   196.2 +++ b/framework/env/trace/_open_section.lua	Sun Oct 25 12:00:00 2009 +0100
   196.3 @@ -0,0 +1,6 @@
   196.4 +function trace._open_section(node)
   196.5 +  local node = trace._new_entry(node)
   196.6 +  node.section = true
   196.7 +  table.insert(trace._stack, node)
   196.8 +  return node
   196.9 +end
   197.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   197.2 +++ b/framework/env/trace/_render_sub_tree.lua	Sun Oct 25 12:00:00 2009 +0100
   197.3 @@ -0,0 +1,154 @@
   197.4 +local function open(class)
   197.5 +  slot.put('<li class="trace_' .. class .. '">')
   197.6 +end
   197.7 +local function open_head()
   197.8 +  slot.put('<div class="trace_head">')
   197.9 +end
  197.10 +local function close_head()
  197.11 +  slot.put('</div>')
  197.12 +end
  197.13 +local function close()
  197.14 +  slot.put('</li>')
  197.15 +end
  197.16 +function trace._render_sub_tree(node)
  197.17 +  local function render_children(tail)
  197.18 +    if #node > 0 then
  197.19 +      slot.put('<ul class="trace_list">')
  197.20 +      for idx, child in ipairs(node) do
  197.21 +        trace._render_sub_tree(child)
  197.22 +      end
  197.23 +      if tail then
  197.24 +        slot.put(tail)
  197.25 +      end
  197.26 +      slot.put("</ul>")
  197.27 +    end
  197.28 +  end
  197.29 +  local function close_with_children()
  197.30 +    close_head()
  197.31 +    render_children()
  197.32 +    close()
  197.33 +  end
  197.34 +  local node_type = node.type
  197.35 +  if node_type == "root" then
  197.36 +    render_children()
  197.37 +  elseif node_type == "debug" then
  197.38 +    open("debug")
  197.39 +    slot.put(encode.html(node.message))
  197.40 +    close()
  197.41 +  elseif node_type == "request" then
  197.42 +    open("request")
  197.43 +    open_head()
  197.44 +    slot.put("REQUESTED")
  197.45 +    if node.view then
  197.46 +      slot.put(" VIEW")
  197.47 +    elseif node.action then
  197.48 +      slot.put(" ACTION")
  197.49 +    end
  197.50 +    slot.put(
  197.51 +      ": ",
  197.52 +      encode.html(node.module),
  197.53 +      "/",
  197.54 +      encode.html(node.view or node.action)
  197.55 +    )
  197.56 +    close_with_children()
  197.57 +  elseif node_type == "config" then
  197.58 +    open("config")
  197.59 +    open_head()
  197.60 +    slot.put('Configuration "', encode.html(node.name), '"')
  197.61 +    close_with_children()
  197.62 +  elseif node_type == "filter" then
  197.63 +    open("filter")
  197.64 +    open_head()
  197.65 +    slot.put(encode.html(node.path))
  197.66 +    close_with_children()
  197.67 +  elseif node_type == "view" then
  197.68 +    open("view")
  197.69 +    open_head()
  197.70 +    slot.put(
  197.71 +      "EXECUTE VIEW: ",
  197.72 +      encode.html(node.module),
  197.73 +      "/",
  197.74 +      encode.html(node.view)
  197.75 +    )
  197.76 +    close_with_children()
  197.77 +  elseif node_type == "action" then
  197.78 +    if
  197.79 +      node.status and (
  197.80 +        node.status == "ok" or
  197.81 +        string.find(node.status, "^ok_")
  197.82 +      )
  197.83 +    then
  197.84 +      open("action_success")
  197.85 +    elseif
  197.86 +      node.status and (
  197.87 +        node.status == "error" or
  197.88 +        string.find(node.status, "^error_")
  197.89 +      )
  197.90 +    then
  197.91 +      open("action_softfail")
  197.92 +    else
  197.93 +      open("action_neutral")
  197.94 +    end
  197.95 +    open_head()
  197.96 +    slot.put(
  197.97 +      "EXECUTE ACTION: ",
  197.98 +      encode.html(node.module),
  197.99 +      "/",
 197.100 +      encode.html(node.action)
 197.101 +    )
 197.102 +    close_head()
 197.103 +    if node.status == "softfail" then
 197.104 +      render_children(
 197.105 +        '<li class="trace_action_status">Status code: "' ..
 197.106 +        encode.html(node.failure_code) ..
 197.107 +        '"</li>'
 197.108 +      )
 197.109 +    else
 197.110 +      render_children()
 197.111 +    end
 197.112 +    close()
 197.113 +  elseif node_type == "redirect" then
 197.114 +    open("redirect")
 197.115 +    open_head()
 197.116 +    slot.put("303 REDIRECT TO VIEW: ", encode.html(node.module), "/", encode.html(node.view))
 197.117 +    close_with_children()
 197.118 +  elseif node_type == "forward" then
 197.119 +    open("forward")
 197.120 +    open_head()
 197.121 +    slot.put("INTERNAL FORWARD TO VIEW: ", encode.html(node.module), "/", encode.html(node.view))
 197.122 +    close_with_children()
 197.123 +  elseif node_type == "exectime" then
 197.124 +    open("exectime")
 197.125 +    open_head()
 197.126 +    slot.put(
 197.127 +      "Finished after " ..
 197.128 +      string.format("%.1f", os.monotonic_hires_time() * 1000) ..
 197.129 +      ' ms (' ..
 197.130 +      string.format("%.1f", os.clock() * 1000) ..
 197.131 +      ' ms CPU)'
 197.132 +    )
 197.133 +    close_with_children()
 197.134 +  elseif node_type == "sql" then
 197.135 +    open("sql")
 197.136 +    if node.error_position then
 197.137 +      -- error position starts counting with 1
 197.138 +      local part1 = string.sub(node.command, 1, node.error_position - 1)
 197.139 +      --local part2 = string.sub(node.command, node.error_position - 1, node.error_position + 1)
 197.140 +      local part2 = string.sub(node.command, node.error_position)
 197.141 +      slot.put(encode.html(part1))
 197.142 +      slot.put('<span class="trace_error_position">&rArr;</span>')
 197.143 +      slot.put(encode.html(part2))
 197.144 +      --slot.put('</span>')
 197.145 +      --slot.put(encode.html(part3))
 197.146 +    else
 197.147 +      slot.put(encode.html(node.command))
 197.148 +    end
 197.149 +    close();
 197.150 +  elseif node_type == "error" then
 197.151 +    open("error")
 197.152 +    open_head()
 197.153 +    slot.put("UNEXPECTED ERROR")
 197.154 +    close_head()
 197.155 +    close()
 197.156 +  end
 197.157 +end
   198.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   198.2 +++ b/framework/env/trace/debug.lua	Sun Oct 25 12:00:00 2009 +0100
   198.3 @@ -0,0 +1,12 @@
   198.4 +--[[--
   198.5 +trace_debug(
   198.6 +  message     -- message to be inserted into the trace log
   198.7 +)
   198.8 +
   198.9 +This function can be used to include debug output in the trace log.
  198.10 +
  198.11 +--]]--
  198.12 +
  198.13 +function trace.debug(message)
  198.14 +  trace._new_entry{ type = "debug", message = tostring(message) }
  198.15 +end
   199.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   199.2 +++ b/framework/env/trace/enter_action.lua	Sun Oct 25 12:00:00 2009 +0100
   199.3 @@ -0,0 +1,21 @@
   199.4 +--[[--
   199.5 +trace.enter_action{
   199.6 +  module = module, 
   199.7 +  action = action
   199.8 +}
   199.9 +
  199.10 +This function is used by execute.action and logs the call of an action.
  199.11 +
  199.12 +--]]--
  199.13 +
  199.14 +function trace.enter_action(args)
  199.15 +  local module = args.module
  199.16 +  local action = args.action
  199.17 +  if type(module) ~= "string" then
  199.18 +    error("No module string passed to trace.enter_action{...}.")
  199.19 +  end
  199.20 +  if type(action) ~= "string" then
  199.21 +    error("No action string passed to trace.enter_action{...}.")
  199.22 +  end
  199.23 +  trace._open_section{ type = "action", module = module, action = action }
  199.24 +end
   200.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   200.2 +++ b/framework/env/trace/enter_config.lua	Sun Oct 25 12:00:00 2009 +0100
   200.3 @@ -0,0 +1,16 @@
   200.4 +--[[--
   200.5 +trace.enter_config{
   200.6 +  name = name 
   200.7 +}
   200.8 +
   200.9 +This function is used by execute.config and logs the inclusion of a configuration.
  200.10 +
  200.11 +--]]--
  200.12 +
  200.13 +function trace.enter_config(args)
  200.14 +  local name = args.name
  200.15 +  if type(name) ~= "string" then
  200.16 +    error("No name string passed to trace.enter_config{...}.")
  200.17 +  end
  200.18 +  trace._open_section{ type = "config", name = name }
  200.19 +end
   201.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   201.2 +++ b/framework/env/trace/enter_filter.lua	Sun Oct 25 12:00:00 2009 +0100
   201.3 @@ -0,0 +1,16 @@
   201.4 +--[[--
   201.5 +trace.enter_filter{
   201.6 +  path = path
   201.7 +}
   201.8 +
   201.9 +This function logs the call of a filter, when using execute.filtered_view{...} and execute.filtered_action{...}.
  201.10 +
  201.11 +--]]--
  201.12 +
  201.13 +function trace.enter_filter(args)
  201.14 +  local path = args.path
  201.15 +  if type(path) ~= "string" then
  201.16 +    error("No path string passed to trace.enter_filter{...}.")
  201.17 +  end
  201.18 +  trace._open_section{ type = "filter", path = path }
  201.19 +end
   202.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   202.2 +++ b/framework/env/trace/enter_view.lua	Sun Oct 25 12:00:00 2009 +0100
   202.3 @@ -0,0 +1,21 @@
   202.4 +--[[--
   202.5 +trace.enter_view{
   202.6 +  module = module, 
   202.7 +  view   = view
   202.8 +}
   202.9 +
  202.10 +This function is used by execute.view and logs the call of a view.
  202.11 +
  202.12 +--]]--
  202.13 +
  202.14 +function trace.enter_view(args)
  202.15 +  local module = args.module
  202.16 +  local view   = args.view
  202.17 +  if type(module) ~= "string" then
  202.18 +    error("No module passed to trace.enter_view{...}.")
  202.19 +  end
  202.20 +  if type(view) ~= "string" then
  202.21 +    error("No view passed to trace.enter_view{...}.")
  202.22 +  end
  202.23 +  trace._open_section{ type = "view", module = module, view = view }
  202.24 +end
   203.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   203.2 +++ b/framework/env/trace/error.lua	Sun Oct 25 12:00:00 2009 +0100
   203.3 @@ -0,0 +1,14 @@
   203.4 +--[[--
   203.5 +trace.error{
   203.6 +}
   203.7 +
   203.8 +This function is called automatically in case of errors to log them.
   203.9 +
  203.10 +--]]--
  203.11 +
  203.12 +function trace.error(args)
  203.13 +  trace._new_entry { type = "error" }
  203.14 +  local closed_section = trace._close_section()
  203.15 +  closed_section.hard_error = true  -- TODO: not used, maybe remove
  203.16 +  trace._stack = { trace._tree }
  203.17 +end
   204.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   204.2 +++ b/framework/env/trace/exectime.lua	Sun Oct 25 12:00:00 2009 +0100
   204.3 @@ -0,0 +1,21 @@
   204.4 +--[[--
   204.5 +trace.exectime{
   204.6 +  real = real,   -- physical time in seconds
   204.7 +  cpu  = cpu     -- CPU time in seconds
   204.8 +}
   204.9 +
  204.10 +This function is called automatically to log the execution time of the handling of a request.
  204.11 +
  204.12 +--]]--
  204.13 +
  204.14 +function trace.exectime(args)
  204.15 +  local real = args.real
  204.16 +  local cpu  = args.cpu
  204.17 +  if type(real) ~= "number" then
  204.18 +    error("Called trace.exectime{...} without numeric 'real' argument.")
  204.19 +  end
  204.20 +  if type(cpu) ~= "number" then
  204.21 +    error("Called trace.exectime{...} without numeric 'cpu' argument.")
  204.22 +  end
  204.23 +  trace._new_entry{ type = "exectime", real = args.real, cpu = args.cpu }
  204.24 +end
   205.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   205.2 +++ b/framework/env/trace/execution_return.lua	Sun Oct 25 12:00:00 2009 +0100
   205.3 @@ -0,0 +1,20 @@
   205.4 +--[[--
   205.5 +trace.execution_return{
   205.6 +  status = status        -- optional status
   205.7 +}
   205.8 +
   205.9 +This function is called automatically when returning from a view, action, configuration or filter for logging purposes.
  205.10 +
  205.11 +--]]--
  205.12 +
  205.13 +function trace.execution_return(args)
  205.14 +  local status
  205.15 +  if args then
  205.16 +    status = args.status
  205.17 +  end
  205.18 +  if status and type(status) ~= "string" then
  205.19 +    error("Status passed to trace.execution_return{...} is not a string.")
  205.20 +  end
  205.21 +  local closed_section = trace._close_section()
  205.22 +  closed_section.status = status
  205.23 +end
   206.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   206.2 +++ b/framework/env/trace/forward.lua	Sun Oct 25 12:00:00 2009 +0100
   206.3 @@ -0,0 +1,21 @@
   206.4 +--[[--
   206.5 +trace.forward{
   206.6 +  module = module,
   206.7 +  view   = view
   206.8 +}
   206.9 +
  206.10 +This function is called automatically by request.forward{...} for logging.
  206.11 +
  206.12 +--]]--
  206.13 +
  206.14 +function trace.forward(args)
  206.15 +  local module = args.module
  206.16 +  local view   = args.view
  206.17 +  if type(module) ~= "string" then
  206.18 +    error("No module string passed to trace.forward{...}.")
  206.19 +  end
  206.20 +  if type(view) ~= "string" then
  206.21 +    error("No view string passed to trace.forward{...}.")
  206.22 +  end
  206.23 +  trace._new_entry{ type = "forward", module = module, view = view }
  206.24 +end
   207.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   207.2 +++ b/framework/env/trace/redirect.lua	Sun Oct 25 12:00:00 2009 +0100
   207.3 @@ -0,0 +1,21 @@
   207.4 +--[[--
   207.5 +trace.redirect{
   207.6 +  module = module,
   207.7 +  view   = view
   207.8 +}
   207.9 +
  207.10 +This function is called automatically by request.redirect{...} for logging.
  207.11 +
  207.12 +--]]--
  207.13 +
  207.14 +function trace.redirect(args)
  207.15 +  local module = args.module
  207.16 +  local view   = args.view
  207.17 +  if type(module) ~= "string" then
  207.18 +    error("No module string passed to trace.redirect{...}.")
  207.19 +  end
  207.20 +  if type(view) ~= "string" then
  207.21 +    error("No view string passed to trace.redirect{...}.")
  207.22 +  end
  207.23 +  trace._new_entry{ type = "redirect", module = module, view = view }
  207.24 +end
   208.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   208.2 +++ b/framework/env/trace/render.lua	Sun Oct 25 12:00:00 2009 +0100
   208.3 @@ -0,0 +1,11 @@
   208.4 +--[[--
   208.5 +trace.render()
   208.6 +
   208.7 +This function renders a trace log and writes it into the active slot.
   208.8 +
   208.9 +--]]--
  208.10 +
  208.11 +function trace.render()
  208.12 +  -- TODO: check if all sections are closed?
  208.13 +  trace._render_sub_tree(trace._tree)
  208.14 +end
   209.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   209.2 +++ b/framework/env/trace/request.lua	Sun Oct 25 12:00:00 2009 +0100
   209.3 @@ -0,0 +1,37 @@
   209.4 +--[[--
   209.5 +trace.request{
   209.6 +  module = module,
   209.7 +  view   = view,
   209.8 +  action = action
   209.9 +}
  209.10 +
  209.11 +This function is called automatically to log which view or action has been requested by the web browser.
  209.12 +
  209.13 +--]]--
  209.14 +
  209.15 +function trace.request(args)
  209.16 +  local module       = args.module
  209.17 +  local view         = args.view
  209.18 +  local action       = args.action
  209.19 +  if type(module) ~= "string" then
  209.20 +    error("No module string passed to trace.request{...}.")
  209.21 +  end
  209.22 +  if view and action then
  209.23 +    error("Both view and action passed to trace.request{...}.")
  209.24 +  end
  209.25 +  if not (view or action) then
  209.26 +    error("Neither view nor action passed to trace.request{...}.")
  209.27 +  end
  209.28 +  if view and type(view) ~= "string" then
  209.29 +    error("No view string passed to trace.request{...}.")
  209.30 +  end
  209.31 +  if action and type(action) ~= "string" then
  209.32 +    error("No action string passed to trace.request{...}.")
  209.33 +  end
  209.34 +  trace._new_entry{
  209.35 +    type = "request",
  209.36 +    module       = args.module,
  209.37 +    view         = args.view,
  209.38 +    action       = args.action
  209.39 +  }
  209.40 +end
   210.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   210.2 +++ b/framework/env/trace/restore_slots.lua	Sun Oct 25 12:00:00 2009 +0100
   210.3 @@ -0,0 +1,11 @@
   210.4 +--[[--
   210.5 +trace.restore_slots{
   210.6 +}
   210.7 +
   210.8 +This function is used to log the event of restoring previously stored slot contents.
   210.9 +
  210.10 +--]]--
  210.11 +
  210.12 +function trace.restore_slots(args)
  210.13 +  trace._new_entry{ type = "restore_slots" }
  210.14 +end
   211.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   211.2 +++ b/framework/env/trace/sql.lua	Sun Oct 25 12:00:00 2009 +0100
   211.3 @@ -0,0 +1,27 @@
   211.4 +--[[--
   211.5 +trace.sql{
   211.6 +  command        = command,        -- executed SQL command as string
   211.7 +  error_position = error_position  -- optional position in bytes where an error occurred
   211.8 +}
   211.9 +
  211.10 +This command can be used to log SQL command execution. It is currently not invoked automatically.
  211.11 +
  211.12 +--]]--
  211.13 +
  211.14 +-- TODO: automatic use of this function?
  211.15 +
  211.16 +function trace.sql(args)
  211.17 +  local command = args.command
  211.18 +  local error_position = args.error_position
  211.19 +  if type(command) ~= "string" then
  211.20 +    error("No command string passed to trace.sql{...}.")
  211.21 +  end
  211.22 +  if error_position and type(error_position) ~= "number" then
  211.23 +    error("error_position must be a number.")
  211.24 +  end
  211.25 +  trace._new_entry{
  211.26 +    type = "sql",
  211.27 +    command = command,
  211.28 +    error_position = error_position
  211.29 +  }
  211.30 +end
   212.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   212.2 +++ b/framework/env/ui/autofield.lua	Sun Oct 25 12:00:00 2009 +0100
   212.3 @@ -0,0 +1,59 @@
   212.4 +--[[--
   212.5 +ui.autofield{
   212.6 +  name           = name,               -- field name (also used by default as HTML name)
   212.7 +  html_name      = html_name,          -- explicit HTML name to be used instead of 'name'
   212.8 +  value          = nihil.lift(value),  -- initial value, nil causes automatic lookup of value, use nihil.lift(nil) for nil
   212.9 +  container_attr = container_attr,     -- extra HTML attributes for the container (div) enclosing field and label
  212.10 +  attr           = attr,               -- extra HTML attributes for the field
  212.11 +  label          = label,              -- text to be used as label for the input field
  212.12 +  label_attr     = label_attr,         -- extra HTML attributes for the label
  212.13 +  readonly       = readonly_flag       -- set to true, to force read-only mode
  212.14 +  record         = record,             -- record to be used, defaults to record given to ui.form{...}
  212.15 +  ...                                  -- extra arguments for applicable ui.field.* helpers
  212.16 +}
  212.17 +
  212.18 +This function automatically selects a ui.field.* helper to be used for a field of a record.
  212.19 +
  212.20 +--]]--
  212.21 +
  212.22 +function ui.autofield(args)
  212.23 +  local args = table.new(args)
  212.24 +  assert(args.name, "ui.autofield{...} needs a field 'name'.")
  212.25 +  if not args.record then
  212.26 +    local slot_state = slot.get_state_table()
  212.27 +    if not slot_state then
  212.28 +      error("ui.autofield{...} was called without an explicit record to be used, and is also not called inside a form.")
  212.29 +    elseif not slot_state.form_record then
  212.30 +      error("ui.autofield{...} was called without an explicit record to be used, and the form does not have a record assigned either.")
  212.31 +    else
  212.32 +      args.record = slot_state.form_record
  212.33 +    end
  212.34 +  end
  212.35 +  local class = args.record._class
  212.36 +  assert(class, "Used ui.autofield{...} on a record with no class information stored in the '_class' attribute.")
  212.37 +  local fields, field_info, ui_field_type, ui_field_options
  212.38 +  fields = class.fields
  212.39 +  if fields then
  212.40 +    field_info = fields[args.name]
  212.41 +  end
  212.42 +  if field_info then
  212.43 +    ui_field_type    = field_info.ui_field_type
  212.44 +    ui_field_options = table.new(field_info.ui_field_options)
  212.45 +  end
  212.46 +  if not ui_field_type then
  212.47 +    ui_field_type = "text"
  212.48 +  end
  212.49 +  if not ui_field_options then
  212.50 +    ui_field_options = {}
  212.51 +  end
  212.52 +  local ui_field_func = ui.field[ui_field_type]
  212.53 +  if not ui_field_func then
  212.54 +    error(string.format("Did not find ui.field helper of type %q.", ui_field_type))
  212.55 +  end
  212.56 +  for key, value in pairs(ui_field_options) do
  212.57 +    if args[key] == nil then
  212.58 +      args[key] = value
  212.59 +    end
  212.60 +  end
  212.61 +  return ui_field_func(args)
  212.62 +end
   213.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   213.2 +++ b/framework/env/ui/container.lua	Sun Oct 25 12:00:00 2009 +0100
   213.3 @@ -0,0 +1,90 @@
   213.4 +--[[--
   213.5 +ui.container{
   213.6 +  auto_args = auto_args,
   213.7 +  attr          = attr,           -- HTML attributes for the surrounding div or fieldset
   213.8 +  label         = label,          -- text to be used as label
   213.9 +  label_for     = label_for,      -- DOM id of element to which the label should refer
  213.10 +  label_attr    = label_attr,     -- extra HTML attributes for a label tag
  213.11 +  legend        = legend,         -- text to be used as legend
  213.12 +  legend_attr   = legend_attr,    -- HTML attributes for a legend tag
  213.13 +  content_first = content_first,  -- set to true to place label or legend after the content
  213.14 +  content = function()
  213.15 +    ...                       --
  213.16 +  end
  213.17 +}
  213.18 +
  213.19 +This function encloses content in a div element (or a fieldset element, if 'legend' is given). An additional 'label' or 'legend' can be placed before the content or after the content. The argument 'auto_args' is set by other ui helper functions when calling ui.container automatically.
  213.20 +
  213.21 +--]]--
  213.22 +
  213.23 +function ui.container(args)
  213.24 +  local attr, label, label_attr, legend, legend_attr, content
  213.25 +  local auto_args = args.auto_args
  213.26 +  if auto_args then
  213.27 +    attr        = auto_args.container_attr
  213.28 +    label       = auto_args.label
  213.29 +    label_attr  = auto_args.label_attr
  213.30 +    legend      = auto_args.legend
  213.31 +    legend_attr = auto_args.legend_attr
  213.32 +    if label and auto_args.attr and auto_args.attr.id then
  213.33 +      label_attr = table.new(label_attr)
  213.34 +      label_attr["for"] = auto_args.attr.id
  213.35 +    end
  213.36 +  else
  213.37 +    attr        = args.attr
  213.38 +    label       = args.label
  213.39 +    label_attr  = args.label_attr or {}
  213.40 +    legend      = args.legend
  213.41 +    legend_attr = args.legend_attr
  213.42 +    content     = content
  213.43 +    if args.label_for then
  213.44 +      label_attr["for"] = args.label_for
  213.45 +    end
  213.46 +  end
  213.47 +  local content = args.content
  213.48 +  if label and not legend then
  213.49 +    return ui.tag {
  213.50 +      tag     = "div",
  213.51 +      attr    = attr,
  213.52 +      content = function()
  213.53 +        if not args.content_first then
  213.54 +          ui.tag{ tag = "label", attr = label_attr, content = label }
  213.55 +          slot.put(" ")
  213.56 +        end
  213.57 +        if type(content) == "function" then
  213.58 +          content()
  213.59 +        elseif content then
  213.60 +          slot.put(encode.html(content))
  213.61 +        end
  213.62 +        if args.content_first then
  213.63 +          slot.put(" ")
  213.64 +          ui.tag{ tag = "label", attr = label_attr, content = label }
  213.65 +        end
  213.66 +      end
  213.67 +    }
  213.68 +  elseif legend and not label then
  213.69 +    return ui.tag {
  213.70 +      tag     = "fieldset",
  213.71 +      attr    = attr,
  213.72 +      content = function()
  213.73 +        if not args.content_first then
  213.74 +          ui.tag{ tag = "legend", attr = legend_attr, content = legend }
  213.75 +          slot.put(" ")
  213.76 +        end
  213.77 +        if type(content) == "function" then
  213.78 +          content()
  213.79 +        elseif content then
  213.80 +          slot.put(encode.html(content))
  213.81 +        end
  213.82 +        if args.content_first then
  213.83 +          slot.put(" ")
  213.84 +          ui.tag{ tag = "legend", attr = legend_attr, content = legend }
  213.85 +        end
  213.86 +      end
  213.87 +    }
  213.88 +  elseif fieldset and label then
  213.89 +    error("ui.container{...} may either get a label or a legend.")
  213.90 +  else
  213.91 +    return ui.tag{ tag = "div", attr = attr, content = content }
  213.92 +  end
  213.93 +end
   214.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   214.2 +++ b/framework/env/ui/create_unique_id.lua	Sun Oct 25 12:00:00 2009 +0100
   214.3 @@ -0,0 +1,11 @@
   214.4 +--[[--
   214.5 +unique_id =            -- unique string to be used as an id in the DOM tree
   214.6 +ui.create_unique_id()
   214.7 +
   214.8 +This function returns a unique string to be used as an id in the DOM tree for elements.
   214.9 +
  214.10 +--]]--
  214.11 +
  214.12 +function ui.create_unique_id()
  214.13 +  return "unique_" .. multirand.string(32, "bcdfghjklmnpqrstvwxyz")
  214.14 +end
   215.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   215.2 +++ b/framework/env/ui/field/boolean.lua	Sun Oct 25 12:00:00 2009 +0100
   215.3 @@ -0,0 +1,128 @@
   215.4 +--[[--
   215.5 +ui.field.boolean{
   215.6 +  ...                        -- generic ui.field.* arguments, as described for ui.autofield{...}
   215.7 +  style       = style,       -- "radio" or "checkbox",
   215.8 +  nil_allowed = nil_allowed  -- set to true, if nil is allowed as third value
   215.9 +}
  215.10 +
  215.11 +This function inserts a field for boolean values in the active slot. For description of the generic field helper arguments, see help for ui.autofield{...}.
  215.12 +
  215.13 +--]]--
  215.14 +
  215.15 +function ui.field.boolean(args)
  215.16 +  local style = args.style
  215.17 +  if not style then
  215.18 +    if args.nil_allowed then
  215.19 +      style = "radio"
  215.20 +    else
  215.21 +      style = "checkbox"
  215.22 +    end
  215.23 +  end
  215.24 +  local extra_args = { fetch_value = true }
  215.25 +  if not args.readonly and args.style == "radio" then
  215.26 +    extra_args.disable_label_for_id = true
  215.27 +  end
  215.28 +  ui.form_element(args, extra_args, function(args)
  215.29 +    local value = args.value
  215.30 +    if value ~= true and value ~= false and value ~= nil then
  215.31 +      error("Boolean value must be true, false or nil.")
  215.32 +    end
  215.33 +    if value == nil then
  215.34 +      if args.nil_allowed then
  215.35 +        value = args.default
  215.36 +      else
  215.37 +        value = args.default or false
  215.38 +      end
  215.39 +    end
  215.40 +    if args.readonly then
  215.41 +      ui.tag{
  215.42 +        tag     = args.tag,
  215.43 +        attr    = args.attr,
  215.44 +        content = format.boolean(value, args.format_options)
  215.45 +      }
  215.46 +    elseif style == "radio" then
  215.47 +      local attr = table.new(args.attr)
  215.48 +      attr.type  = "radio"
  215.49 +      attr.name  = args.html_name
  215.50 +      attr.id    = ui.create_unique_id()
  215.51 +      attr.value = "1"
  215.52 +      if value == true then
  215.53 +        attr.checked = "checked"
  215.54 +      else
  215.55 +        attr.checked = nil
  215.56 +      end
  215.57 +      ui.container{
  215.58 +        attr          = { class = "ui_radio_div" },
  215.59 +        label         = args.true_as or "Yes",  -- TODO: localize
  215.60 +        label_for     = attr.id,
  215.61 +        label_attr    = { class = "ui_radio_label" },
  215.62 +        content_first = true,
  215.63 +        content       = function()
  215.64 +          ui.tag{ tag  = "input", attr = attr }
  215.65 +        end
  215.66 +      }
  215.67 +      attr.id    = ui.create_unique_id()
  215.68 +      attr.value = "0"
  215.69 +      if value == false then
  215.70 +        attr.checked = "1"
  215.71 +      else
  215.72 +        attr.checked = nil
  215.73 +      end
  215.74 +      ui.container{
  215.75 +        attr          = { class = "ui_radio_div" },
  215.76 +        label         = args.false_as or "No",  -- TODO: localize
  215.77 +        label_for     = attr.id,
  215.78 +        label_attr    = { class = "ui_radio_label" },
  215.79 +        content_first = true,
  215.80 +        content       = function()
  215.81 +          ui.tag{ tag  = "input", attr = attr }
  215.82 +        end
  215.83 +      }
  215.84 +      if args.nil_allowed then
  215.85 +        attr.id    = ui.create_unique_id()
  215.86 +        attr.value = ""
  215.87 +        if value == nil then
  215.88 +          attr.checked = "1"
  215.89 +        else
  215.90 +          attr.checked = nil
  215.91 +        end
  215.92 +        ui.container{
  215.93 +          attr          = { class = "ui_radio_div" },
  215.94 +          label         = args.nil_as or "N/A",  -- TODO: localize
  215.95 +          label_for     = attr.id,
  215.96 +          label_attr    = { class = "ui_radio_label" },
  215.97 +          content_first = true,
  215.98 +          content       = function()
  215.99 +            ui.tag{ tag  = "input", attr = attr }
 215.100 +          end
 215.101 +        }
 215.102 +      end
 215.103 +      ui.hidden_field{
 215.104 +        name = args.html_name .. "__format", value = "boolean"
 215.105 +      }
 215.106 +    elseif style == "checkbox" then
 215.107 +      if args.nil_allowed then
 215.108 +        error("Checkboxes do not support nil values.")
 215.109 +      end
 215.110 +      local attr = table.new(args.attr)
 215.111 +      attr.type  = "checkbox"
 215.112 +      attr.name  = args.html_name
 215.113 +      attr.value = "1"
 215.114 +      if value then
 215.115 +        attr.checked = "checked"
 215.116 +      else
 215.117 +        attr.checked = nil
 215.118 +      end
 215.119 +      ui.tag{ tag = "input", attr = attr }
 215.120 +      ui.hidden_field{
 215.121 +        name = args.html_name .. "__format",
 215.122 +        value = encode.format_info(
 215.123 +          "boolean",
 215.124 +          { true_as = "1", false_as = "" }
 215.125 +        )
 215.126 +      }
 215.127 +    else
 215.128 +      error("'style' attribute for ui.field.boolean{...} must be set to \"radio\", \"checkbox\" or nil.")
 215.129 +    end
 215.130 +  end)
 215.131 +end
   216.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   216.2 +++ b/framework/env/ui/field/date.lua	Sun Oct 25 12:00:00 2009 +0100
   216.3 @@ -0,0 +1,84 @@
   216.4 +--[[--
   216.5 +ui.field.date{
   216.6 +  ...           -- generic ui.field.* arguments, as described for ui.autofield{...}
   216.7 +}
   216.8 +
   216.9 +This function inserts a field for dates in the active slot. If the JavaScript library "gregor.js" has been loaded, a rich input field is used. For description of the generic field helper arguments, see help for ui.autofield{...}.
  216.10 +
  216.11 +--]]--
  216.12 +
  216.13 +function ui.field.date(args)
  216.14 +  ui.form_element(args, {fetch_value = true}, function(args)
  216.15 +    local value_string = format.date(args.value, args.format_options)
  216.16 +    if args.readonly then
  216.17 +      ui.tag{ tag = args.tag, attr = args.attr, content = value_string }
  216.18 +    else
  216.19 +      local fallback_data = slot.use_temporary(function()
  216.20 +        local attr = table.new(args.attr)
  216.21 +        attr.type  = "text"
  216.22 +        attr.name  = args.html_name
  216.23 +        attr.value = value_string
  216.24 +        attr.class = attr.class or "ui_field_date"
  216.25 +        ui.tag{ tag  = "input", attr = attr }
  216.26 +        ui.hidden_field{
  216.27 +          name  = args.html_name .. "__format",
  216.28 +          value = encode.format_info("date", args.format_options)
  216.29 +        }
  216.30 +      end)
  216.31 +      local user_field_id, hidden_field_id
  216.32 +      local helper_data = slot.use_temporary(function()
  216.33 +        local attr = table.new(args.attr)
  216.34 +        user_field_id = attr.id or ui.create_unique_id()
  216.35 +        hidden_field_id = ui.create_unique_id()
  216.36 +        attr.id    = user_field_id
  216.37 +        attr.type  = "text"
  216.38 +        attr.class = attr.class or "ui_field_date"
  216.39 +        ui.tag{ tag = "input", attr = attr }
  216.40 +        local attr = table.new(args.attr)
  216.41 +        attr.id    = hidden_field_id
  216.42 +        attr.type  = "hidden"
  216.43 +        attr.name  = args.html_name
  216.44 +        attr.value = atom.dump(args.value)  -- extra safety for JS failure
  216.45 +        ui.tag{
  216.46 +          tag = "input",
  216.47 +          attr = {
  216.48 +            id   = hidden_field_id,
  216.49 +            type = "hidden",
  216.50 +            name = args.html_name
  216.51 +          }
  216.52 +        }
  216.53 +      end)
  216.54 +      -- TODO: localization
  216.55 +      ui.script{
  216.56 +        noscript = fallback_data,
  216.57 +        type     = "text/javascript",
  216.58 +        content  = function()
  216.59 +          slot.put(
  216.60 +            "if (gregor_addGui == null) document.write(",
  216.61 +            encode.json(fallback_data),
  216.62 +            "); else { document.write(",
  216.63 +            encode.json(helper_data),
  216.64 +            "); gregor_addGui({element_id: ",
  216.65 +            encode.json(user_field_id),
  216.66 +            ", month_names: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], weekday_names: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'], week_numbers: 'left', format: 'DD.MM.YYYY', selected: "
  216.67 +          )
  216.68 +          if (args.value) then
  216.69 +            slot.put(
  216.70 +              "{year: ", tostring(args.value.year),
  216.71 +              ", month: ", tostring(args.value.month),
  216.72 +              ", day: ", tostring(args.value.day),
  216.73 +              "}"
  216.74 +            )
  216.75 +          else
  216.76 +            slot.put("null")
  216.77 +          end
  216.78 +          slot.put(
  216.79 +           ", select_callback: function(date) { document.getElementById(",
  216.80 +           encode.json(hidden_field_id),
  216.81 +           ").value = (date == null) ? '' : date.iso_string; } } ) }"
  216.82 +          )
  216.83 +        end
  216.84 +      }
  216.85 +    end
  216.86 +  end)
  216.87 +end
   217.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   217.2 +++ b/framework/env/ui/field/hidden.lua	Sun Oct 25 12:00:00 2009 +0100
   217.3 @@ -0,0 +1,20 @@
   217.4 +--[[--
   217.5 +ui.field.hidden{
   217.6 +  ...             -- generic ui.field.* arguments, as described for ui.autofield{...}
   217.7 +}
   217.8 +
   217.9 +This function inserts a hidden form field in the active slot. It is a high level function compared to ui.hidden_field{...}. If called inside a read-only form, then this function does nothing.
  217.10 +
  217.11 +--]]--
  217.12 +
  217.13 +function ui.field.hidden(args)
  217.14 +  ui.form_element(args, {fetch_value = true}, function(args)
  217.15 +    if not args.readonly then
  217.16 +      ui.hidden_field{
  217.17 +        attr  = args.attr,
  217.18 +        name  = args.html_name,
  217.19 +        value = args.value
  217.20 +      }
  217.21 +    end
  217.22 +  end)
  217.23 +end
   218.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   218.2 +++ b/framework/env/ui/field/integer.lua	Sun Oct 25 12:00:00 2009 +0100
   218.3 @@ -0,0 +1,28 @@
   218.4 +--[[--
   218.5 +ui.field.integer{
   218.6 +  ...                              -- generic ui.field.* arguments, as described for ui.autofield{...}
   218.7 +  format_options = format_options  -- format options for format.decimal
   218.8 +}
   218.9 +
  218.10 +This function inserts a field for an integer in the active slot. For description of the generic field helper arguments, see help for ui.autofield{...}.
  218.11 +
  218.12 +--]]--
  218.13 +
  218.14 +function ui.field.integer(args)
  218.15 +  ui.form_element(args, {fetch_value = true}, function(args)
  218.16 +    local value_string = format.decimal(args.value, args.format_options)
  218.17 +    if args.readonly then
  218.18 +      ui.tag{ tag = args.tag, attr = args.attr, content = value_string }
  218.19 +    else
  218.20 +      local attr = table.new(args.attr)
  218.21 +      attr.type  = "text"
  218.22 +      attr.name  = args.html_name
  218.23 +      attr.value = value_string
  218.24 +      ui.tag{ tag  = "input", attr = attr }
  218.25 +      ui.hidden_field{
  218.26 +        name  = args.html_name .. "__format",
  218.27 +        value = encode.format_info("decimal", args.format_options)
  218.28 +      }
  218.29 +    end
  218.30 +  end)
  218.31 +end
   219.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   219.2 +++ b/framework/env/ui/field/password.lua	Sun Oct 25 12:00:00 2009 +0100
   219.3 @@ -0,0 +1,23 @@
   219.4 +--[[--
   219.5 +ui.field.password{
   219.6 +  ...                        -- generic ui.field.* arguments, as described for ui.autofield{...}
   219.7 +}
   219.8 +
   219.9 +This function inserts a field for a password in the active slot. For read-only forms this function does nothing. For description of the generic field helper arguments, see help for ui.autofield{...}.
  219.10 +
  219.11 +--]]--
  219.12 +
  219.13 +function ui.field.password(args)
  219.14 +  ui.form_element(args, {fetch_value = true}, function(args)
  219.15 +    local value_string = atom.dump(args.value)
  219.16 +    if args.readonly then
  219.17 +      -- nothing
  219.18 +    else
  219.19 +      local attr = table.new(args.attr)
  219.20 +      attr.type  = "password"
  219.21 +      attr.name  = args.html_name
  219.22 +      attr.value = value_string
  219.23 +      ui.tag{ tag  = "input", attr = attr }
  219.24 +    end
  219.25 +  end)
  219.26 +end
   220.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   220.2 +++ b/framework/env/ui/field/select.lua	Sun Oct 25 12:00:00 2009 +0100
   220.3 @@ -0,0 +1,68 @@
   220.4 +--[[--
   220.5 +ui.field.select{
   220.6 +  ...                                 -- generic ui.field.* arguments, as described for ui.autofield{...}
   220.7 +  foreign_records = foreign_records,  -- list of records to be chosen from, or function returning such a list
   220.8 +  foreign_id      = foreign_id,       -- name of id field in foreign records
   220.9 +  foreign_name    = foreign_name,     -- name of field to be used as name in foreign records
  220.10 +  format_options  = format_options    -- format options for format.string
  220.11 +}
  220.12 +
  220.13 +This function inserts a select field in the active slot. For description of the generic field helper arguments, see help for ui.autofield{...}.
  220.14 +
  220.15 +--]]--
  220.16 +
  220.17 +function ui.field.select(args)
  220.18 +  ui.form_element(args, {fetch_value = true}, function(args)
  220.19 +    local foreign_records = args.foreign_records
  220.20 +    if type(foreign_records) == "function" then
  220.21 +      foreign_records = foreign_records(args.record)
  220.22 +    end
  220.23 +    if args.readonly then
  220.24 +      local name
  220.25 +      for idx, record in ipairs(foreign_records) do
  220.26 +        if record[args.foreign_id] == args.value then
  220.27 +          name = record[args.foreign_name]
  220.28 +          break
  220.29 +        end
  220.30 +      end
  220.31 +      ui.tag{
  220.32 +        tag     = args.tag,
  220.33 +        attr    = args.attr, 
  220.34 +        content = format.string(name, args.format_options)
  220.35 +      }
  220.36 +    else
  220.37 +      local attr = table.new(args.attr)
  220.38 +      attr.name  = args.html_name
  220.39 +      ui.tag{
  220.40 +        tag     = "select",
  220.41 +        attr    = attr,
  220.42 +        content = function()
  220.43 +          if args.nil_as then
  220.44 +            ui.tag{
  220.45 +              tag     = "option",
  220.46 +              attr    = { value = "" },
  220.47 +              content = format.string(
  220.48 +                args.nil_as,
  220.49 +                args.format_options
  220.50 +              )
  220.51 +            }
  220.52 +          end
  220.53 +          for idx, record in ipairs(foreign_records) do
  220.54 +            local key = record[args.foreign_id]
  220.55 +            ui.tag{
  220.56 +              tag     = "option",
  220.57 +              attr    = {
  220.58 +                value    = key,
  220.59 +                selected = ((key == args.value) and "selected" or nil)
  220.60 +              },
  220.61 +              content = format.string(
  220.62 +                record[args.foreign_name],
  220.63 +                args.format_options
  220.64 +              )
  220.65 +            }
  220.66 +          end
  220.67 +        end
  220.68 +      }
  220.69 +    end
  220.70 +  end)
  220.71 +end
   221.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   221.2 +++ b/framework/env/ui/field/text.lua	Sun Oct 25 12:00:00 2009 +0100
   221.3 @@ -0,0 +1,28 @@
   221.4 +--[[--
   221.5 +ui.field.text{
   221.6 +  ...                              -- generic ui.field.* arguments, as described for ui.autofield{...}
   221.7 +  format_options = format_options  -- format options for format.string
   221.8 +}
   221.9 +
  221.10 +This function inserts a field for a text in the active slot. For description of the generic field helper arguments, see help for ui.autofield{...}.
  221.11 +
  221.12 +--]]--
  221.13 +
  221.14 +function ui.field.text(args)
  221.15 +  ui.form_element(args, {fetch_value = true}, function(args)
  221.16 +    local value_string = format.string(args.value, args.format_options)
  221.17 +    if args.readonly then
  221.18 +      ui.tag{ tag = args.tag, attr = args.attr, content = value_string }
  221.19 +    else
  221.20 +      local attr = table.new(args.attr)
  221.21 +      attr.name  = args.html_name
  221.22 +      if args.multiline then
  221.23 +        ui.tag { tag = "textarea", attr = attr, content = value_string }
  221.24 +      else
  221.25 +        attr.type  = "text"
  221.26 +        attr.value = value_string
  221.27 +        ui.tag{ tag  = "input", attr = attr }
  221.28 +      end
  221.29 +    end
  221.30 +  end)
  221.31 +end
   222.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   222.2 +++ b/framework/env/ui/form.lua	Sun Oct 25 12:00:00 2009 +0100
   222.3 @@ -0,0 +1,99 @@
   222.4 +--[[--
   222.5 +ui.form{
   222.6 +  record    = record,     -- optional record to be used 
   222.7 +  read_only = read_only,  -- set to true, if form should be read-only (no submit button)
   222.8 +  external  = external,   -- external URL to be used as HTML form action
   222.9 +  module    = module,     -- module name to be used for HTML form action
  222.10 +  view      = view,       -- view name   to be used for HTML form action
  222.11 +  action    = action,     -- action name to be used for HTML form action
  222.12 +  routing = {
  222.13 +    default = {           -- default routing for called action
  222.14 +      mode   = mode,      -- "forward" or "redirect"
  222.15 +      module = module,    -- optional module name, defaults to current module
  222.16 +      view   = view,      -- view name
  222.17 +      id     = id,        -- optional id to be passed to the view
  222.18 +      params = params     -- optional params to be passed to the view
  222.19 +    },
  222.20 +    ok    = { ... },      -- routing when "ok"    is returned by the called action
  222.21 +    error = { ... },      -- routing when "error" is returned by the called action
  222.22 +    ...   = { ... }       -- routing when "..."   is returned by the called action
  222.23 +  }
  222.24 +  content = function()
  222.25 +    ...                   -- code creating the contents of the form
  222.26 +  end
  222.27 +}
  222.28 +
  222.29 +This functions creates a web form, which encloses the content created by the given 'content' function. When a 'record' is given, ui.field.* helper functions will be able to automatically determine field values by using the given record. If 'read_only' is set to true, then a call of ui.submit{...} will be ignored, and ui.field.* helper functions will behave differently.
  222.30 +
  222.31 +--]]--
  222.32 +
  222.33 +function ui.form(args)
  222.34 +  local args = args or {}
  222.35 +  local slot_state = slot.get_state_table()
  222.36 +  local old_record   = slot_state.form_record
  222.37 +  local old_readonly = slot_state.form_readonly
  222.38 +  slot_state.form_record = args.record
  222.39 +  if args.readonly then
  222.40 +    slot_state.form_readonly = true
  222.41 +    ui.container{ attr = args.attr, content = args.content }
  222.42 +  else
  222.43 +    slot_state.form_readonly = false
  222.44 +    local params = table.new(args.params)
  222.45 +    local routing_default_given = false
  222.46 +    if args.routing then
  222.47 +      for status, settings in pairs(args.routing) do
  222.48 +        if status == "default" then
  222.49 +          routing_default_given = true
  222.50 +        end
  222.51 +        local module = settings.module or args.module or request.get_module()
  222.52 +        assert(settings.mode, "No mode specified in routing entry.")
  222.53 +        assert(settings.view, "No view specified in routing entry.")
  222.54 +        params["_webmcp_routing." .. status .. ".mode"]   = settings.mode
  222.55 +        params["_webmcp_routing." .. status .. ".module"] = module
  222.56 +        params["_webmcp_routing." .. status .. ".view"]   = settings.view
  222.57 +        params["_webmcp_routing." .. status .. ".id"]     = settings.id
  222.58 +        if settings.params then
  222.59 +          for key, value in pairs(settings.params) do
  222.60 +            params["_webmcp_routing." .. status .. ".params." .. key] = value
  222.61 +          end
  222.62 +        end
  222.63 +      end
  222.64 +    end
  222.65 +    if not routing_default_given then
  222.66 +      params["_webmcp_routing.default.mode"]   = "forward"
  222.67 +      params["_webmcp_routing.default.module"] = request.get_module()
  222.68 +      params["_webmcp_routing.default.view"]   = request.get_view()
  222.69 +    end
  222.70 +    params._webmcp_csrf_secret = request.get_csrf_secret()
  222.71 +    local attr = table.new(args.attr)
  222.72 +    attr.action = encode.url{
  222.73 +      external  = args.external,
  222.74 +      module    = args.module or request.get_module(),
  222.75 +      view      = args.view,
  222.76 +      action    = args.action,
  222.77 +    }
  222.78 +    attr.method = args.method and string.upper(args.method) or "POST"
  222.79 +    if slot_state.form_opened then
  222.80 +      error("Cannot open a non-readonly form inside a non-readonly form.")
  222.81 +    end
  222.82 +    slot_state.form_opened = true
  222.83 +    ui.tag {
  222.84 +      tag     = "form",
  222.85 +      attr    = attr,
  222.86 +      content = function()
  222.87 +        if args.id then
  222.88 +          ui.hidden_field{ name = "_webmcp_id", value = args.id }
  222.89 +        end
  222.90 +        for key, value in pairs(params) do
  222.91 +          ui.hidden_field{ name = key, value = value }
  222.92 +        end
  222.93 +        if args.content then
  222.94 +          args.content()
  222.95 +        end
  222.96 +      end
  222.97 +    }
  222.98 +    slot_state.form_opened = false
  222.99 +  end
 222.100 +  slot_state.form_readonly = old_readonly
 222.101 +  slot_state.form_record   = old_record
 222.102 +end
   223.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   223.2 +++ b/framework/env/ui/form_element.lua	Sun Oct 25 12:00:00 2009 +0100
   223.3 @@ -0,0 +1,81 @@
   223.4 +--[[--
   223.5 +ui.form_element(
   223.6 +  args,                                                -- external arguments
   223.7 +  {                                                    -- options for this function call
   223.8 +    fetch_value          = fetch_value_flag,           -- true causes automatic determination of args.value, if nil
   223.9 +    fetch_record         = fetch_record_flag,          -- true causes automatic determination of args.record, if nil
  223.10 +    disable_label_for_id = disable_label_for_id_flag,  -- true suppresses automatic setting of args.attr.id for a HTML label_for reference
  223.11 +  },
  223.12 +  function(args)
  223.13 +    ...                                                -- program code
  223.14 +  end
  223.15 +)
  223.16 +
  223.17 +This function helps other form helpers by preprocessing arguments passed to the helper, e.g. fetching a value from a record stored in a state-table of the currently active slot.
  223.18 +
  223.19 +--]]--
  223.20 +
  223.21 +-- TODO: better documentation
  223.22 +
  223.23 +function ui.form_element(args, extra_args, func)
  223.24 +  local args = table.new(args)
  223.25 +  if extra_args then
  223.26 +    for key, value in pairs(extra_args) do
  223.27 +      args[key] = value
  223.28 +    end
  223.29 +  end
  223.30 +  local slot_state = slot.get_state_table()
  223.31 +  args.html_name = args.html_name or args.name
  223.32 +  if args.fetch_value then
  223.33 +    if args.value == nil then
  223.34 +      if not args.record and slot_state then
  223.35 +        args.record = slot_state.form_record
  223.36 +      end
  223.37 +      if args.record then
  223.38 +        args.value = args.record[args.name]
  223.39 +      end
  223.40 +    else
  223.41 +      args.value = nihil.lower(args.value)
  223.42 +    end
  223.43 +  elseif args.fetch_record then
  223.44 +    if not args.record and slot_state then
  223.45 +      args.record = slot_state.form_record
  223.46 +    end
  223.47 +  end
  223.48 +  if
  223.49 +    args.html_name and
  223.50 +    not args.readonly and
  223.51 +    slot_state.form_readonly == false
  223.52 +  then
  223.53 +    args.readonly = false
  223.54 +    local prefix
  223.55 +    if args.html_name_prefix == nil then
  223.56 +      prefix = slot_state.html_name_prefix
  223.57 +    else
  223.58 +      prefix = args.html_name_prefix
  223.59 +    end
  223.60 +    if prefix then
  223.61 +      args.html_name = prefix .. args.html_name
  223.62 +    end
  223.63 +  else
  223.64 +    args.readonly = true
  223.65 +  end
  223.66 +  if args.label then
  223.67 +    if not args.disable_label_for_id then
  223.68 +      if not args.attr then
  223.69 +        args.attr = { id = ui.create_unique_id() }
  223.70 +      elseif not args.attr.id then
  223.71 +        args.attr.id = ui.create_unique_id()
  223.72 +      end
  223.73 +    end
  223.74 +    if not args.label_attr then
  223.75 +      args.label_attr = { class = "ui_field_label" }
  223.76 +    elseif not args.label_attr.class then
  223.77 +      args.label_attr.class = "ui_field_label"
  223.78 +    end
  223.79 +  end
  223.80 +  ui.container{
  223.81 +    auto_args = args,
  223.82 +    content = function() return func(args) end
  223.83 +  }
  223.84 +end
   224.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   224.2 +++ b/framework/env/ui/heading.lua	Sun Oct 25 12:00:00 2009 +0100
   224.3 @@ -0,0 +1,18 @@
   224.4 +--[[--
   224.5 +ui.heading{
   224.6 +  level   = level,   -- level from 1 to 6, defaults to 1
   224.7 +  attr    = attr,    -- extra HTML attributes
   224.8 +  content = content  -- string or function for content
   224.9 +}
  224.10 +
  224.11 +This function inserts a heading into the active slot.
  224.12 +
  224.13 +--]]--
  224.14 +
  224.15 +function ui.heading(args)
  224.16 +  return ui.tag{
  224.17 +    tag     = "h" .. (args.level or 1),
  224.18 +    attr    = args.attr,
  224.19 +    content = args.content
  224.20 +  }
  224.21 +end
   225.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   225.2 +++ b/framework/env/ui/hidden_field.lua	Sun Oct 25 12:00:00 2009 +0100
   225.3 @@ -0,0 +1,19 @@
   225.4 +--[[--
   225.5 +ui.hidden_field{
   225.6 +  name = name,    -- HTML name
   225.7 +  value = value,  -- value
   225.8 +  attr  = attr    -- extra HTML attributes
   225.9 +}
  225.10 +
  225.11 +This function inserts a hidden form field in the active slot. It is a low level function compared to ui.field.hidden{...}.
  225.12 +
  225.13 +--]]--
  225.14 +
  225.15 +function ui.hidden_field(args)
  225.16 +  local args = args or {}
  225.17 +  local attr = table.new(args.attr)
  225.18 +  attr.type  = "hidden"
  225.19 +  attr.name  = args.name
  225.20 +  attr.value = atom.dump(args.value)
  225.21 +  return ui.tag{ tag  = "input", attr = attr }
  225.22 +end
   226.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   226.2 +++ b/framework/env/ui/image.lua	Sun Oct 25 12:00:00 2009 +0100
   226.3 @@ -0,0 +1,13 @@
   226.4 +function ui.image(args)
   226.5 +  local args = args or {}
   226.6 +  local attr = table.new(args.attr)
   226.7 +  attr.src = encode.url{
   226.8 +    external  = args.external,
   226.9 +    static    = args.static,
  226.10 +    module    = args.module or request.get_module(),
  226.11 +    view      = args.view,
  226.12 +    id        = args.id,
  226.13 +    params    = args.params,
  226.14 +  }
  226.15 +  return ui.tag{ tag = "img", attr = attr }
  226.16 +end
   227.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   227.2 +++ b/framework/env/ui/link.lua	Sun Oct 25 12:00:00 2009 +0100
   227.3 @@ -0,0 +1,90 @@
   227.4 +--[[--
   227.5 +ui.link{
   227.6 +  external  = external,   -- external URL
   227.7 +  static    = static,     -- URL relative to the static file directory
   227.8 +  module    = module,     -- module name
   227.9 +  view      = view,       -- view name
  227.10 +  action    = action,     -- action name
  227.11 +  id        = id,         -- optional id to be passed to the view or action to select a particular data record
  227.12 +  params    = params,     -- optional parameters to be passed to the view or action
  227.13 +  routing   = routing,    -- optional routing information for action links, as described for ui.form{...}
  227.14 +  text      = text,       -- link text
  227.15 +  content   = content     -- alternative name for 'text' option, preferred for functions
  227.16 +}
  227.17 +
  227.18 +This function inserts a link into the active slot. It may be either an internal application link ('module' given and 'view' or 'action' given), or a link to an external web page ('external' given), or a link to a file in the static file directory of the application ('static' given).
  227.19 +
  227.20 +--]]--
  227.21 +
  227.22 +function ui.link(args)
  227.23 +  local args = args or {}
  227.24 +  local content = args.text or args.content  -- TODO: decide which argument name to use
  227.25 +  assert(content, "ui.link{...} needs a text.")
  227.26 +  local function wrapped_content()
  227.27 +    -- TODO: icon/image
  227.28 +    if type(content) == "function" then
  227.29 +      content()
  227.30 +    else
  227.31 +      slot.put(encode.html(content))
  227.32 +    end
  227.33 +  end
  227.34 +  if args.action then
  227.35 +    local form_attr   = table.new(args.form_attr)
  227.36 +    local form_id
  227.37 +    if form_attr.id then
  227.38 +      form_id = form_attr.id
  227.39 +    else
  227.40 +      form_id = ui.create_unique_id()
  227.41 +    end
  227.42 +    local quoted_form_id = encode.json(form_id)
  227.43 +    form_attr.id      = form_id
  227.44 +    local a_attr      = table.new(args.attr)
  227.45 +    a_attr.href       = "#"
  227.46 +    a_attr.onclick    =
  227.47 +      "var f = document.getElementById(" .. quoted_form_id .. "); if (! f.onsubmit || f.onsubmit() != false) { f.submit() };"
  227.48 +    ui.form{
  227.49 +      external = args.external,
  227.50 +      module   = args.module or request.get_module(),
  227.51 +      action   = args.action,
  227.52 +      id       = args.id,
  227.53 +      params   = args.params,
  227.54 +      routing  = args.routing,
  227.55 +      attr     = form_attr,
  227.56 +      content  = function()
  227.57 +        ui.submit{ text = args.text, attr = args.submit_attr }
  227.58 +      end
  227.59 +    }
  227.60 +    ui.script{
  227.61 +      type = "text/javascript",
  227.62 +      script = (
  227.63 +        "document.getElementById(" ..
  227.64 +        quoted_form_id ..
  227.65 +        ").style.display = 'none'; document.write(" ..
  227.66 +        encode.json(
  227.67 +          slot.use_temporary(
  227.68 +            function()
  227.69 +              ui.tag{
  227.70 +                tag     = "a",
  227.71 +                attr    = a_attr,
  227.72 +                content = wrapped_content
  227.73 +              }
  227.74 +            end
  227.75 +          )
  227.76 +        ) ..
  227.77 +        ");"
  227.78 +      )
  227.79 +    }
  227.80 +  else
  227.81 +    -- TODO: support content function
  227.82 +    local a_attr = table.new(args.attr)
  227.83 +    a_attr.href = encode.url{
  227.84 +      external  = args.external,
  227.85 +      static    = args.static,
  227.86 +      module    = args.module or request.get_module(),
  227.87 +      view      = args.view,
  227.88 +      id        = args.id,
  227.89 +      params    = args.params,
  227.90 +    }
  227.91 +    return ui.tag{ tag  = "a", attr = a_attr, content = wrapped_content }
  227.92 +  end
  227.93 +end
   228.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   228.2 +++ b/framework/env/ui/list.lua	Sun Oct 25 12:00:00 2009 +0100
   228.3 @@ -0,0 +1,227 @@
   228.4 +--[[--
   228.5 +ui.list{
   228.6 +  label   = list_label,  -- optional label for the whole list
   228.7 +  style   = style,       -- "table", "ulli" or "div"
   228.8 +  prefix  = prefix,      -- prefix for HTML field names
   228.9 +  records = records,     -- array of records to be displayed as rows in the list
  228.10 +  columns = {
  228.11 +    {
  228.12 +      label          = column_label,    -- label for the column
  228.13 +      label_attr     = label_attr,      -- table with HTML attributes for the heading cell or div
  228.14 +      field_attr     = field_attr,      -- table with HTML attributes for the data cell or div
  228.15 +      name           = name,            -- name of the field in each record
  228.16 +      html_name      = html_name,       -- optional html-name for writable fields (defaults to name)
  228.17 +      ui_field_type  = ui_field_type,   -- name of the ui.field.* function to use
  228.18 +      ....,                             -- other options for the given ui.field.* functions
  228.19 +      format         = format,          -- name of the format function to be used (if not using ui_field_type)
  228.20 +      format_options = format_options,  -- options to be passed to the format function
  228.21 +      content        = content          -- function to output field data per record (ignoring name, format, ...)
  228.22 +    },
  228.23 +    { ... },
  228.24 +    ...
  228.25 +  }
  228.26 +}
  228.27 +
  228.28 +This function takes an array of records to be displayed in a list. The whole list may have a label. The style's "table" (for <table>), "ulli" (for <ul><li>) and "div" (just using <div> tags) are supported. For each column several options must be specified.
  228.29 +
  228.30 +--]]--
  228.31 +
  228.32 +-- TODO: documentation of the prefix option
  228.33 +-- TODO: check short descriptions of fields in documentation
  228.34 +-- TODO: field_attr is used for the OUTER html tag's attributes, while attr is used for the INNER html tag's attributes (produced by ui.field.*), is that okay?
  228.35 +-- TODO: use field information of record class, if no columns are given
  228.36 +-- TODO: callback to set row attr's for a specific row
  228.37 +
  228.38 +function ui.list(args)
  228.39 +  local args = args or {}
  228.40 +  local label     = args.label
  228.41 +  local list_type = args.style or "table"
  228.42 +  local prefix    = args.prefix
  228.43 +  local records   = assert(args.records, "ui.list{...} needs records.")
  228.44 +  local columns   = assert(args.columns, "ui.list{...} needs column definitions.")
  228.45 +  local outer_attr = table.new(args.attr)
  228.46 +  local header_existent = false
  228.47 +  for idx, column in ipairs(columns) do
  228.48 +    if column.label then
  228.49 +      header_existent = true
  228.50 +      break
  228.51 +    end
  228.52 +  end
  228.53 +  local slot_state = slot.get_state_table()
  228.54 +  local outer_tag, head_tag, head_tag2, label_tag, body_tag, row_tag
  228.55 +  if list_type == "table" then
  228.56 +    outer_tag = "table"
  228.57 +    head_tag  = "thead"
  228.58 +    head_tag2 = "tr"
  228.59 +    label_tag = "th"
  228.60 +    body_tag  = "tbody"
  228.61 +    row_tag   = "tr"
  228.62 +    field_tag = "td"
  228.63 +  elseif list_type == "ulli" then
  228.64 +    outer_tag = "div"
  228.65 +    head_tag  = "div"
  228.66 +    label_tag = "div"
  228.67 +    body_tag  = "ul"
  228.68 +    row_tag   = "li"
  228.69 +    field_tag = "td"
  228.70 +  elseif list_type == "div" then
  228.71 +    outer_tag = "div"
  228.72 +    head_tag  = "div"
  228.73 +    label_tag = "div"
  228.74 +    body_tag  = "div"
  228.75 +    row_tag   = "div"
  228.76 +    field_tag = "div"
  228.77 +  else
  228.78 +    error("Unknown list type specified for ui.list{...}.")
  228.79 +  end
  228.80 +  outer_attr.class = outer_attr.class or "ui_list"
  228.81 +  ui.container{
  228.82 +    auto_args = args,
  228.83 +    content   = function()
  228.84 +      ui.tag{
  228.85 +        tag     = outer_tag,
  228.86 +        attr    = outer_attr,
  228.87 +        content = function()
  228.88 +          if header_existent then
  228.89 +            ui.tag{
  228.90 +              tag     = head_tag,
  228.91 +              attr    = { class = "ui_list_head" },
  228.92 +              content = function()
  228.93 +                local function header_content()
  228.94 +                  for idx, column in ipairs(columns) do
  228.95 +                    if column.ui_field_type ~= "hidden" then
  228.96 +                      local label_attr = table.new(column.label_attr)
  228.97 +                      label_attr.class =
  228.98 +                        label_attr.class or { class = "ui_list_label" }
  228.99 +                      ui.tag{
 228.100 +                        tag     = label_tag,
 228.101 +                        attr    = label_attr,
 228.102 +                        content = column.label or ""
 228.103 +                      }
 228.104 +                    end
 228.105 +                  end
 228.106 +                end
 228.107 +                if head_tag2 then
 228.108 +                  ui.tag{ tag = head_tag2, content = header_content }
 228.109 +                else
 228.110 +                  header_content()
 228.111 +                end
 228.112 +              end
 228.113 +            }
 228.114 +          end
 228.115 +          ui.tag{
 228.116 +            tag     = body_tag,
 228.117 +            attr    = { class = "ui_list_body" },
 228.118 +            content = function()
 228.119 +              for record_idx, record in ipairs(records) do
 228.120 +                local row_class
 228.121 +                if record_idx % 2 == 0 then
 228.122 +                  row_class = "ui_list_row ui_list_even"
 228.123 +                else
 228.124 +                  row_class = "ui_list_row ui_list_odd"
 228.125 +                end
 228.126 +                ui.tag{
 228.127 +                  tag     = row_tag,
 228.128 +                  attr    = { class = row_class },
 228.129 +                  content = function()
 228.130 +                    local old_html_name_prefix, old_form_record
 228.131 +                    if prefix then
 228.132 +                      old_html_name_prefix        = slot_state.html_name_prefix
 228.133 +                      old_form_record             = slot_state.form_record
 228.134 +                      slot_state.html_name_prefix = prefix .. "[" .. record_idx .. "]"
 228.135 +                      slot_state.form_record      = record
 228.136 +                    end
 228.137 +                    local first_column = true
 228.138 +                    for column_idx, column in ipairs(columns) do
 228.139 +                      if column.ui_field_type ~= "hidden" then
 228.140 +                        local field_attr = table.new(column.field_attr)
 228.141 +                        field_attr.class =
 228.142 +                          field_attr.class or { class = "ui_list_field" }
 228.143 +                        local field_content
 228.144 +                        if column.content then
 228.145 +                          field_content = function()
 228.146 +                            return column.content(record)
 228.147 +                          end
 228.148 +                        elseif column.name then
 228.149 +                          if column.ui_field_type then
 228.150 +                            local ui_field_func = ui.field[column.ui_field_type]
 228.151 +                            if not ui_field_func then
 228.152 +                              error('Unknown ui_field_type "' .. column.ui_field_type .. '".')
 228.153 +                            end
 228.154 +                            local ui_field_options = table.new(column)
 228.155 +                            ui_field_options.record = record
 228.156 +                            ui_field_options.label  = nil
 228.157 +                            if not prefix and ui_field_options.readonly == nil then
 228.158 +                              ui_field_options.readonly = true
 228.159 +                            end
 228.160 +                            field_content = function()
 228.161 +                              return ui.field[column.ui_field_type](ui_field_options)
 228.162 +                            end
 228.163 +                          elseif column.format then
 228.164 +                            local formatter = format[column.format]
 228.165 +                            if not formatter then
 228.166 +                              error('Unknown format "' .. column.format .. '".')
 228.167 +                            end
 228.168 +                            field_content = formatter(
 228.169 +                              record[column.name], column.format_options
 228.170 +                            )
 228.171 +                          else
 228.172 +                            field_content = function()
 228.173 +                              return ui.autofield{
 228.174 +                                record    = record,
 228.175 +                                name      = column.name,
 228.176 +                                html_name = column.html_name
 228.177 +                              }
 228.178 +                            end
 228.179 +                          end
 228.180 +                        else
 228.181 +                          error("Each column needs either a 'content' or a 'name'.")
 228.182 +                        end
 228.183 +                        local extended_field_content
 228.184 +                        if first_column then
 228.185 +                          first_column = false
 228.186 +                          extended_field_content = function()
 228.187 +                            for column_idx, column in ipairs(columns) do
 228.188 +                              if column.ui_field_type == "hidden" then
 228.189 +                                local ui_field_options = table.new(column)
 228.190 +                                ui_field_options.record = record
 228.191 +                                ui_field_options.label  = nil
 228.192 +                                if not prefix and ui_field_options.readonly == nil then
 228.193 +                                  ui_field_options.readonly = true
 228.194 +                                end
 228.195 +                                ui.field.hidden(ui_field_options)
 228.196 +                              end
 228.197 +                            end
 228.198 +                            field_content()
 228.199 +                          end
 228.200 +                        else
 228.201 +                          extended_field_content = field_content
 228.202 +                        end
 228.203 +                        ui.tag{
 228.204 +                          tag     = field_tag,
 228.205 +                          attr    = field_attr,
 228.206 +                          content = extended_field_content
 228.207 +                        }
 228.208 +                      end
 228.209 +                    end
 228.210 +                    if prefix then
 228.211 +                      slot_state.html_name_prefix = old_html_name_prefix
 228.212 +                      slot_state.form_record      = old_form_record
 228.213 +                    end
 228.214 +                  end
 228.215 +                }
 228.216 +              end
 228.217 +            end
 228.218 +          }
 228.219 +        end
 228.220 +      }
 228.221 +    end
 228.222 +  }
 228.223 +  if prefix then
 228.224 +    -- ui.field.hidden is used instead of ui.hidden_field to suppress output in case of read-only mode.
 228.225 +    ui.field.hidden{
 228.226 +      html_name = prefix .. "[len]",
 228.227 +      value     = #records
 228.228 +    }
 228.229 +  end
 228.230 +end
   229.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   229.2 +++ b/framework/env/ui/multiselect.lua	Sun Oct 25 12:00:00 2009 +0100
   229.3 @@ -0,0 +1,138 @@
   229.4 +--[[--
   229.5 +ui.multiselect{
   229.6 +  name               = name,                -- HTML name ('html_name' is NOT a valid argument for this function)
   229.7 +  container_attr     = container_attr,      -- extra HTML attributes for the container (div) enclosing field and label
   229.8 +  attr               = attr,                -- extra HTML attributes for the field
   229.9 +  label              = label,               -- text to be used as label for the input field
  229.10 +  label_attr         = label_attr,          -- extra HTML attributes for the label
  229.11 +  readonly           = readonly_flag        -- set to true, to force read-only mode
  229.12 +  foreign_records    = foreign_records,     -- list of records to be chosen from, or function returning such a list
  229.13 +  foreign_id         = foreign_id,          -- name of id field in foreign records
  229.14 +  foreign_name       = foreign_name,        -- name of field to be used as name in foreign records
  229.15 +  selected_ids       = selected_ids,        -- list of ids of currently selected foreign records
  229.16 +  connecting_records = connecting_records,  -- list of connection entries, determining which foreign records are currently selected
  229.17 +  own_id             = own_id,              -- TODO documentation needed
  229.18 +  own_reference      = own_reference,       -- name of foreign key field in connecting records, which references the main record
  229.19 +  foreign_reference  = foreign_reference,   -- name of foreign key field in connecting records, which references foreign records
  229.20 +  format_options     = format_options       -- format options for format.string
  229.21 +}
  229.22 +
  229.23 +This function inserts a select field with possibility of multiple selections in the active slot. This function does not reside within ui.field.*, because multiple selections are not stored within a field of a record, but within a different SQL table. Note that 'html_name' is NOT a valid argument to this function. For description of the generic field helper arguments, see help for ui.autofield{...}.
  229.24 +
  229.25 +--]]--
  229.26 +
  229.27 +function ui.multiselect(args)
  229.28 +  local style = args.style or "checkbox"
  229.29 +  local extra_args = { fetch_record = true }
  229.30 +  if not args.readonly and args.style == "checkbox" then
  229.31 +    extra_args.disable_label_for_id = true
  229.32 +  end
  229.33 +  ui.form_element(args, extra_args, function(args)
  229.34 +    local foreign_records = args.foreign_records
  229.35 +    if type(foreign_records) == "function" then
  229.36 +      foreign_records = foreign_records(args.record)
  229.37 +    end
  229.38 +    local connecting_records = args.connecting_records
  229.39 +    if type(connecting_records) == "function" then
  229.40 +      connecting_records = connecting_records(args.record)
  229.41 +    end
  229.42 +    local select_hash = {}
  229.43 +    if args.selected_ids then
  229.44 +      for idx, selected_id in ipairs(args.selected_ids) do
  229.45 +        select_hash[selected_id] = true
  229.46 +      end
  229.47 +    elseif args.own_reference then
  229.48 +      for idx, connecting_record in ipairs(args.connecting_records) do
  229.49 +        if connecting_record[args.own_reference] == args.record[args.own_id] then
  229.50 +          select_hash[connecting_record[args.foreign_reference]] = true
  229.51 +        end
  229.52 +      end
  229.53 +    else
  229.54 +      for idx, connecting_record in ipairs(args.connecting_records) do
  229.55 +        select_hash[connecting_record[args.foreign_reference]] = true
  229.56 +      end
  229.57 +    end
  229.58 +    local attr = table.new(args.attr)
  229.59 +    if not attr.class then
  229.60 +      attr.class = "ui_multi_selection"
  229.61 +    end
  229.62 +    if args.readonly then
  229.63 +      ui.tag{
  229.64 +        tag     = "ul",
  229.65 +        attr    = attr,
  229.66 +        content = function()
  229.67 +          for idx, record in ipairs(foreign_records) do
  229.68 +            if select_hash[record[args.foreign_id]] then
  229.69 +              ui.tag{
  229.70 +                tag     = "li",
  229.71 +                content = format.string(
  229.72 +                  record[args.foreign_name],
  229.73 +                  args.format_options
  229.74 +                )
  229.75 +              }
  229.76 +            end
  229.77 +          end
  229.78 +        end
  229.79 +      }
  229.80 +    elseif style == "select" then
  229.81 +      attr.name     = args.name
  229.82 +      attr.multiple = "multiple"
  229.83 +      ui.tag{
  229.84 +        tag     = "select",
  229.85 +        attr    = attr,
  229.86 +        content = function()
  229.87 +          if args.nil_as then
  229.88 +            ui.tag{
  229.89 +              tag     = "option",
  229.90 +              attr    = { value = "" },
  229.91 +              content = format.string(
  229.92 +                args.nil_as,
  229.93 +                args.format_options
  229.94 +              )
  229.95 +            }
  229.96 +          end
  229.97 +          for idx, record in ipairs(foreign_records) do
  229.98 +            local key = record[args.foreign_id]
  229.99 +            local selected = select_hash[key]
 229.100 +            ui.tag{
 229.101 +              tag     = "option",
 229.102 +              attr    = {
 229.103 +                value    = key,
 229.104 +                selected = (selected and "selected" or nil)
 229.105 +              },
 229.106 +              content = format.string(
 229.107 +                record[args.foreign_name],
 229.108 +                args.format_options
 229.109 +              )
 229.110 +            }
 229.111 +          end
 229.112 +        end
 229.113 +      }
 229.114 +    elseif style == "checkbox" then
 229.115 +      attr.type = "checkbox"
 229.116 +      attr.name = args.name
 229.117 +      for idx, record in ipairs(foreign_records) do
 229.118 +        local key = record[args.foreign_id]
 229.119 +        local selected = select_hash[key]
 229.120 +        attr.id   = ui.create_unique_id()
 229.121 +        attr.value = key
 229.122 +        attr.checked = selected and "checked" or nil
 229.123 +        ui.container{
 229.124 +          label = format.string(
 229.125 +            record[args.foreign_name],
 229.126 +            args.format_options
 229.127 +          ),
 229.128 +          attr          = { class = "ui_checkbox_div" },
 229.129 +          label_for     = attr.id,
 229.130 +          label_attr    = { class = "ui_checkbox_label" },
 229.131 +          content_first = true,
 229.132 +          content       = function()
 229.133 +            ui.tag{ tag  = "input", attr = attr }
 229.134 +          end
 229.135 +        }
 229.136 +      end
 229.137 +    else
 229.138 +      error("'style' attribute for ui.multiselect{...} must be set to \"select\", \"checkbox\" or nil.")
 229.139 +    end
 229.140 +  end)
 229.141 +end
   230.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   230.2 +++ b/framework/env/ui/paginate.lua	Sun Oct 25 12:00:00 2009 +0100
   230.3 @@ -0,0 +1,69 @@
   230.4 +--[[--
   230.5 +ui.paginate{
   230.6 +  selector = selector,  -- a selector for items from the database
   230.7 +  per_page = per_page,  -- items per page, defaults to 10
   230.8 +  name     = name,      -- name of the CGI get variable, defaults to "page"
   230.9 +  content = function()
  230.10 +    ...                 -- code block which should be encapsulated with page selection links
  230.11 +  end
  230.12 +}
  230.13 +
  230.14 +This function preceeds and appends the output of the given 'content' function with page selection links. The passed selector will be modified to show only a limited amount ('per_page') of items.
  230.15 +--]]--
  230.16 +
  230.17 +function ui.paginate(args)
  230.18 +  local selector = args.selector
  230.19 +  local per_page = args.per_page or 10
  230.20 +  local name     = args.name or 'page'
  230.21 +  local content  = args.content
  230.22 +  local count_selector = selector:get_db_conn():new_selector()
  230.23 +  count_selector:add_field('count(1)')
  230.24 +  count_selector:add_from(selector)
  230.25 +  count_selector:single_object_mode()
  230.26 +  local count = count_selector:exec().count
  230.27 +  local page_count = math.floor((count - 1) / per_page) + 1
  230.28 +  local current_page = param.get(name, atom.integer) or 1
  230.29 +  selector:limit(per_page)
  230.30 +  selector:offset((current_page - 1) * per_page)
  230.31 +  local id     = param.get_id_cgi()
  230.32 +  local params = param.get_all_cgi()
  230.33 +  local function pagination_elements()
  230.34 +    if page_count > 1 then
  230.35 +      for page = 1, page_count do
  230.36 +        if page > 1 then
  230.37 +          slot.put(" ")
  230.38 +        end
  230.39 +        params[name] = page
  230.40 +        local attr = {}
  230.41 +        if current_page == page then
  230.42 +          attr.class = "active"
  230.43 +        end
  230.44 +        ui.link{
  230.45 +          attr = attr,
  230.46 +          module = request.get_module(),
  230.47 +          view   = request.get_view(),
  230.48 +          id     = id,
  230.49 +          params = params,
  230.50 +          text   = tostring(page)
  230.51 +        }
  230.52 +      end
  230.53 +    end
  230.54 +  end
  230.55 +  ui.container{
  230.56 +    attr = { class = 'ui_paginate' },
  230.57 +    content = function()
  230.58 +      ui.container{
  230.59 +        attr = { class = 'ui_paginate_head ui_paginate_select' },
  230.60 +        content = pagination_elements
  230.61 +      }
  230.62 +      ui.container{
  230.63 +        attr = { class = 'ui_paginate_content' },
  230.64 +        content = content
  230.65 +      }
  230.66 +      ui.container{
  230.67 +        attr = { class = 'ui_paginate_foot ui_paginate_select' },
  230.68 +        content = pagination_elements
  230.69 +      }
  230.70 +    end
  230.71 +  }
  230.72 +end
   231.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   231.2 +++ b/framework/env/ui/script.lua	Sun Oct 25 12:00:00 2009 +0100
   231.3 @@ -0,0 +1,35 @@
   231.4 +--[[--
   231.5 +ui.script{
   231.6 +  noscript_attr = noscript_attr,  -- HTML attributes for noscript tag
   231.7 +  noscript      = noscript,       -- string or function for noscript content
   231.8 +  attr          = attr,           -- extra HTML attributes for script tag
   231.9 +  type          = type,           -- type of script, defaults to "text/javascript"
  231.10 +  script        = script,         -- string or function for script content
  231.11 +}
  231.12 +
  231.13 +This function is used to insert a script into the active slot. It is currently not XML compliant and the script must not contain a closing script tag.
  231.14 +
  231.15 +--]]--
  231.16 +
  231.17 +-- TODO: CDATA or SGML comment?
  231.18 +
  231.19 +function ui.script(args)
  231.20 +  local args = args or {}
  231.21 +  local noscript_attr = args.noscript_attr
  231.22 +  local noscript = args.noscript
  231.23 +  local attr = table.new(args.attr)
  231.24 +  attr.type = attr.type or args.type or "text/javascript"
  231.25 +  local script = args.script
  231.26 +  if script and type(script) ~= "function" then
  231.27 +    -- disable HTML entity escaping
  231.28 +    script = function()
  231.29 +      slot.put(args.script)
  231.30 +    end
  231.31 +  end
  231.32 +  if noscript then
  231.33 +    ui.tag{ tag = "noscript", attr = attr, content = noscript }
  231.34 +  end
  231.35 +  if script then
  231.36 +    ui.tag{ tag = "script", attr = attr, content = script }
  231.37 +  end
  231.38 +end
   232.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   232.2 +++ b/framework/env/ui/submit.lua	Sun Oct 25 12:00:00 2009 +0100
   232.3 @@ -0,0 +1,21 @@
   232.4 +--[[--
   232.5 +ui.submit{
   232.6 +  name  = name,   -- optional HTML name
   232.7 +  value = value,  -- HTML value
   232.8 +  text  = value   -- text on button
   232.9 +}
  232.10 +
  232.11 +This function places a HTML form submit button in the active slot. Currently the text displayed on the button and the value attribute are the same, so specifying both a 'value' and a 'text' makes no sense.
  232.12 +
  232.13 +--]]--
  232.14 +
  232.15 +function ui.submit(args)
  232.16 +  if slot.get_state_table().form_readonly == false then
  232.17 +    local args = args or {}
  232.18 +    local attr = table.new(attr)
  232.19 +    attr.type  = "submit"
  232.20 +    attr.name  = args.name
  232.21 +    attr.value = args.value or args.text
  232.22 +    return ui.tag{ tag  = "input", attr = attr }
  232.23 +  end
  232.24 +end
   233.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   233.2 +++ b/framework/env/ui/tag.lua	Sun Oct 25 12:00:00 2009 +0100
   233.3 @@ -0,0 +1,50 @@
   233.4 +--[[--
   233.5 +ui.tag{
   233.6 +  tag     = tag,     -- HTML tag, e.g. "a" for <a>...</a>
   233.7 +  attr    = attr,    -- table of HTML attributes, e.g. { class = "hide" }
   233.8 +  content = content  -- string to be HTML encoded, or function to be executed
   233.9 +}
  233.10 +
  233.11 +This function writes a HTML tag into the active slot.
  233.12 +
  233.13 +NOTE: ACCELERATED FUNCTION
  233.14 +Do not change unless also you also update webmcp_accelerator.c
  233.15 +
  233.16 +--]]--
  233.17 +
  233.18 +function ui.tag(args)
  233.19 +  local tag, attr, content
  233.20 +  tag     = args.tag
  233.21 +  attr    = args.attr or {}
  233.22 +  content = args.content
  233.23 +  if type(attr.class) == "table" then
  233.24 +    attr = table.new(attr)
  233.25 +    attr.class = table.concat(attr.class, " ")
  233.26 +  end
  233.27 +  if not tag and next(attr) then
  233.28 +    tag = "span"
  233.29 +  end
  233.30 +  if tag then
  233.31 +    slot.put('<', tag)
  233.32 +    for key, value in pairs(attr) do
  233.33 +      slot.put(' ', key, '="', encode.html(value), '"')
  233.34 +    end
  233.35 +  end
  233.36 +  if content then
  233.37 +    if tag then
  233.38 +      slot.put('>')
  233.39 +    end
  233.40 +    if type(content) == "function" then
  233.41 +      content()
  233.42 +    else
  233.43 +      slot.put(encode.html(content))
  233.44 +    end
  233.45 +    if tag then
  233.46 +      slot.put('</', tag, '>')
  233.47 +    end
  233.48 +  else
  233.49 +    if tag then
  233.50 +      slot.put(' />')
  233.51 +    end
  233.52 +  end
  233.53 +end
   234.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   234.2 +++ b/framework/env/ui_deprecated/__init.lua	Sun Oct 25 12:00:00 2009 +0100
   234.3 @@ -0,0 +1,2 @@
   234.4 +ui_deprecated._form = {}
   234.5 +
   235.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   235.2 +++ b/framework/env/ui_deprecated/_prepare_redirect_params.lua	Sun Oct 25 12:00:00 2009 +0100
   235.3 @@ -0,0 +1,26 @@
   235.4 +function ui_deprecated._prepare_redirect_params(params, redirect_to)
   235.5 +  if redirect_to then
   235.6 +    for status, settings in pairs(redirect_to) do
   235.7 +      local module, view = settings.module, settings.view
   235.8 +      if not module then
   235.9 +        error("No redirection module specified.")
  235.10 +      end
  235.11 +      if not view then
  235.12 +        error("No redirection view specified.")
  235.13 +      end
  235.14 +      if status == "ok" then
  235.15 +        params["_webmcp_routing." .. status .. ".mode"] = "redirect"
  235.16 +      else
  235.17 +        params["_webmcp_routing." .. status .. ".mode"] = "forward"
  235.18 +      end
  235.19 +      params["_webmcp_routing." .. status .. ".module"] = settings.module
  235.20 +      params["_webmcp_routing." .. status .. ".view"]   = settings.view
  235.21 +      params["_webmcp_routing." .. status .. ".id"]     = settings.id
  235.22 +      if settings.params then
  235.23 +        for key, value in pairs(settings.params) do
  235.24 +          params["_webmcp_routing." .. status .. ".params." .. key] = value
  235.25 +        end
  235.26 +      end
  235.27 +    end
  235.28 +  end
  235.29 +end
   236.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   236.2 +++ b/framework/env/ui_deprecated/_stringify_table.lua	Sun Oct 25 12:00:00 2009 +0100
   236.3 @@ -0,0 +1,11 @@
   236.4 +function ui_deprecated._stringify_table(table)
   236.5 +  if not table then
   236.6 +    return ''
   236.7 +  end
   236.8 +  local string = ''
   236.9 +  for key, value in pairs(table) do
  236.10 +    string = string .. ' ' .. key .. '="' .. value ..'"'
  236.11 +  end
  236.12 +  return string
  236.13 +end
  236.14 +
   237.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   237.2 +++ b/framework/env/ui_deprecated/box.lua	Sun Oct 25 12:00:00 2009 +0100
   237.3 @@ -0,0 +1,29 @@
   237.4 +--[[doc: ui_deprecated.box
   237.5 +
   237.6 +Starts a box
   237.7 +
   237.8 +label (string) Label, optional
   237.9 +class (string) Style class, optional
  237.10 +content (function) or (string) The content of the box
  237.11 +
  237.12 +
  237.13 +Example:
  237.14 +
  237.15 +ui_deprecated.box{
  237.16 +  label   = 'My box label',
  237.17 +  class   = 'my_css_class',
  237.18 +  content = function() 
  237.19 +    ui_deprecated.text('My text')
  237.20