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 +  end
  237.21 +}
  237.22 +
  237.23 +--]]
  237.24 +
  237.25 +function ui_deprecated.box(args)
  237.26 +  if args.class then
  237.27 +    args.html_options = args.html_options or {}
  237.28 +    args.html_options.class = args.class
  237.29 +  end
  237.30 +  ui_deprecated.tag('div', args)
  237.31 +end
  237.32 +
   238.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   238.2 +++ b/framework/env/ui_deprecated/calendar.lua	Sun Oct 25 12:00:00 2009 +0100
   238.3 @@ -0,0 +1,147 @@
   238.4 +function ui_deprecated.calendar(args)
   238.5 +  local record = assert(slot.get_state_table(), "ui_deprecated.calender was not called within a form.").form_record
   238.6 +  local value = param.get(args.field, atom.date) or record[args.field]
   238.7 +  local year = param.get('_ui_calendar_year', atom.integer) or args.year or 2008
   238.8 +  local month = param.get('_ui_calendar_month', atom.integer) or args.month or 10
   238.9 +  local empty_days = atom.date{ year = year, month = month, day = 1 }.iso_weekday -1
  238.10 +  local enabled = not args.disabled
  238.11 +  
  238.12 +  local prev_year = year
  238.13 +  local prev_month = month - 1
  238.14 +  if prev_month == 0 then
  238.15 +    prev_month = 12
  238.16 +    prev_year = prev_year - 1
  238.17 +  end
  238.18 +
  238.19 +  local next_year = year
  238.20 +  local next_month = month + 1
  238.21 +  if next_month == 13 then
  238.22 +    next_month = 1
  238.23 +    next_year = next_year + 1
  238.24 +  end
  238.25 +  
  238.26 +  ui_deprecated.tag('div', {
  238.27 +    html_options = {
  238.28 +      class="ui_field ui_calendar"
  238.29 +    },
  238.30 +    content = function()
  238.31 +      ui_deprecated.tag('input', {
  238.32 +        html_options = {
  238.33 +          type = 'hidden',
  238.34 +          value = year,
  238.35 +          name = '_ui_calendar_year',
  238.36 +          id = '_ui_calendar_year',
  238.37 +          onchange = 'this.form.submit();'
  238.38 +        }
  238.39 +      })
  238.40 +      ui_deprecated.tag('input', {
  238.41 +        html_options = {
  238.42 +          type = 'hidden',
  238.43 +          value = month,
  238.44 +          name = '_ui_calendar_month',
  238.45 +          id = '_ui_calendar_month',
  238.46 +          onchange = 'this.form.submit();'
  238.47 +        }
  238.48 +      })
  238.49 +      ui_deprecated.tag('input', {
  238.50 +        html_options = {
  238.51 +          type = 'hidden',
  238.52 +          value = value and tostring(value) or '',
  238.53 +          name = args.field,
  238.54 +          id = '_ui_calendar_input',
  238.55 +          onchange = 'this.form.submit();'
  238.56 +        }
  238.57 +      })
  238.58 +      if args.label then
  238.59 +        ui_deprecated.tag('div', {
  238.60 +          html_options = {
  238.61 +            class="label"
  238.62 +          },
  238.63 +          content = function()
  238.64 +            ui_deprecated.text(args.label)
  238.65 +          end
  238.66 +        })
  238.67 +      end
  238.68 +      ui_deprecated.tag('div', {
  238.69 +        html_options = {
  238.70 +          class="value"
  238.71 +        },
  238.72 +        content = function()
  238.73 +          ui_deprecated.tag('div', {
  238.74 +            html_options = {
  238.75 +              class = 'next',
  238.76 +              href = '#',
  238.77 +              onclick = enabled and "document.getElementById('_ui_calendar_year').value = '" .. tostring(next_year) .. "'; document.getElementById('_ui_calendar_month').value = '" .. tostring(next_month) .. "'; document.getElementById('_ui_calendar_year').form.submit();" or '', 
  238.78 +            },
  238.79 +            content = '>>>';
  238.80 +          })
  238.81 +          ui_deprecated.tag('div', {
  238.82 +            html_options = {
  238.83 +              class = 'prev',
  238.84 +              href = '#',
  238.85 +              onclick = enabled and "document.getElementById('_ui_calendar_year').value = '" .. tostring(prev_year) .. "'; document.getElementById('_ui_calendar_month').value = '" .. tostring(prev_month) .. "'; document.getElementById('_ui_calendar_year').form.submit();" or '', 
  238.86 +            },
  238.87 +            content = '<<<';
  238.88 +          })
  238.89 +          ui_deprecated.tag('div', {
  238.90 +            html_options = {
  238.91 +              class="title"
  238.92 +            },
  238.93 +            content = function()
  238.94 +              local months = {_'January', _'February', _'March', _'April', _'May', _'June', _'July', _'August', _'September', _'October', _'November', _'December' }
  238.95 +              ui_deprecated.text(months[month])
  238.96 +              ui_deprecated.text(' ')
  238.97 +              ui_deprecated.text(tostring(year))
  238.98 +            end
  238.99 +          })
 238.100 +          ui_deprecated.tag('table', { 
 238.101 +            content = function()
 238.102 +              ui_deprecated.tag('thead', { 
 238.103 +                content = function()
 238.104 +                  ui_deprecated.tag('tr', {
 238.105 +                    content = function()
 238.106 +                      local dows = { _'Mon', _'Tue', _'Wed', _'Thu', _'Fri', _'Sat', _'Sun' }
 238.107 +                      for col = 1,7 do
 238.108 +                        ui_deprecated.tag('th', { content = dows[col] })
 238.109 +                      end
 238.110 +                    end
 238.111 +                  })
 238.112 +                end
 238.113 +              })
 238.114 +              ui_deprecated.tag('tbody', { 
 238.115 +                content = function()
 238.116 +                  for row = 1,6 do
 238.117 +                    ui_deprecated.tag('tr', {
 238.118 +                      content = function()
 238.119 +                        for col = 1,7 do
 238.120 +                          local day = (row -1) * 7 + col - empty_days
 238.121 +                          local date = atom.date.invalid
 238.122 +                          if day > 0 then
 238.123 +                            date = atom.date{ year = year, month = month, day = day }
 238.124 +                          end
 238.125 +                          ui_deprecated.tag('td', {
 238.126 +                            html_options = {
 238.127 +                              onclick = enabled and 'document.getElementById(\'_ui_calendar_input\').value = \'' .. tostring(date) .. '\'; document.getElementById(\'_ui_calendar_input\').onchange(); ' or ''
 238.128 +                            },
 238.129 +                            content = function()
 238.130 +                              if date.invalid then
 238.131 +                                slot.put('&nbsp;')
 238.132 +                              else
 238.133 +                                local selected = date == value
 238.134 +                                args.day_func(date, selected)
 238.135 +                              end
 238.136 +                            end
 238.137 +                          })
 238.138 +                        end
 238.139 +                      end
 238.140 +                    })
 238.141 +                  end
 238.142 +                end
 238.143 +              })
 238.144 +            end
 238.145 +          })
 238.146 +        end
 238.147 +      })
 238.148 +    end
 238.149 +  })
 238.150 +end
   239.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   239.2 +++ b/framework/env/ui_deprecated/field.lua	Sun Oct 25 12:00:00 2009 +0100
   239.3 @@ -0,0 +1,34 @@
   239.4 +--
   239.5 +-- Creates an output field
   239.6 +--
   239.7 +-- label      (string) The label of the field
   239.8 +-- value      (atom)   The value to put out
   239.9 +-- field_type (string) The type of the field (default: 'string')
  239.10 +--
  239.11 +-- Example:
  239.12 +--
  239.13 +--  ui_deprecated.field({
  239.14 +--    label = _'Id',
  239.15 +--    value = myobject.id, 
  239.16 +--    field_type = 'integer'
  239.17 +--  })
  239.18 +--
  239.19 +
  239.20 +function ui_deprecated.field(args)
  239.21 +  local value_type = args.value_type or atom.string
  239.22 +  slot.put(
  239.23 +    '<div class="ui_field ui_field_', value_type.name, '">',
  239.24 +      '<div class="label">',
  239.25 +        encode.html(args.label or ''), 
  239.26 +      '</div>',
  239.27 +      '<div class="value">')
  239.28 +  if args.value then
  239.29 +    slot.put(encode.html(convert.to_human(args.value, value_type)))
  239.30 +  elseif args.link then
  239.31 +    ui_deprecated.link(args.link)
  239.32 +  end
  239.33 +  slot.put(
  239.34 +      '</div>',
  239.35 +    '</div>\n'
  239.36 +  )
  239.37 +end
   240.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   240.2 +++ b/framework/env/ui_deprecated/form.lua	Sun Oct 25 12:00:00 2009 +0100
   240.3 @@ -0,0 +1,85 @@
   240.4 +--
   240.5 +-- Creates a formular
   240.6 +--
   240.7 +-- record          (record)   Take field values from this object (optional)
   240.8 +-- class           (string)   Style class (optional)
   240.9 +-- method          (string)   Submit method ['post', 'get'] (optional)
  240.10 +-- module          (string)   The module to submit to
  240.11 +-- action          (string)   The action to submit to
  240.12 +-- params          (table)    The GET parameter to be send with
  240.13 +-- content         (function) Function for the content of the form
  240.14 +--
  240.15 +-- Example:
  240.16 +--
  240.17 +--  ui_deprecated.form({
  240.18 +--    object = client,
  240.19 +--    class  = 'form_class',
  240.20 +--    method = 'post',
  240.21 +--    module = 'client',
  240.22 +--    action = 'update',
  240.23 +--    params = {
  240.24 +--      id = client.id
  240.25 +--    }, 
  240.26 +--    redirect_to = {
  240.27 +--      ok = {
  240.28 +--        module = 'client', 
  240.29 +--        view = 'list'
  240.30 +--        },
  240.31 +--      error = {
  240.32 +--        module = 'client', 
  240.33 +--        view = 'edit', 
  240.34 +--        params = { 
  240.35 +--          id = client.id
  240.36 +--        }
  240.37 +--      }
  240.38 +--    },
  240.39 +--  })
  240.40 +
  240.41 +function ui_deprecated.form(args)
  240.42 +  local slot_state = slot.get_state_table()
  240.43 +  if slot_state.form_opened then
  240.44 +    error("Cannot open a form inside a form.")
  240.45 +  end
  240.46 +  slot_state.form_opened = true
  240.47 +  local old_record = slot_state.form_record
  240.48 +  slot_state.form_record = args.record or {}  -- TODO: decide what really should happen when no record is specified
  240.49 +
  240.50 +  local params = {}
  240.51 +  if args.params then
  240.52 +    for key, value in pairs(args.params) do
  240.53 +      params[key] = value
  240.54 +    end
  240.55 +  end
  240.56 +  ui_deprecated._prepare_redirect_params(params, args.redirect_to)
  240.57 +
  240.58 +  local attr_action = args.url or encode.url{
  240.59 +    module = args.module,
  240.60 +    view   = args.view,
  240.61 +    action = args.action,
  240.62 +    id     = args.id,
  240.63 +    params = params
  240.64 +  }
  240.65 +
  240.66 +  local base = request.get_relative_baseurl()
  240.67 +  local attr_class = table.concat({ 'ui_form', args.class }, ' ')
  240.68 +  local attr_method = args.method or 'POST'  -- TODO: uppercase/sanatize method
  240.69 +
  240.70 +  slot.put(
  240.71 +    '<form',
  240.72 +    ' action="', attr_action, '"',
  240.73 +    ' class="',  attr_class,  '"',
  240.74 +    ' method="', attr_method, '"',
  240.75 +    '>\n'
  240.76 +  )
  240.77 +
  240.78 +  if type(args.content) == 'function' then
  240.79 +    args.content()
  240.80 +  else
  240.81 +    error('No content function')
  240.82 +  end
  240.83 +
  240.84 +  slot.put('</form>')
  240.85 +  slot_state.form_record = old_record
  240.86 +  slot_state.form_opened = false
  240.87 +
  240.88 +end
   241.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   241.2 +++ b/framework/env/ui_deprecated/image.lua	Sun Oct 25 12:00:00 2009 +0100
   241.3 @@ -0,0 +1,22 @@
   241.4 +--
   241.5 +-- Creates an image
   241.6 +--
   241.7 +-- image           (string)
   241.8 +--
   241.9 +-- Example:
  241.10 +--
  241.11 +--  ui_deprecated.image({
  241.12 +--    image = 'test.png',
  241.13 +--  })
  241.14 +--
  241.15 +
  241.16 +function ui_deprecated.image(args)
  241.17 +  assert(args.image, "No image argument given.")
  241.18 +  slot.put(
  241.19 +    '<img src="',
  241.20 +    request.get_relative_baseurl(),
  241.21 +    'static/',
  241.22 +    args.image,
  241.23 +    '" />'
  241.24 +  )
  241.25 +end
   242.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   242.2 +++ b/framework/env/ui_deprecated/input.lua	Sun Oct 25 12:00:00 2009 +0100
   242.3 @@ -0,0 +1,67 @@
   242.4 +--
   242.5 +-- Creates an input field in a form
   242.6 +--
   242.7 +-- label      (string) The label of the input field
   242.8 +-- field      (string) The name of the record field
   242.9 +-- field_type (string) The type of the record field
  242.10 +--
  242.11 +-- Example:
  242.12 +--
  242.13 +--  ui_deprecated.input({
  242.14 +--    label = _'Comment',
  242.15 +--    field = 'comment', 
  242.16 +--    field_type = 'textarea'
  242.17 +--  })
  242.18 +--
  242.19 +local field_type_to_atom_class_map = {
  242.20 +  text       = atom.string,
  242.21 +  textarea   = atom.string,
  242.22 +  number     = atom.number,
  242.23 +  percentage = atom.number,
  242.24 +}
  242.25 +
  242.26 +function ui_deprecated.input(args)
  242.27 +  local record = assert(slot.get_state_table(), "ui_deprecated.input was not called within a form.").form_record
  242.28 +
  242.29 +  local field_type = args.field_type or "text"
  242.30 +
  242.31 +  local field_func = assert(ui_deprecated.input_field[field_type], "no field helper for given type '" .. field_type .. "'")
  242.32 +
  242.33 +  local html_name = args.name or args.field
  242.34 +  local field_html
  242.35 +
  242.36 +  if args.field then
  242.37 +    local param_type = field_type_to_atom_class_map[field_type] or error('Unkown field type')
  242.38 +    field_html = field_func{
  242.39 +      name  = html_name, 
  242.40 +      value = param.get(html_name, param_type) 
  242.41 +              or record[args.field],
  242.42 +      height = args.height,
  242.43 +    }
  242.44 +
  242.45 +  elseif args.value then
  242.46 +    field_html = field_func{
  242.47 +      name  = html_name, 
  242.48 +      value = args.value,
  242.49 +      height = args.height,
  242.50 +    }
  242.51 +  
  242.52 +  else
  242.53 +    field_html = field_func{
  242.54 +      name  = html_name, 
  242.55 +      value = '',
  242.56 +      height = args.height,
  242.57 +    }
  242.58 +    
  242.59 +  end
  242.60 +  
  242.61 +  slot.put('<div class="ui_field ui_input_', field_type, '">\n')
  242.62 +  if args.label then
  242.63 +    slot.put('<div class="label">', encode.html(args.label), '</div>\n')
  242.64 +  end
  242.65 +  slot.put('<div class="value">',
  242.66 +        field_html,
  242.67 +      '</div>\n',
  242.68 +    '</div>\n'
  242.69 +  )
  242.70 +end
   246.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   246.2 +++ b/framework/env/ui_deprecated/input_field/number.lua	Sun Oct 25 12:00:00 2009 +0100
   246.3 @@ -0,0 +1,5 @@
   246.4 +function ui_deprecated.input_field.number(args)
   246.5 +	name = args.name or ''
   246.6 +	value = args.value or 0
   246.7 +	return '<input class="ui_input_field_number" type="text" name="' .. name .. '" value="' .. convert.to_human(value, "number") .. '" />'
   246.8 +end
   246.9 \ No newline at end of file
   247.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   247.2 +++ b/framework/env/ui_deprecated/input_field/percentage.lua	Sun Oct 25 12:00:00 2009 +0100
   247.3 @@ -0,0 +1,5 @@
   247.4 +function ui_deprecated.input_field.percentage(args)
   247.5 +	name = args.name or ''
   247.6 +	value = args.value or 0
   247.7 +	return '<input class="ui_input_field_percentage" type="text" name="' .. name .. '" value="' .. convert.to_human(value, "number") .. '" /> %'
   247.8 +end
   247.9 \ No newline at end of file
   249.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   249.2 +++ b/framework/env/ui_deprecated/input_field/text.lua	Sun Oct 25 12:00:00 2009 +0100
   249.3 @@ -0,0 +1,5 @@
   249.4 +function ui_deprecated.input_field.text(args)
   249.5 +	name = args.name or ''
   249.6 +	value = args.value or ''
   249.7 +	return '<input type="text" name="' .. name .. '" value="' .. convert.to_human(value, "string") .. '" />'
   249.8 +end
   249.9 \ No newline at end of file
   250.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   250.2 +++ b/framework/env/ui_deprecated/input_field/textarea.lua	Sun Oct 25 12:00:00 2009 +0100
   250.3 @@ -0,0 +1,9 @@
   250.4 +function ui_deprecated.input_field.textarea(args)
   250.5 +	local value = args.value or ''
   250.6 +	local name = assert(args.name, 'No field name given')
   250.7 +	local style = ''
   250.8 +	if args.height then
   250.9 +	  style = 'style="height:' .. args.height .. '"'
  250.10 +	end
  250.11 +	return '<textarea name="' .. name .. '" ' .. style .. '>' .. encode.html(convert.to_human(value, "string")) .. '</textarea>'
  250.12 +end
   252.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   252.2 +++ b/framework/env/ui_deprecated/label.lua	Sun Oct 25 12:00:00 2009 +0100
   252.3 @@ -0,0 +1,5 @@
   252.4 +function ui_deprecated.label(value)
   252.5 +  slot.put '<div class="ui_label">' 
   252.6 +  ui_deprecated.text( value )
   252.7 +  slot.put '</div>\n'
   252.8 +end
   252.9 \ No newline at end of file
   253.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   253.2 +++ b/framework/env/ui_deprecated/link.lua	Sun Oct 25 12:00:00 2009 +0100
   253.3 @@ -0,0 +1,69 @@
   253.4 +function ui_deprecated.link(args)
   253.5 +  if args.action then
   253.6 +    local params = {}
   253.7 +    if args.params then
   253.8 +      for key, value in pairs(args.params) do
   253.9 +        params[key] = value
  253.10 +      end
  253.11 +    end
  253.12 +    ui_deprecated._prepare_redirect_params(params, args.redirect_to)
  253.13 +
  253.14 +    local attr_action = args.url or encode.url{
  253.15 +      module = args.module or request.get_module(),
  253.16 +      action = args.action,
  253.17 +      id     = args.id,
  253.18 +      params = params
  253.19 +    }
  253.20 +    local attr_class = table.concat({ 'ui_link', args.class }, ' ')
  253.21 +    local attr_target = args.target or ''
  253.22 +    local redirect_to = args.redirect_to
  253.23 +    local unique_id = "unique_" .. multirand.string(32, "abcdefghijklmnopqrstuvwxyz0123456789")
  253.24 +    slot.put(
  253.25 +      '<form',
  253.26 +      ' id="', unique_id , '"',
  253.27 +      ' action="', attr_action,  '"',
  253.28 +      ' class="', attr_class, '"',
  253.29 +      ' target="', attr_target, '"',
  253.30 +      ' method="post"',
  253.31 +      '>\n',
  253.32 +      '<input type="submit" value="', args.label or '', '" />',
  253.33 +      '</form>',
  253.34 +      '<script>document.getElementById(\'', unique_id, '\').style.display=\'none\';document.write(\'<a href="#" class="', attr_class , '" onclick="document.getElementById(\\\'', unique_id, '\\\').submit();">'
  253.35 +    )
  253.36 +    if args.icon then
  253.37 +      ui_deprecated.image{ image = 'ui/icon/' .. args.icon }
  253.38 +    end
  253.39 +    if args.image then
  253.40 +      ui_deprecated.image{ image = args.image }
  253.41 +    end
  253.42 +    if args.label then
  253.43 +      slot.put(args.label)
  253.44 +    end
  253.45 +    slot.put("</a>');</script>")
  253.46 +  else
  253.47 +    local attr_class = table.concat({ 'ui_link', args.class }, ' ')
  253.48 +    slot.put(
  253.49 +      '<a href="',
  253.50 +      args.url or encode.url{
  253.51 +        module    = args.module or request.get_module(),
  253.52 +        view      = args.view,
  253.53 +        id        = args.id,
  253.54 +        params    = args.params,
  253.55 +      },
  253.56 +      '" ',
  253.57 +      ui_deprecated._stringify_table({ class = attr_class }),
  253.58 +      ui_deprecated._stringify_table( args.html_options or {} ),
  253.59 +      '>'
  253.60 +    )
  253.61 +    if args.icon then
  253.62 +      ui_deprecated.image{ image = 'ui/icon/' .. args.icon }
  253.63 +    end
  253.64 +    if args.image then
  253.65 +      ui_deprecated.image{ image = args.image }
  253.66 +    end
  253.67 +    if args.label then
  253.68 +      slot.put(args.label)
  253.69 +    end
  253.70 +    slot.put('</a>')
  253.71 +  end
  253.72 +end
   254.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   254.2 +++ b/framework/env/ui_deprecated/list.lua	Sun Oct 25 12:00:00 2009 +0100
   254.3 @@ -0,0 +1,153 @@
   254.4 +--
   254.5 +-- Creates a list view of a collection
   254.6 +--
   254.7 +-- Example:
   254.8 +--
   254.9 +--   ui_deprecated.list({ 
  254.10 +--     label = 'Point table',
  254.11 +--     collection = mycollection,
  254.12 +--     type = 'table' -- 'table', 'ulli'
  254.13 +--     cols = {
  254.14 +--       { 
  254.15 +--         label = _'Id',
  254.16 +--         field = 'id',
  254.17 +--         width = 100,
  254.18 +--         link = { module = 'mymodule', view = 'show' },
  254.19 +--              sort = true,
  254.20 +--       },
  254.21 +--       {
  254.22 +--         label = _'Name',
  254.23 +--         field = 'name',
  254.24 +--         width = 200,
  254.25 +--         link = { module = 'mymodule', view = 'show' },
  254.26 +--              sort = true,
  254.27 +--       },
  254.28 +--      {
  254.29 +--        label = _'Points',
  254.30 +--        func = function(record)
  254.31 +--          return record.points_a + record.points_b + record.points_c
  254.32 +--        end,
  254.33 +--        width = 300
  254.34 +--      }
  254.35 +--     }
  254.36 +--   })
  254.37 +--
  254.38 +
  254.39 +function ui_deprecated.list(args)
  254.40 +  local args = args
  254.41 +  args.type = args.type or 'table'
  254.42 +  if args.label then
  254.43 +    slot.put('<div class="ui_list_label">' .. encode.html(args.label) .. '</div>\n')
  254.44 +  end
  254.45 +  if not args or not args.collection or not args.cols then
  254.46 +    error('No args for ui_deprecated.list_end')
  254.47 +  end
  254.48 +  if args.type == 'table' then
  254.49 +    slot.put('<table class="ui_list">')
  254.50 +    slot.put('<tr class="ui_list_head">')
  254.51 +  elseif args.type == 'ulli' then
  254.52 +    slot.put('<ul class="ui_list">')
  254.53 +    slot.put('<li class="ui_list_head">')
  254.54 +  end
  254.55 +  if args.type == 'table' then
  254.56 +  elseif args.type == 'ulli' then
  254.57 +  end
  254.58 +  for j, col in ipairs(args.cols) do
  254.59 +    class_string = ''
  254.60 +    if col.align then
  254.61 +      class_string = ' class="' .. col.align .. '"'
  254.62 +    end
  254.63 +    if args.type == 'table' then
  254.64 +      slot.put('<th style="width: ' .. col.width .. ';"' .. class_string ..'>')
  254.65 +      slot.put(ui_deprecated.box({ name = 'ui_list_col_value', content = encode.html(col.label) }) )
  254.66 +      slot.put('</th>')
  254.67 +    elseif args.type == 'ulli' then
  254.68 +      slot.put('<div style="width: ' .. col.width .. ';"' .. class_string ..'>')
  254.69 +      slot.put(ui_deprecated.box({ name = 'ui_list_col_value', content = encode.html(col.label) }) )
  254.70 +      slot.put('</div>')
  254.71 +    end
  254.72 +  end
  254.73 +  if args.type == 'table' then
  254.74 +    slot.put('</tr>')
  254.75 +  elseif args.type == 'ulli' then
  254.76 +    slot.put('<br style="clear: left;" />')
  254.77 +    slot.put('</li>')
  254.78 +  end
  254.79 +  for i, obj in ipairs(args.collection) do
  254.80 +    if args.type == 'table' then
  254.81 +      slot.put('<tr>')
  254.82 +    elseif args.type == 'ulli' then
  254.83 +      slot.put('<li>')
  254.84 +    end
  254.85 +    for j, col in ipairs(args.cols) do
  254.86 +      class_string = ''
  254.87 +      if col.align then
  254.88 +        class_string = ' class="ui_list_col_align_' .. col.align .. '"'
  254.89 +      end
  254.90 +      if args.type == 'table' then
  254.91 +        slot.put('<td' .. class_string ..'>')
  254.92 +      elseif args.type == 'ulli' then
  254.93 +        slot.put('<div style="width: ' .. col.width .. ';"' .. class_string ..'>')
  254.94 +      end
  254.95 +      if col.link then
  254.96 +        local params = {}
  254.97 +        if col.link then
  254.98 +          params = col.link.params or {}
  254.99 +        end
 254.100 +        if col.link_values then
 254.101 +          for key, field in pairs(col.link_values) do
 254.102 +            params[key] = obj[field]
 254.103 +          end
 254.104 +        end
 254.105 +        local id
 254.106 +        if col.link_id_field then
 254.107 +          id = obj[col.link_id_field]
 254.108 +        end
 254.109 +        local value
 254.110 +        if col.value then
 254.111 +          value = col.value
 254.112 +        else
 254.113 +          value = obj[col.field]
 254.114 +        end
 254.115 +        ui_deprecated.link{
 254.116 +          label  = convert.to_human(value),
 254.117 +          module = col.link.module,
 254.118 +          view   = col.link.view,
 254.119 +          id     = id,
 254.120 +          params = params,
 254.121 +        }
 254.122 +      elseif col.link_func then
 254.123 +        local link = col.link_func(obj)
 254.124 +        if link then
 254.125 +	      ui_deprecated.link(link)
 254.126 +	    end
 254.127 +      else
 254.128 +        local text
 254.129 +        if col.func then
 254.130 +          text = col.func(obj)
 254.131 +        elseif col.value then
 254.132 +          text = convert.to_human(value)
 254.133 +        else
 254.134 +          text = convert.to_human(obj[col.field])
 254.135 +        end
 254.136 +        ui_deprecated.box{ name = 'ui_list_col_value', content = text }
 254.137 +      end
 254.138 +      if args.type == 'table' then
 254.139 +        slot.put('</td>')
 254.140 +      elseif args.type == 'ulli' then
 254.141 +        slot.put('</div>')
 254.142 +      end
 254.143 +    end
 254.144 +    if args.type == 'table' then
 254.145 +      slot.put('</tr>')
 254.146 +    elseif args.type == 'ulli' then
 254.147 +      slot.put('<br style="clear: left;" />')
 254.148 +      slot.put('</li>')
 254.149 +    end
 254.150 +  end
 254.151 +  if args.type == 'table' then
 254.152 +    slot.put('</table>')
 254.153 +  elseif args.type == 'ulli' then
 254.154 +    slot.put('</ul><div style="clear: left">&nbsp;</div>')
 254.155 +  end
 254.156 +end
 254.157 \ No newline at end of file
   255.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   255.2 +++ b/framework/env/ui_deprecated/multiselect.lua	Sun Oct 25 12:00:00 2009 +0100
   255.3 @@ -0,0 +1,50 @@
   255.4 +function ui_deprecated.multiselect(args)
   255.5 +  local record = assert(slot.get_state_table(), "ui_deprecated.multiselect was not called within a form.").form_record
   255.6 +  local name = args.name
   255.7 +  local relationship = args.relationship
   255.8 +
   255.9 +  local foreign_records = relationship.foreign_records
  255.10 +	
  255.11 +  local selector = relationship.connected_by_model:new_selector()
  255.12 +  selector:add_where{ relationship.connected_by_my_id .. ' = ?', record[relationship.my_id] }
  255.13 +  local connected_by_records = selector:exec()
  255.14 +	
  255.15 +  local connections = {}
  255.16 +  for i, connected_by_record in ipairs(connected_by_records) do
  255.17 +    connections[connected_by_record[relationship.connected_by_foreign_id]] = connected_by_record
  255.18 +  end
  255.19 +
  255.20 +  local html = {}
  255.21 +
  255.22 +  if args.type == "checkboxes" then
  255.23 +    for i, foreign_record in ipairs(foreign_records) do
  255.24 +      local selected = ''
  255.25 +      if connections[foreign_record[relationship.foreign_id]] then
  255.26 +        selected = ' checked="1"'
  255.27 +      end
  255.28 +      html[#html + 1] = '<input type="checkbox" name="' .. name .. '" value="' .. foreign_record[relationship.foreign_id] .. '"' .. selected .. '>&nbsp;' .. convert.to_human(foreign_record[relationship.foreign_name], "string") .. '<br />\n'
  255.29 +    end
  255.30 + 
  255.31 +  else
  255.32 +    html[#html + 1] = '<select name="' .. name .. '" multiple="1">'
  255.33 +    for i, foreign_record in ipairs(foreign_records) do
  255.34 +      local selected = ''
  255.35 +      if connections[foreign_record[relationship.foreign_id]] then
  255.36 +        selected = ' selected="1"'
  255.37 +      end
  255.38 +      html[#html + 1] = '<option value="' .. foreign_record[relationship.foreign_id] .. '"' .. selected .. '>' .. convert.to_human(foreign_record[relationship.foreign_name], "string") .. '</option>\n'
  255.39 +    end
  255.40 +    html[#html + 1] = '</select>'
  255.41 +
  255.42 +  end
  255.43 +  	
  255.44 +  slot.put('<div class="ui_field ui_multiselect">\n')
  255.45 +  if args.label then
  255.46 +    slot.put('<div class="label">', encode.html(args.label), '</div>\n')
  255.47 +  end
  255.48 +  slot.put('<div class="value">',
  255.49 +        table.concat(html),
  255.50 +      '</div>\n',
  255.51 +    '</div>\n'
  255.52 +  )
  255.53 +end
  255.54 \ No newline at end of file
   256.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   256.2 +++ b/framework/env/ui_deprecated/select.lua	Sun Oct 25 12:00:00 2009 +0100
   256.3 @@ -0,0 +1,31 @@
   256.4 +function ui_deprecated.select(args)
   256.5 +  local record = assert(slot.get_state_table(), "ui_deprecated.select was not called within a form.").form_record
   256.6 +  local value = param.get(args.field) or record[args.field]
   256.7 +  local html_options = args.html_options or {}
   256.8 +  html_options.name = args.field
   256.9 +  
  256.10 +  ui_deprecated.tag("div", { html_options = { class="ui_field ui_select" }, content = function()
  256.11 +    if args.label then
  256.12 +      ui_deprecated.tag("div", { html_options = { class="label" }, content = function()
  256.13 +        ui_deprecated.text(args.label)
  256.14 +      end })
  256.15 +    end
  256.16 +    ui_deprecated.tag("div", { html_options = { class="value" }, content = function()
  256.17 +      ui_deprecated.tag("select", { html_options = html_options, content = function()
  256.18 +        if args.include_option then
  256.19 +          ui_deprecated.tag("option", { html_options = { value = "" }, content = args.include_option })
  256.20 +        end
  256.21 +        for i, object in ipairs(args.foreign_records) do
  256.22 +          local selected = nil
  256.23 +          if tostring(object[args.foreign_id]) == tostring(value) then
  256.24 +            selected = "1"
  256.25 +          end
  256.26 +          ui_deprecated.tag("option", { html_options = { value = object[args.foreign_id], selected = selected }, content = object[args.foreign_name] })
  256.27 +        end
  256.28 +      end }) -- /select
  256.29 +    end }) -- /div
  256.30 +  end }) -- /div
  256.31 +end
  256.32 +
  256.33 +
  256.34 +
   257.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   257.2 +++ b/framework/env/ui_deprecated/submit.lua	Sun Oct 25 12:00:00 2009 +0100
   257.3 @@ -0,0 +1,24 @@
   257.4 +--
   257.5 +-- Creates a submit buttom for a form
   257.6 +--
   257.7 +-- label      (string) The label of the box
   257.8 +--
   257.9 +-- Example:
  257.10 +--
  257.11 +--	ui_deprecated.submit({
  257.12 +--		label = 'Save'
  257.13 +--	})
  257.14 +--
  257.15 +
  257.16 +function ui_deprecated.submit(args)
  257.17 +  local args = args or {}
  257.18 +  args.label = args.label or 'Submit'
  257.19 +  slot.put(
  257.20 +    '<div class="ui_field ui_submit">',
  257.21 +      '<div class="label">&nbsp;</div>',
  257.22 +      '<div class="value">',
  257.23 +        '<input type="submit" value="', encode.html(args.label), '" />',
  257.24 +      '</div>',
  257.25 +    '</div>'
  257.26 +  )
  257.27 +end
   258.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   258.2 +++ b/framework/env/ui_deprecated/tag.lua	Sun Oct 25 12:00:00 2009 +0100
   258.3 @@ -0,0 +1,20 @@
   258.4 +function ui_deprecated.tag(name, args)
   258.5 +  
   258.6 +  local html_options = args.html_options or {}
   258.7 +   
   258.8 +  slot.put('<', name, ui_deprecated._stringify_table(html_options), '>')
   258.9 +
  258.10 +  if args.label then
  258.11 +    ui_deprecated.label(args.label)
  258.12 +  end
  258.13 +
  258.14 +  if type(args.content) == 'function' then
  258.15 +    args.content()
  258.16 +  else
  258.17 +    ui_deprecated.text(args.content)
  258.18 +  end
  258.19 +
  258.20 +  slot.put('</', name, '>')
  258.21 +
  258.22 +
  258.23 +end
  258.24 \ No newline at end of file
   259.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   259.2 +++ b/framework/env/ui_deprecated/text.lua	Sun Oct 25 12:00:00 2009 +0100
   259.3 @@ -0,0 +1,4 @@
   259.4 +
   259.5 +function ui_deprecated.text(value)
   259.6 +  slot.put(encode.html(convert.to_human(value)))
   259.7 +end
   259.8 \ No newline at end of file
   261.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   261.2 +++ b/libraries/atom/atom.lua	Sun Oct 25 12:00:00 2009 +0100
   261.3 @@ -0,0 +1,1529 @@
   261.4 +#!/usr/bin/env lua
   261.5 +
   261.6 +local _G             = _G
   261.7 +local _VERSION       = _VERSION
   261.8 +local assert         = assert
   261.9 +local error          = error
  261.10 +local getfenv        = getfenv
  261.11 +local getmetatable   = getmetatable
  261.12 +local ipairs         = ipairs
  261.13 +local module         = module
  261.14 +local next           = next
  261.15 +local pairs          = pairs
  261.16 +local print          = print
  261.17 +local rawequal       = rawequal
  261.18 +local rawget         = rawget
  261.19 +local rawset         = rawset
  261.20 +local require        = require
  261.21 +local select         = select
  261.22 +local setfenv        = setfenv
  261.23 +local setmetatable   = setmetatable
  261.24 +local tonumber       = tonumber
  261.25 +local tostring       = tostring
  261.26 +local type           = type
  261.27 +local unpack         = unpack
  261.28 +
  261.29 +local coroutine = coroutine
  261.30 +local io        = io
  261.31 +local math      = math
  261.32 +local os        = os
  261.33 +local string    = string
  261.34 +local table     = table
  261.35 +
  261.36 +module(...)
  261.37 +
  261.38 +
  261.39 +
  261.40 +---------------------------------------
  261.41 +-- general functions and definitions --
  261.42 +---------------------------------------
  261.43 +
  261.44 +--[[--
  261.45 +bool =            -- true, if value is an integer within resolution
  261.46 +atom.is_integer(
  261.47 +  value           -- value to be tested
  261.48 +)
  261.49 +
  261.50 +This function returns true if the given object is an integer within resolution.
  261.51 +
  261.52 +--]]--
  261.53 +function is_integer(i)
  261.54 +  return
  261.55 +    type(i) == "number" and i % 1 == 0 and
  261.56 +    (i + 1) - i == 1 and i - (i - 1) == 1
  261.57 +end
  261.58 +--//--
  261.59 +
  261.60 +--[[--
  261.61 +atom.not_a_number
  261.62 +
  261.63 +Value representing an invalid numeric result. Used for atom.integer.invalid and atom.number.invalid.
  261.64 +
  261.65 +--]]--
  261.66 +not_a_number = 0 / 0
  261.67 +--//--
  261.68 +
  261.69 +do
  261.70 +
  261.71 +  local shadow = setmetatable({}, { __mode = "k" })
  261.72 +
  261.73 +  local type_mt = { __index = {} }
  261.74 +
  261.75 +  function type_mt:__call(...)
  261.76 +    return self:new(...)
  261.77 +  end
  261.78 +
  261.79 +  function type_mt.__index:_create(data)
  261.80 +    local value = setmetatable({}, self)
  261.81 +    shadow[value] = data
  261.82 +    return value
  261.83 +  end
  261.84 +
  261.85 +  local function write_prohibited()
  261.86 +    error("Modification of an atom is prohibited.")
  261.87 +  end
  261.88 +
  261.89 +  -- returns a new type as a table, which serves also as metatable
  261.90 +  function create_new_type(name)
  261.91 +    local t = setmetatable(
  261.92 +      { methods = {}, getters = {}, name = name },
  261.93 +      type_mt
  261.94 +    )
  261.95 +    function t.__index(self, key)
  261.96 +      local data = shadow[self]
  261.97 +      local value = data[key]
  261.98 +      if value ~= nil then return value end
  261.99 +      local method = t.methods[key]
 261.100 +      if method then return method end
 261.101 +      local getter = t.getters[key]
 261.102 +      if getter then return getter(self) end
 261.103 +    end
 261.104 +    t.__newindex = write_prohibited
 261.105 +    return t
 261.106 +  end
 261.107 +
 261.108 +  --[[--
 261.109 +  bool =          -- true, if 'value' is of type 't'
 261.110 +  atom.has_type(
 261.111 +    value,        -- any value
 261.112 +    t             -- atom time, e.g. atom.date, or lua type, e.g. "string"
 261.113 +  )
 261.114 +
 261.115 +  This function checks, if a value is of a given type. The value may be an invalid value though, e.g. atom.date.invalid.
 261.116 +
 261.117 +  --]]--
 261.118 +  function has_type(value, t)
 261.119 +    if t == nil then error("No type passed to has_type(...) function.") end
 261.120 +    local lua_type = type(value)
 261.121 +    return
 261.122 +      lua_type == t or
 261.123 +      getmetatable(value) == t or
 261.124 +      (lua_type == "boolean" and t == _M.boolean) or
 261.125 +      (lua_type == "string" and t == _M.string) or (
 261.126 +        lua_type == "number" and
 261.127 +        (t == _M.number or (
 261.128 +          t == _M.integer and (
 261.129 +            not (value <= 0 or value >= 0) or (
 261.130 +              value % 1 == 0 and
 261.131 +              (value + 1) - value == 1 and
 261.132 +              value - (value - 1) == 1
 261.133 +            )
 261.134 +          )
 261.135 +        ))
 261.136 +      )
 261.137 +  end
 261.138 +  --//--
 261.139 +
 261.140 +  --[[--
 261.141 +  bool =          -- true, if 'value' is of type 't'
 261.142 +  atom.is_valid(
 261.143 +    value,        -- any value
 261.144 +    t             -- atom time, e.g. atom.date, or lua type, e.g. "string"
 261.145 +  )
 261.146 +
 261.147 +  This function checks, if a value is valid. It optionally checks, if the value is of a given type.
 261.148 +
 261.149 +  --]]--
 261.150 +  function is_valid(value, t)
 261.151 +    local lua_type = type(value)
 261.152 +    if lua_type == "table" then
 261.153 +      local mt = getmetatable(value)
 261.154 +      if t then
 261.155 +        return t == mt and not value.invalid
 261.156 +      else
 261.157 +        return (getmetatable(mt) == type_mt) and not value.invalid
 261.158 +      end
 261.159 +    elseif lua_type == "boolean" then
 261.160 +      return not t or t == "boolean" or t == _M.boolean
 261.161 +    elseif lua_type == "string" then
 261.162 +      return not t or t == "string" or t == _M.string
 261.163 +    elseif lua_type == "number" then
 261.164 +      if t == _M.integer then
 261.165 +        return
 261.166 +          value % 1 == 0 and
 261.167 +          (value + 1) - value == 1 and
 261.168 +          value - (value - 1) == 1
 261.169 +      else
 261.170 +        return
 261.171 +          (not t or t == "number" or t == _M.number) and
 261.172 +          (value <= 0 or value >= 0)
 261.173 +      end
 261.174 +    else
 261.175 +      return false
 261.176 +    end
 261.177 +  end
 261.178 +  --//--
 261.179 +
 261.180 +end
 261.181 +
 261.182 +--[[--
 261.183 +string =    -- string representation to be passed to a load function
 261.184 +atom.dump(
 261.185 +  value     -- value to be dumped
 261.186 +)
 261.187 +
 261.188 +This function returns a string representation of the given value.
 261.189 +
 261.190 +--]]--
 261.191 +function dump(obj)
 261.192 +  if obj == nil then
 261.193 +    return ""
 261.194 +  else
 261.195 +    return tostring(obj)
 261.196 +  end
 261.197 +end
 261.198 +--//--
 261.199 +
 261.200 +
 261.201 +
 261.202 +-------------
 261.203 +-- boolean --
 261.204 +-------------
 261.205 +
 261.206 +boolean = { name = "boolean" }
 261.207 +
 261.208 +--[[--
 261.209 +bool =              -- true, false, or nil
 261.210 +atom.boolean:load(
 261.211 +  string            -- string to be interpreted as boolean
 261.212 +)
 261.213 +
 261.214 +This method returns true or false or nil, depending on the input string.
 261.215 +
 261.216 +--]]--
 261.217 +function boolean:load(str)
 261.218 +  if type(str) ~= "string" then
 261.219 +    error("String expected")
 261.220 +  elseif str == "" then
 261.221 +    return nil
 261.222 +  elseif string.find(str, "^[TtYy1]") then
 261.223 +    return true
 261.224 +  elseif string.find(str, "^[FfNn0]") then
 261.225 +    return false
 261.226 +  else
 261.227 +    return nil  -- we don't have an undefined bool
 261.228 +  end
 261.229 +end
 261.230 +--//--
 261.231 +
 261.232 +
 261.233 +
 261.234 +------------
 261.235 +-- string --
 261.236 +------------
 261.237 +
 261.238 +_M.string = { name = "string" }
 261.239 +
 261.240 +--[[--
 261.241 +string =            -- the same string
 261.242 +atom.string:load(
 261.243 +  string            -- a string
 261.244 +)
 261.245 +
 261.246 +This method returns the passed string, or throws an error, if the passed argument is not a string.
 261.247 +
 261.248 +--]]--
 261.249 +function _M.string:load(str)
 261.250 +  if type(str) ~= "string" then
 261.251 +    error("String expected")
 261.252 +  else
 261.253 +    return str
 261.254 +  end
 261.255 +end
 261.256 +--//--
 261.257 +
 261.258 +
 261.259 +
 261.260 +-------------
 261.261 +-- integer --
 261.262 +-------------
 261.263 +
 261.264 +integer = { name = "integer" }
 261.265 +
 261.266 +--[[--
 261.267 +int =              -- an integer or atom.integer.invalid (atom.not_a_number)
 261.268 +atom.integer:load(
 261.269 +  string           -- a string representing an integer
 261.270 +)
 261.271 +
 261.272 +This method returns an integer represented by the given string. If the string doesn't represent a valid integer, then not-a-number is returned.
 261.273 +
 261.274 +--]]--
 261.275 +function integer:load(str)
 261.276 +  if type(str) ~= "string" then
 261.277 +    error("String expected")
 261.278 +  elseif str == "" then
 261.279 +    return nil
 261.280 +  else
 261.281 +    local num = tonumber(str)
 261.282 +    if is_integer(num) then return num else return not_a_number end
 261.283 +  end
 261.284 +end
 261.285 +--//--
 261.286 +
 261.287 +--[[--
 261.288 +atom.integer.invalid
 261.289 +
 261.290 +This represents an invalid integer.
 261.291 +
 261.292 +--]]--
 261.293 +integer.invalid = not_a_number
 261.294 +--//
 261.295 +
 261.296 +
 261.297 +
 261.298 +------------
 261.299 +-- number --
 261.300 +------------
 261.301 +
 261.302 +number = create_new_type("number")
 261.303 +
 261.304 +--[[--
 261.305 +int =              -- a number or atom.number.invalid (atom.not_a_number)
 261.306 +atom.number:load(
 261.307 +  string           -- a string representing a number
 261.308 +)
 261.309 +
 261.310 +This method returns a number represented by the given string. If the string doesn't represent a valid number, then not-a-number is returned.
 261.311 +
 261.312 +--]]--
 261.313 +function number:load(str)
 261.314 +  if type(str) ~= "string" then
 261.315 +    error("String expected")
 261.316 +  elseif str == "" then
 261.317 +    return nil
 261.318 +  else
 261.319 +    return tonumber(str) or not_a_number
 261.320 +  end
 261.321 +end
 261.322 +--//--
 261.323 +
 261.324 +--[[--
 261.325 +atom.number.invalid
 261.326 +
 261.327 +This represents an invalid number.
 261.328 +
 261.329 +--]]--
 261.330 +number.invalid = not_a_number
 261.331 +--//--
 261.332 +
 261.333 +
 261.334 +
 261.335 +--------------
 261.336 +-- fraction --
 261.337 +--------------
 261.338 +
 261.339 +fraction = create_new_type("fraction")
 261.340 +
 261.341 +--[[--
 261.342 +i =        -- the greatest common divisor (GCD) of all given natural numbers
 261.343 +atom.gcd(
 261.344 +  a,      -- a natural number
 261.345 +  b,      -- another natural number
 261.346 +  ...     -- optionally more natural numbers
 261.347 +)
 261.348 +
 261.349 +This function returns the greatest common divisor (GCD) of two or more natural numbers.
 261.350 +
 261.351 +--]]--
 261.352 +function gcd(a, b, ...)
 261.353 +  if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
 261.354 +  if b == nil then
 261.355 +    return a
 261.356 +  else
 261.357 +    if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
 261.358 +    if ... == nil then
 261.359 +      local k = 0
 261.360 +      local t
 261.361 +      while a % 2 == 0 and b % 2 == 0 do
 261.362 +        a = a / 2; b = b / 2; k = k + 1
 261.363 +      end
 261.364 +      if a % 2 == 0 then t = a else t = -b end
 261.365 +      while t ~= 0 do
 261.366 +        while t % 2 == 0 do t = t / 2 end
 261.367 +        if t > 0 then a = t else b = -t end
 261.368 +        t = a - b
 261.369 +      end
 261.370 +      return a * 2 ^ k
 261.371 +    else
 261.372 +      return gcd(gcd(a, b), ...)
 261.373 +    end
 261.374 +  end
 261.375 +end
 261.376 +--//--
 261.377 +
 261.378 +--[[--
 261.379 +i =        --the least common multiple (LCD) of all given natural numbers
 261.380 +atom.lcm(
 261.381 +  a,       -- a natural number
 261.382 +  b,       -- another natural number
 261.383 +  ...      -- optionally more natural numbers
 261.384 +)
 261.385 +
 261.386 +This function returns the least common multiple (LCD) of two or more natural numbers.
 261.387 +
 261.388 +--]]--
 261.389 +function lcm(a, b, ...)
 261.390 +  if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
 261.391 +  if b == nil then
 261.392 +    return a
 261.393 +  else
 261.394 +    if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
 261.395 +    if ... == nil then
 261.396 +      return a * b / gcd(a, b)
 261.397 +    else
 261.398 +      return lcm(lcm(a, b), ...)
 261.399 +    end
 261.400 +  end
 261.401 +end
 261.402 +--//--
 261.403 +
 261.404 +--[[--
 261.405 +atom.fraction.invalid
 261.406 +
 261.407 +Value representing an invalid fraction.
 261.408 +
 261.409 +--]]--
 261.410 +fraction.invalid = fraction:_create{
 261.411 +  numerator = not_a_number, denominator = not_a_number, invalid = true
 261.412 +}
 261.413 +--//--
 261.414 +
 261.415 +--[[--
 261.416 +frac =              -- fraction
 261.417 +atom.fraction:new(
 261.418 +  numerator,        -- numerator
 261.419 +  denominator       -- denominator
 261.420 +)
 261.421 +
 261.422 +This method creates a new fraction.
 261.423 +
 261.424 +--]]--
 261.425 +function fraction:new(numerator, denominator)
 261.426 +  if not (
 261.427 +    (numerator == nil   or type(numerator)   == "number") and
 261.428 +    (denominator == nil or type(denominator) == "number")
 261.429 +  ) then
 261.430 +    error("Invalid arguments passed to fraction constructor.")
 261.431 +  elseif
 261.432 +    (not is_integer(numerator)) or
 261.433 +    (denominator and (not is_integer(denominator)))
 261.434 +  then
 261.435 +    return fraction.invalid
 261.436 +  elseif denominator then
 261.437 +    if denominator == 0 then
 261.438 +      return fraction.invalid
 261.439 +    elseif numerator == 0 then
 261.440 +      return fraction:_create{ numerator = 0, denominator = 1, float = 0 }
 261.441 +    else
 261.442 +      local d = gcd(math.abs(numerator), math.abs(denominator))
 261.443 +      if denominator < 0 then d = -d end
 261.444 +      local numerator2, denominator2 = numerator / d, denominator / d
 261.445 +      return fraction:_create{
 261.446 +        numerator   = numerator2,
 261.447 +        denominator = denominator2,
 261.448 +        float       = numerator2 / denominator2
 261.449 +      }
 261.450 +    end
 261.451 +  else
 261.452 +    return fraction:_create{
 261.453 +      numerator = numerator, denominator = 1, float = numerator
 261.454 +    }
 261.455 +  end
 261.456 +end
 261.457 +--//--
 261.458 +
 261.459 +--[[--
 261.460 +frac =               -- fraction represented by the given string
 261.461 +atom.fraction:load(
 261.462 +  string             -- string representation of a fraction
 261.463 +)
 261.464 +
 261.465 +This method returns a fraction represented by the given string.
 261.466 +
 261.467 +--]]--
 261.468 +function fraction:load(str)
 261.469 +  if str == "" then
 261.470 +    return nil
 261.471 +  else
 261.472 +    local sign, int = string.match(str, "^(%-?)([0-9]+)$")
 261.473 +    if sign == "" then return fraction:new(tonumber(int))
 261.474 +    elseif sign == "-" then return fraction:new(- tonumber(int))
 261.475 +    end
 261.476 +    local sign, n, d = string.match(str, "^(%-?)([0-9]+)/([0-9]+)$")
 261.477 +    if sign == "" then return fraction:new(tonumber(n), tonumber(d))
 261.478 +    elseif sign == "-" then return fraction:new(- tonumber(n), tonumber(d))
 261.479 +    end
 261.480 +    return fraction.invalid
 261.481 +  end
 261.482 +end
 261.483 +--//--
 261.484 +
 261.485 +function fraction:__tostring()
 261.486 +  if self.invalid then
 261.487 +    return "not_a_fraction"
 261.488 +  else
 261.489 +    return self.numerator .. "/" .. self.denominator
 261.490 +  end
 261.491 +end
 261.492 +
 261.493 +function fraction.__eq(value1, value2)
 261.494 +  if value1.invalid or value2.invalid then
 261.495 +    return false
 261.496 +  else
 261.497 +    return
 261.498 +      value1.numerator == value2.numerator and
 261.499 +      value1.denominator == value2.denominator
 261.500 +  end
 261.501 +end
 261.502 +
 261.503 +function fraction.__lt(value1, value2)
 261.504 +  if value1.invalid or value2.invalid then
 261.505 +    return false
 261.506 +  else
 261.507 +    return value1.float < value2.float
 261.508 +  end
 261.509 +end
 261.510 +
 261.511 +function fraction.__le(value1, value2)
 261.512 +  if value1.invalid or value2.invalid then
 261.513 +    return false
 261.514 +  else
 261.515 +    return value1.float <= value2.float
 261.516 +  end
 261.517 +end
 261.518 +
 261.519 +function fraction:__unm()
 261.520 +  return fraction(-self.numerator, self.denominator)
 261.521 +end
 261.522 +
 261.523 +do
 261.524 +
 261.525 +  local function extract(value1, value2)
 261.526 +    local n1, d1, n2, d2
 261.527 +    if getmetatable(value1) == fraction then
 261.528 +      n1 = value1.numerator
 261.529 +      d1 = value1.denominator
 261.530 +    elseif type(value1) == "number" then
 261.531 +      n1 = value1
 261.532 +      d1 = 1
 261.533 +    else
 261.534 +      error("Left operand of operator has wrong type.")
 261.535 +    end
 261.536 +    if getmetatable(value2) == fraction then
 261.537 +      n2 = value2.numerator
 261.538 +      d2 = value2.denominator
 261.539 +    elseif type(value2) == "number" then
 261.540 +      n2 = value2
 261.541 +      d2 = 1
 261.542 +    else
 261.543 +      error("Right operand of operator has wrong type.")
 261.544 +    end
 261.545 +    return n1, d1, n2, d2
 261.546 +  end
 261.547 +
 261.548 +  function fraction.__add(value1, value2)
 261.549 +    local n1, d1, n2, d2 = extract(value1, value2)
 261.550 +    return fraction(n1 * d2 + n2 * d1, d1 * d2)
 261.551 +  end
 261.552 +
 261.553 +  function fraction.__sub(value1, value2)
 261.554 +    local n1, d1, n2, d2 = extract(value1, value2)
 261.555 +    return fraction(n1 * d2 - n2 * d1, d1 * d2)
 261.556 +  end
 261.557 +
 261.558 +  function fraction.__mul(value1, value2)
 261.559 +    local n1, d1, n2, d2 = extract(value1, value2)
 261.560 +    return fraction(n1 * n2, d1 * d2)
 261.561 +  end
 261.562 +
 261.563 +  function fraction.__div(value1, value2)
 261.564 +    local n1, d1, n2, d2 = extract(value1, value2)
 261.565 +    return fraction(n1 * d2, d1 * n2)
 261.566 +  end
 261.567 +
 261.568 +  function fraction.__pow(value1, value2)
 261.569 +    local n1, d1, n2, d2 = extract(value1, value2)
 261.570 +    local n1_abs = math.abs(n1)
 261.571 +    local d1_abs = math.abs(d1)
 261.572 +    local n2_abs = math.abs(n2)
 261.573 +    local d2_abs = math.abs(d2)
 261.574 +    local numerator, denominator
 261.575 +    if d2_abs == 1 then
 261.576 +      numerator = n1_abs
 261.577 +      denominator = d1_abs
 261.578 +    else
 261.579 +      numerator = 0
 261.580 +      while true do
 261.581 +        local t = numerator ^ d2_abs
 261.582 +        if t == n1_abs then break end
 261.583 +        if not (t < n1_abs) then return value1.float / value2.float end
 261.584 +        numerator = numerator + 1
 261.585 +      end
 261.586 +      denominator = 1
 261.587 +      while true do
 261.588 +        local t = denominator ^ d2_abs
 261.589 +        if t == d1_abs then break end
 261.590 +        if not (t < d1_abs) then return value1.float / value2.float end
 261.591 +        denominator = denominator + 1
 261.592 +      end
 261.593 +    end
 261.594 +    if n1 < 0 then
 261.595 +      if d2_abs % 2 == 1 then
 261.596 +        numerator = -numerator
 261.597 +      else
 261.598 +        return fraction.invalid
 261.599 +      end
 261.600 +    end
 261.601 +    if n2 < 0 then
 261.602 +      numerator, denominator = denominator, numerator
 261.603 +    end
 261.604 +    return fraction(numerator ^ n2_abs, denominator ^ n2_abs)
 261.605 +  end
 261.606 +
 261.607 +end
 261.608 +
 261.609 +
 261.610 +
 261.611 +----------
 261.612 +-- date --
 261.613 +----------
 261.614 +
 261.615 +date = create_new_type("date")
 261.616 +
 261.617 +do
 261.618 +  local c1 = 365             -- days of a non-leap year
 261.619 +  local c4 = 4 * c1 + 1      -- days of a full 4 year cycle
 261.620 +  local c100 = 25 * c4 - 1   -- days of a full 100 year cycle
 261.621 +  local c400 = 4 * c100 + 1  -- days of a full 400 year cycle
 261.622 +  local get_month_offset     -- function returning days elapsed within
 261.623 +                             -- the given year until the given month
 261.624 +                             -- (exclusive the given month)
 261.625 +  do
 261.626 +    local normal_month_offsets = {}
 261.627 +    local normal_month_lengths = {
 261.628 +      31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
 261.629 +    }
 261.630 +    local sum = 0
 261.631 +    for i = 1, 12 do
 261.632 +      normal_month_offsets[i] = sum
 261.633 +      sum = sum + normal_month_lengths[i]
 261.634 +    end
 261.635 +    function get_month_offset(year, month)
 261.636 +      if
 261.637 +        (((year % 4 == 0) and not (year % 100 == 0)) or (year % 400 == 0))
 261.638 +        and month > 2
 261.639 +      then
 261.640 +        return normal_month_offsets[month] + 1
 261.641 +      else
 261.642 +        return normal_month_offsets[month]
 261.643 +      end
 261.644 +    end
 261.645 +  end
 261.646 +
 261.647 +  --[[--
 261.648 +  jd =                  -- days from January 1st 1970
 261.649 +  atom.date.ymd_to_jd(
 261.650 +    year,               -- year
 261.651 +    month,              -- month from 1 to 12
 261.652 +    day                 -- day from 1 to 31
 261.653 +  )
 261.654 +
 261.655 +  This function calculates the days from January 1st 1970 for a given year, month and day.
 261.656 +
 261.657 +  --]]--
 261.658 +  local offset = 0
 261.659 +  function date.ymd_to_jd(year, month, day)
 261.660 +    assert(is_integer(year), "Invalid year specified.")
 261.661 +    assert(is_integer(month), "Invalid month specified.")
 261.662 +    assert(is_integer(day), "Invalid day specified.")
 261.663 +    local calc_year = year - 1
 261.664 +    local n400 = math.floor(calc_year / 400)
 261.665 +    local r400 = calc_year % 400
 261.666 +    local n100 = math.floor(r400 / 100)
 261.667 +    local r100 = r400 % 100
 261.668 +    local n4 = math.floor(r100 / 4)
 261.669 +    local n1 = r100 % 4
 261.670 +    local jd = (
 261.671 +      c400 * n400 + c100 * n100 + c4 * n4 + c1 * n1 +
 261.672 +      get_month_offset(year, month) + (day - 1)
 261.673 +    )
 261.674 +    return jd - offset
 261.675 +  end
 261.676 +  offset = date.ymd_to_jd(1970, 1, 1)
 261.677 +  --//--
 261.678 +
 261.679 +  --[[--
 261.680 +  year,                 -- year
 261.681 +  month,                -- month from 1 to 12
 261.682 +  day =                 -- day from 1 to 31
 261.683 +  atom.date.jd_to_ymd(
 261.684 +    jd,                 -- days from January 1st 1970
 261.685 +  )
 261.686 +
 261.687 +  Given the days from January 1st 1970 this function returns year, month and day.
 261.688 +
 261.689 +  --]]--
 261.690 +  function date.jd_to_ymd(jd)
 261.691 +    assert(is_integer(jd), "Invalid julian date specified.")
 261.692 +    local calc_jd = jd + offset
 261.693 +    assert(is_integer(calc_jd), "Julian date is out of range.")
 261.694 +    local n400 = math.floor(calc_jd / c400)
 261.695 +    local r400 = calc_jd % c400
 261.696 +    local n100 = math.floor(r400 / c100)
 261.697 +    local r100 = r400 % c100
 261.698 +    if n100 == 4 then n100, r100 = 3, c100 end
 261.699 +    local n4 = math.floor(r100 / c4)
 261.700 +    local r4 = r100 % c4
 261.701 +    local n1 = math.floor(r4 / c1)
 261.702 +    local r1 = r4 % c1
 261.703 +    if n1 == 4 then n1, r1 = 3, c1 end
 261.704 +    local year = 1 + 400 * n400 + 100 * n100 + 4 * n4 + n1
 261.705 +    local month = 1 + math.floor(r1 / 31)
 261.706 +    local month_offset = get_month_offset(year, month)
 261.707 +    if month < 12 then
 261.708 +      local next_month_offset = get_month_offset(year, month + 1)
 261.709 +      if r1 >= next_month_offset then
 261.710 +        month = month + 1
 261.711 +        month_offset = next_month_offset
 261.712 +      end
 261.713 +    end
 261.714 +    local day = 1 + r1 - month_offset
 261.715 +    return year, month, day
 261.716 +  end
 261.717 +  --//--
 261.718 +end
 261.719 +
 261.720 +--[[--
 261.721 +atom.date.invalid
 261.722 +
 261.723 +Value representing an invalid date.
 261.724 +
 261.725 +--]]--
 261.726 +date.invalid = date:_create{
 261.727 +  jd = not_a_number,
 261.728 +  year = not_a_number, month = not_a_number, day = not_a_number,
 261.729 +  invalid = true
 261.730 +}
 261.731 +--//--
 261.732 +
 261.733 +--[[--
 261.734 +d =                             -- date based on the given data
 261.735 +atom.date:new{
 261.736 +  jd           = jd,            -- days since January 1st 1970
 261.737 +  year         = year,          -- year
 261.738 +  month        = month,         -- month from 1 to 12
 261.739 +  day          = day,           -- day from 1 to 31
 261.740 +  iso_weekyear = iso_weekyear,  -- year according to ISO 8601
 261.741 +  iso_week     = iso_week,      -- week number according to ISO 8601
 261.742 +  iso_weekday  = iso_weekday,   -- day of week from 1 for monday to 7 for sunday
 261.743 +  us_weekyear  = us_weekyear,   -- year
 261.744 +  us_week      = us_week,       -- week number according to US style counting
 261.745 +  us_weekday   = us_weekday     -- day of week from 1 for sunday to 7 for saturday
 261.746 +}
 261.747 +
 261.748 +This method returns a new date value, based on given data.
 261.749 +
 261.750 +--]]--
 261.751 +function date:new(args)
 261.752 +  local args = args
 261.753 +  if type(args) == "number" then args = { jd = args } end
 261.754 +  if type(args) == "table" then
 261.755 +    local year, month, day = args.year, args.month, args.day
 261.756 +    local jd = args.jd
 261.757 +    local iso_weekyear = args.iso_weekyear
 261.758 +    local iso_week     = args.iso_week
 261.759 +    local iso_weekday  = args.iso_weekday
 261.760 +    local us_week      = args.us_week
 261.761 +    local us_weekday   = args.us_weekday
 261.762 +    if
 261.763 +      type(year)  == "number" and
 261.764 +      type(month) == "number" and
 261.765 +      type(day)   == "number"
 261.766 +    then
 261.767 +      if
 261.768 +        is_integer(year)  and year >= 1  and year <= 9999 and
 261.769 +        is_integer(month) and month >= 1 and month <= 12  and
 261.770 +        is_integer(day)   and day >= 1   and day <= 31
 261.771 +      then
 261.772 +        return date:_create{
 261.773 +          jd = date.ymd_to_jd(year, month, day),
 261.774 +          year = year, month = month, day = day
 261.775 +        }
 261.776 +      else
 261.777 +        return date.invalid
 261.778 +      end
 261.779 +    elseif type(jd) == "number" then
 261.780 +      if is_integer(jd) and jd >= -719162 and jd <= 2932896 then
 261.781 +        local year, month, day = date.jd_to_ymd(jd)
 261.782 +        return date:_create{
 261.783 +          jd = jd, year = year, month = month, day = day
 261.784 +        }
 261.785 +      else
 261.786 +        return date.invalid
 261.787 +      end
 261.788 +    elseif
 261.789 +      type(year)        == "number" and not iso_weekyear and
 261.790 +      type(iso_week)    == "number" and
 261.791 +      type(iso_weekday) == "number"
 261.792 +    then
 261.793 +      if
 261.794 +        is_integer(year) and
 261.795 +        is_integer(iso_week)     and iso_week >= 0 and iso_week <= 53 and
 261.796 +        is_integer(iso_weekday)  and iso_weekday >= 1 and iso_weekday <= 7
 261.797 +      then
 261.798 +        local jan4 = date{ year = year, month = 1, day = 4 }
 261.799 +        local reference = jan4 - jan4.iso_weekday - 7  -- Sun. of week -1
 261.800 +        return date(reference + 7 * iso_week + iso_weekday)
 261.801 +      else
 261.802 +        return date.invalid
 261.803 +      end
 261.804 +    elseif
 261.805 +      type(iso_weekyear) == "number" and not year and
 261.806 +      type(iso_week)     == "number" and
 261.807 +      type(iso_weekday)  == "number"
 261.808 +    then
 261.809 +      if
 261.810 +        is_integer(iso_weekyear) and
 261.811 +        is_integer(iso_week)     and iso_week >= 0 and iso_week <= 53 and
 261.812 +        is_integer(iso_weekday)  and iso_weekday >= 1 and iso_weekday <= 7
 261.813 +      then
 261.814 +        local guessed = date{
 261.815 +          year        = iso_weekyear,
 261.816 +          iso_week    = iso_week,
 261.817 +          iso_weekday = iso_weekday
 261.818 +        }
 261.819 +        if guessed.invalid or guessed.iso_weekyear == iso_weekyear then
 261.820 +          return guessed
 261.821 +        else
 261.822 +          local year
 261.823 +          if iso_week <= 1 then
 261.824 +            year = iso_weekyear - 1
 261.825 +          elseif iso_week >= 52 then
 261.826 +            year = iso_weekyear + 1
 261.827 +          else
 261.828 +            error("Internal error in ISO week computation occured.")
 261.829 +          end
 261.830 +          return date{
 261.831 +            year = year, iso_week = iso_week, iso_weekday = iso_weekday
 261.832 +          }
 261.833 +        end
 261.834 +      else
 261.835 +        return date.invalid
 261.836 +      end
 261.837 +    elseif
 261.838 +      type(year) == "number" and
 261.839 +      type(us_week)     == "number" and
 261.840 +      type(us_weekday)  == "number"
 261.841 +    then
 261.842 +      if
 261.843 +        is_integer(year) and
 261.844 +        is_integer(us_week)     and us_week >= 0    and us_week <= 54   and
 261.845 +        is_integer(us_weekday)  and us_weekday >= 1 and us_weekday <= 7
 261.846 +      then
 261.847 +        local jan1 = date{ year = year, month = 1, day = 1 }
 261.848 +        local reference = jan1 - jan1.us_weekday - 7  -- Sat. of week -1
 261.849 +        return date(reference + 7 * us_week + us_weekday)
 261.850 +      else
 261.851 +        return date.invalid
 261.852 +      end
 261.853 +    end
 261.854 +  end
 261.855 +  error("Illegal arguments passed to date constructor.")
 261.856 +end
 261.857 +--//--
 261.858 +
 261.859 +--[[--
 261.860 +atom.date:get_current()
 261.861 +
 261.862 +This function returns today's date.
 261.863 +
 261.864 +--]]--
 261.865 +function date:get_current()
 261.866 +  local now = os.date("*t")
 261.867 +  return date{
 261.868 +    year = now.year, month = now.month, day = now.day
 261.869 +  }
 261.870 +end
 261.871 +--//--
 261.872 +
 261.873 +--[[--
 261.874 +date =           -- date represented by the string
 261.875 +atom.date:load(
 261.876 +  string         -- string representing a date
 261.877 +)
 261.878 +
 261.879 +This method returns a date represented by the given string.
 261.880 +
 261.881 +--]]--
 261.882 +function date:load(str)
 261.883 +  if str == "" then
 261.884 +    return nil
 261.885 +  else
 261.886 +    local year, month, day = string.match(
 261.887 +      str, "^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"
 261.888 +    )
 261.889 +    if year then
 261.890 +      return date{
 261.891 +        year = tonumber(year),
 261.892 +        month = tonumber(month),
 261.893 +        day = tonumber(day)
 261.894 +      }
 261.895 +    else
 261.896 +      return date.invalid
 261.897 +    end
 261.898 +  end
 261.899 +end
 261.900 +--//--
 261.901 +
 261.902 +function date:__tostring()
 261.903 +  if self.invalid then
 261.904 +    return "invalid_date"
 261.905 +  else
 261.906 +    return string.format(
 261.907 +      "%04i-%02i-%02i", self.year, self.month, self.day
 261.908 +    )
 261.909 +  end
 261.910 +end
 261.911 +
 261.912 +function date.getters:midnight()
 261.913 +  return time{ year = self.year, month = self.month, day = self.day }
 261.914 +end
 261.915 +
 261.916 +function date.getters:midday()
 261.917 +  return time{
 261.918 +    year = self.year, month = self.month, day = self.day,
 261.919 +    hour = 12
 261.920 +  }
 261.921 +end
 261.922 +
 261.923 +function date.getters:iso_weekday()  -- 1 = Monday
 261.924 +  return (self.jd + 3) % 7 + 1
 261.925 +end
 261.926 +
 261.927 +function date.getters:us_weekday()  -- 1 = Sunday
 261.928 +  return (self.jd + 4) % 7 + 1
 261.929 +end
 261.930 +
 261.931 +function date.getters:iso_weekyear()  -- ISO week-numbering year
 261.932 +  local year, month, day = self.year, self.month, self.day
 261.933 +  local iso_weekday      = self.iso_weekday
 261.934 +  if month == 1 then
 261.935 +    if
 261.936 +      (day == 3 and iso_weekday == 7) or
 261.937 +      (day == 2 and iso_weekday >= 6) or
 261.938 +      (day == 1 and iso_weekday >= 5)
 261.939 +    then
 261.940 +      return year - 1
 261.941 +    end
 261.942 +  elseif month == 12 then
 261.943 +    if
 261.944 +      (day == 29 and iso_weekday == 1) or
 261.945 +      (day == 30 and iso_weekday <= 2) or
 261.946 +      (day == 31 and iso_weekday <= 3)
 261.947 +    then
 261.948 +      return year + 1
 261.949 +    end
 261.950 +  end
 261.951 +  return year
 261.952 +end
 261.953 +
 261.954 +function date.getters:iso_week()
 261.955 +  local jan4 = date{ year = self.iso_weekyear, month = 1, day = 4 }
 261.956 +  local reference = jan4.jd - jan4.iso_weekday - 6  -- monday of week 0
 261.957 +  return math.floor((self.jd - reference) / 7)
 261.958 +end
 261.959 +
 261.960 +function date.getters:us_week()
 261.961 +  local jan1 = date{ year = self.year, month = 1, day = 1 }
 261.962 +  local reference = jan1.jd - jan1.us_weekday - 6  -- sunday of week 0
 261.963 +  return math.floor((self.jd - reference) / 7)
 261.964 +end
 261.965 +
 261.966 +function date.__eq(value1, value2)
 261.967 +  if value1.invalid or value2.invalid then
 261.968 +    return false
 261.969 +  else
 261.970 +    return value1.jd == value2.jd
 261.971 +  end
 261.972 +end
 261.973 +
 261.974 +function date.__lt(value1, value2)
 261.975 +  if value1.invalid or value2.invalid then
 261.976 +    return false
 261.977 +  else
 261.978 +    return value1.jd < value2.jd
 261.979 +  end
 261.980 +end
 261.981 +
 261.982 +function date.__le(value1, value2)
 261.983 +  if value1.invalid or value2.invalid then
 261.984 +    return false
 261.985 +  else
 261.986 +    return value1.jd <= value2.jd
 261.987 +  end
 261.988 +end
 261.989 +
 261.990 +function date.__add(value1, value2)
 261.991 +  if getmetatable(value1) == date then
 261.992 +    if getmetatable(value2) == date then
 261.993 +      error("Can not add two dates.")
 261.994 +    elseif type(value2) == "number" then
 261.995 +      return date(value1.jd + value2)
 261.996 +    else
 261.997 +      error("Right operand of '+' operator has wrong type.")
 261.998 +    end
 261.999 +  elseif type(value1) == "number" then
261.1000 +    if getmetatable(value2) == date then
261.1001 +      return date(value1 + value2.jd)
261.1002 +    else
261.1003 +      error("Assertion failed")
261.1004 +    end
261.1005 +  else
261.1006 +    error("Left operand of '+' operator has wrong type.")
261.1007 +  end
261.1008 +end
261.1009 +
261.1010 +function date.__sub(value1, value2)
261.1011 +  if not getmetatable(value1) == date then
261.1012 +    error("Left operand of '-' operator has wrong type.")
261.1013 +  end
261.1014 +  if getmetatable(value2) == date then
261.1015 +    return value1.jd - value2.jd  -- TODO: transform to interval
261.1016 +  elseif type(value2) == "number" then
261.1017 +    return date(value1.jd - value2)
261.1018 +  else
261.1019 +    error("Right operand of '-' operator has wrong type.")
261.1020 +  end
261.1021 +end
261.1022 +
261.1023 +
261.1024 +
261.1025 +---------------
261.1026 +-- timestamp --
261.1027 +---------------
261.1028 +
261.1029 +timestamp = create_new_type("timestamp")
261.1030 +
261.1031 +--[[--
261.1032 +tsec =                          -- seconds since January 1st 1970 00:00
261.1033 +atom.timestamp.ymdhms_to_tsec(
261.1034 +  year,                         -- year
261.1035 +  month,                        -- month from 1 to 12
261.1036 +  day,                          -- day from 1 to 31
261.1037 +  hour,                         -- hour from 0 to 23
261.1038 +  minute,                       -- minute from 0 to 59
261.1039 +  second                        -- second from 0 to 59
261.1040 +)
261.1041 +
261.1042 +Given the year, month, day, hour, minute and second, this function returns the number of seconds since January 1st 1970 00:00.
261.1043 +
261.1044 +--]]--
261.1045 +function timestamp.ymdhms_to_tsec(year, month, day, hour, minute, second)
261.1046 +  return
261.1047 +    86400 * date.ymd_to_jd(year, month, day) +
261.1048 +    3600 * hour + 60 * minute + second
261.1049 +end
261.1050 +--//--
261.1051 +
261.1052 +--[[--
261.1053 +year,                      -- year
261.1054 +month,                     -- month from 1 to 12
261.1055 +day,                       -- day from 1 to 31
261.1056 +hour,                      -- hour from 0 to 23
261.1057 +minute,                    -- minute from 0 to 59
261.1058 +second =                   -- second from 0 to 59
261.1059 +atom.timestamp.tsec_to_ymdhms(
261.1060 +  tsec                     -- seconds since January 1st 1970 00:00
261.1061 +)
261.1062 +
261.1063 +Given the seconds since January 1st 1970 00:00, this function returns the year, month, day, hour, minute and second.
261.1064 +
261.1065 +--]]--
261.1066 +function timestamp.tsec_to_ymdhms(tsec)
261.1067 +  local jd   = math.floor(tsec / 86400)
261.1068 +  local dsec = tsec % 86400
261.1069 +  local year, month, day = date.jd_to_ymd(jd)
261.1070 +  local hour   = math.floor(dsec / 3600)
261.1071 +  local minute = math.floor((dsec % 3600) / 60)
261.1072 +  local second = dsec % 60
261.1073 +  return year, month, day, hour, minute, second
261.1074 +end
261.1075 +--//--
261.1076 +
261.1077 +--[[--
261.1078 +timestamp.invalid
261.1079 +
261.1080 +Value representing an invalid timestamp.
261.1081 +
261.1082 +--]]--
261.1083 +timestamp.invalid = timestamp:_create{
261.1084 +  tsec = not_a_number,
261.1085 +  year = not_a_number, month = not_a_number, day = not_a_number,
261.1086 +  hour = not_a_number, minute = not_a_number, second = not_a_number,
261.1087 +  invalid = true
261.1088 +}
261.1089 +--//--
261.1090 +
261.1091 +--[[--
261.1092 +ts =                 -- timestamp based on given data
261.1093 +atom.timestamp:new{
261.1094 +  tsec   = tsec,     -- seconds since January 1st 1970 00:00
261.1095 +  year   = year,     -- year
261.1096 +  month  = month,    -- month from 1 to 12
261.1097 +  day    = day,      -- day from 1 to 31
261.1098 +  hour   = hour,     -- hour from 0 to 23
261.1099 +  minute = minute,   -- minute from 0 to 59
261.1100 +  second = second    -- second from 0 to 59
261.1101 +}
261.1102 +
261.1103 +This method returns a new timestamp value, based on given data.
261.1104 +
261.1105 +--]]--
261.1106 +function timestamp:new(args)
261.1107 +  local args = args
261.1108 +  if type(args) == "number" then args = { tsec = args } end
261.1109 +  if type(args) == "table" then
261.1110 +    if not args.second then
261.1111 +      args.second = 0
261.1112 +      if not args.minute then
261.1113 +        args.minute = 0
261.1114 +        if not args.hour then
261.1115 +          args.hour = 0
261.1116 +        end
261.1117 +      end
261.1118 +    end
261.1119 +    if
261.1120 +      type(args.year)   == "number" and
261.1121 +      type(args.month)  == "number" and
261.1122 +      type(args.day)    == "number" and
261.1123 +      type(args.hour)   == "number" and
261.1124 +      type(args.minute) == "number" and
261.1125 +      type(args.second) == "number"
261.1126 +    then
261.1127 +      if
261.1128 +        is_integer(args.year) and
261.1129 +        args.year >= 1 and args.year <= 9999 and
261.1130 +        is_integer(args.month) and
261.1131 +        args.month >= 1 and args.month <= 12 and
261.1132 +        is_integer(args.day) and
261.1133 +        args.day >= 1 and args.day <= 31 and
261.1134 +        is_integer(args.hour) and
261.1135 +        args.hour >= 0 and args.hour <= 23 and
261.1136 +        is_integer(args.minute) and
261.1137 +        args.minute >= 0 and args.minute <= 59 and
261.1138 +        is_integer(args.second) and
261.1139 +        args.second >= 0 and args.second <= 59
261.1140 +      then
261.1141 +        return timestamp:_create{
261.1142 +          tsec = timestamp.ymdhms_to_tsec(
261.1143 +            args.year, args.month, args.day,
261.1144 +            args.hour, args.minute, args.second
261.1145 +          ),
261.1146 +          year   = args.year,
261.1147 +          month  = args.month,
261.1148 +          day    = args.day,
261.1149 +          hour   = args.hour,
261.1150 +          minute = args.minute,
261.1151 +          second = args.second
261.1152 +        }
261.1153 +      else
261.1154 +        return timestamp.invalid
261.1155 +      end
261.1156 +    elseif type(args.tsec) == "number" then
261.1157 +      if
261.1158 +        is_integer(args.tsec) and
261.1159 +        args.tsec >= -62135596800 and args.tsec <= 253402300799
261.1160 +      then
261.1161 +        local year, month, day, hour, minute, second =
261.1162 +          timestamp.tsec_to_ymdhms(args.tsec)
261.1163 +        return timestamp:_create{
261.1164 +          tsec = args.tsec,
261.1165 +          year = year, month = month, day = day,
261.1166 +          hour = hour, minute = minute, second = second
261.1167 +        }
261.1168 +      else
261.1169 +        return timestamp.invalid
261.1170 +      end
261.1171 +    end
261.1172 +  end
261.1173 +  error("Invalid arguments passed to timestamp constructor.")
261.1174 +end
261.1175 +--//--
261.1176 +
261.1177 +--[[--
261.1178 +ts =                          -- current date/time as timestamp
261.1179 +atom.timestamp:get_current()
261.1180 +
261.1181 +This function returns the current date and time as timestamp.
261.1182 +
261.1183 +--]]--
261.1184 +function timestamp:get_current()
261.1185 +  local now = os.date("*t")
261.1186 +  return timestamp{
261.1187 +    year = now.year, month = now.month, day = now.day,
261.1188 +    hour = now.hour, minute = now.min, second = now.sec
261.1189 +  }
261.1190 +end
261.1191 +--//--
261.1192 +
261.1193 +--[[--
261.1194 +ts =             -- timestamp represented by the string
261.1195 +atom.timestamp:load(
261.1196 +  string         -- string representing a timestamp
261.1197 +)
261.1198 +
261.1199 +This method returns a timestamp represented by the given string.
261.1200 +
261.1201 +--]]--
261.1202 +function timestamp:load(str)
261.1203 +  if str == "" then
261.1204 +    return nil
261.1205 +  else
261.1206 +    local year, month, day, hour, minute, second = string.match(
261.1207 +      str,
261.1208 +      "^([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$"
261.1209 +    )
261.1210 +    if year then
261.1211 +      return timestamp{
261.1212 +        year   = tonumber(year),
261.1213 +        month  = tonumber(month),
261.1214 +        day    = tonumber(day),
261.1215 +        hour   = tonumber(hour),
261.1216 +        minute = tonumber(minute),
261.1217 +        second = tonumber(second)
261.1218 +      }
261.1219 +    else
261.1220 +      return timestamp.invalid
261.1221 +    end
261.1222 +  end
261.1223 +end
261.1224 +
261.1225 +function timestamp:__tostring()
261.1226 +  if self.invalid then
261.1227 +    return "invalid_timestamp"
261.1228 +  else
261.1229 +    return string.format(
261.1230 +      "%04i-%02i-%02i %02i:%02i:%02i",
261.1231 +      self.year, self.month, self.day, self.hour, self.minute, self.second
261.1232 +    )
261.1233 +  end
261.1234 +end
261.1235 +
261.1236 +function timestamp.getters:date()
261.1237 +  return date{ year = self.year, month = self.month, day = self.day }
261.1238 +end
261.1239 +
261.1240 +function timestamp.getters:time()
261.1241 +  return time{
261.1242 +    hour = self.hour,
261.1243 +    minute = self.minute,
261.1244 +    second = self.second
261.1245 +  }
261.1246 +end
261.1247 +
261.1248 +function timestamp.__eq(value1, value2)
261.1249 +  if value1.invalid or value2.invalid then
261.1250 +    return false
261.1251 +  else
261.1252 +    return value1.tsec == value2.tsec
261.1253 +  end
261.1254 +end
261.1255 +
261.1256 +function timestamp.__lt(value1, value2)
261.1257 +  if value1.invalid or value2.invalid then
261.1258 +    return false
261.1259 +  else
261.1260 +    return value1.tsec < value2.tsec
261.1261 +  end
261.1262 +end
261.1263 +
261.1264 +function timestamp.__le(value1, value2)
261.1265 +  if value1.invalid or value2.invalid then
261.1266 +    return false
261.1267 +  else
261.1268 +    return value1.tsec <= value2.tsec
261.1269 +  end
261.1270 +end
261.1271 +
261.1272 +function timestamp.__add(value1, value2)
261.1273 +  if getmetatable(value1) == timestamp then
261.1274 +    if getmetatable(value2) == timestamp then
261.1275 +      error("Can not add two timestamps.")
261.1276 +    elseif type(value2) == "number" then
261.1277 +      return timestamp(value1.tsec + value2)
261.1278 +    else
261.1279 +      error("Right operand of '+' operator has wrong type.")
261.1280 +    end
261.1281 +  elseif type(value1) == "number" then
261.1282 +    if getmetatable(value2) == timestamp then
261.1283 +      return timestamp(value1 + value2.tsec)
261.1284 +    else
261.1285 +      error("Assertion failed")
261.1286 +    end
261.1287 +  else
261.1288 +    error("Left operand of '+' operator has wrong type.")
261.1289 +  end
261.1290 +end
261.1291 +
261.1292 +function timestamp.__sub(value1, value2)
261.1293 +  if not getmetatable(value1) == timestamp then
261.1294 +    error("Left operand of '-' operator has wrong type.")
261.1295 +  end
261.1296 +  if getmetatable(value2) == timestamp then
261.1297 +    return value1.tsec - value2.tsec  -- TODO: transform to interval
261.1298 +  elseif type(value2) == "number" then
261.1299 +    return timestamp(value1.tsec - value2)
261.1300 +  else
261.1301 +    error("Right operand of '-' operator has wrong type.")
261.1302 +  end
261.1303 +end
261.1304 +
261.1305 +
261.1306 +
261.1307 +----------
261.1308 +-- time --
261.1309 +----------
261.1310 +
261.1311 +time = create_new_type("time")
261.1312 +
261.1313 +function time.hms_to_dsec(hour, minute, second)
261.1314 +  return 3600 * hour + 60 * minute + second
261.1315 +end
261.1316 +
261.1317 +function time.dsec_to_hms(dsec)
261.1318 +  local hour   = math.floor(dsec / 3600)
261.1319 +  local minute = math.floor((dsec % 3600) / 60)
261.1320 +  local second = dsec % 60
261.1321 +  return hour, minute, second
261.1322 +end
261.1323 +
261.1324 +--[[--
261.1325 +atom.time.invalid
261.1326 +
261.1327 +Value representing an invalid time of day.
261.1328 +
261.1329 +--]]--
261.1330 +time.invalid = time:_create{
261.1331 +  dsec = not_a_number,
261.1332 +  hour = not_a_number, minute = not_a_number, second = not_a_number,
261.1333 +  invalid = true
261.1334 +}
261.1335 +--//--
261.1336 +
261.1337 +--[[--
261.1338 +t =                 -- time based on given data
261.1339 +atom.time:new{
261.1340 +  dsec = dsec,      -- seconds since 00:00:00
261.1341 +  hour = hour,      -- hour from 0 to 23
261.1342 +  minute = minute,  -- minute from 0 to 59
261.1343 +  second = second   -- second from 0 to 59
261.1344 +}
261.1345 +
261.1346 +This method returns a new time value, based on given data.
261.1347 +
261.1348 +--]]--
261.1349 +function time:new(args)
261.1350 +  local args = args
261.1351 +  if type(args) == "number" then args = { dsec = args } end
261.1352 +  if type(args) == "table" then
261.1353 +    if not args.second then
261.1354 +      args.second = 0
261.1355 +      if not args.minute then
261.1356 +        args.minute = 0
261.1357 +      end
261.1358 +    end
261.1359 +    if
261.1360 +      type(args.hour)   == "number" and
261.1361 +      type(args.minute) == "number" and
261.1362 +      type(args.second) == "number"
261.1363 +    then
261.1364 +      if
261.1365 +        is_integer(args.hour) and
261.1366 +        args.hour >= 0 and args.hour <= 23 and
261.1367 +        is_integer(args.minute) and
261.1368 +        args.minute >= 0 and args.minute <= 59 and
261.1369 +        is_integer(args.second) and
261.1370 +        args.second >= 0 and args.second <= 59
261.1371 +      then
261.1372 +        return time:_create{
261.1373 +          dsec = time.hms_to_dsec(args.hour, args.minute, args.second),
261.1374 +          hour   = args.hour,
261.1375 +          minute = args.minute,
261.1376 +          second = args.second
261.1377 +        }
261.1378 +      else
261.1379 +        return time.invalid
261.1380 +      end
261.1381 +    elseif type(args.dsec) == "number" then
261.1382 +      if
261.1383 +        is_integer(args.dsec) and
261.1384 +        args.dsec >= 0 and args.dsec <= 86399
261.1385 +      then
261.1386 +        local hour, minute, second =
261.1387 +          time.dsec_to_hms(args.dsec)
261.1388 +        return time:_create{
261.1389 +          dsec = args.dsec,
261.1390 +          hour = hour, minute = minute, second = second
261.1391 +        }
261.1392 +      else
261.1393 +        return time.invalid
261.1394 +      end
261.1395 +    end
261.1396 +  end
261.1397 +  error("Invalid arguments passed to time constructor.")
261.1398 +end
261.1399 +--//--
261.1400 +
261.1401 +--[[--
261.1402 +t =                      -- current time of day
261.1403 +atom.time:get_current()
261.1404 +
261.1405 +This method returns the current time of the day.
261.1406 +
261.1407 +--]]--
261.1408 +function time:get_current()
261.1409 +  local now = os.date("*t")
261.1410 +  return time{ hour = now.hour, minute = now.min, second = now.sec }
261.1411 +end
261.1412 +--//--
261.1413 +
261.1414 +--[[--
261.1415 +t =              -- time represented by the string
261.1416 +atom.time:load(
261.1417 +  string         -- string representing a time of day
261.1418 +)
261.1419 +
261.1420 +This method returns a time represented by the given string.
261.1421 +
261.1422 +--]]--
261.1423 +function time:load(str)
261.1424 +  if str == "" then
261.1425 +    return nil
261.1426 +  else
261.1427 +    local hour, minute, second = string.match(
261.1428 +      str,
261.1429 +      "^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$"
261.1430 +    )
261.1431 +    if hour then
261.1432 +      return time{
261.1433 +        hour   = tonumber(hour),
261.1434 +        minute = tonumber(minute),
261.1435 +        second = tonumber(second)
261.1436 +      }
261.1437 +    else
261.1438 +      return time.invalid
261.1439 +    end
261.1440 +  end
261.1441 +end
261.1442 +--//--
261.1443 +
261.1444 +function time:__tostring()
261.1445 +  if self.invalid then
261.1446 +    return "invalid_time"
261.1447 +  else
261.1448 +    return string.format(
261.1449 +      "%02i:%02i:%02i",
261.1450 +      self.hour, self.minute, self.second
261.1451 +    )
261.1452 +  end
261.1453 +end
261.1454 +
261.1455 +function time.__eq(value1, value2)
261.1456 +  if value1.invalid or value2.invalid then
261.1457 +    return false
261.1458 +  else
261.1459 +    return value1.dsec == value2.dsec
261.1460 +  end
261.1461 +end
261.1462 +
261.1463 +function time.__lt(value1, value2)
261.1464 +  if value1.invalid or value2.invalid then
261.1465 +    return false
261.1466 +  else
261.1467 +    return value1.dsec < value2.dsec
261.1468 +  end
261.1469 +end
261.1470 +
261.1471 +function time.__le(value1, value2)
261.1472 +  if value1.invalid or value2.invalid then
261.1473 +    return false
261.1474 +  else
261.1475 +    return value1.dsec <= value2.dsec
261.1476 +  end
261.1477 +end
261.1478 +
261.1479 +function time.__add(value1, value2)
261.1480 +  if getmetatable(value1) == time then
261.1481 +    if getmetatable(value2) == time then
261.1482 +      error("Can not add two times.")
261.1483 +    elseif type(value2) == "number" then
261.1484 +      return time((value1.dsec + value2) % 86400)
261.1485 +    else
261.1486 +      error("Right operand of '+' operator has wrong type.")
261.1487 +    end
261.1488 +  elseif type(value1) == "number" then
261.1489 +    if getmetatable(value2) == time then
261.1490 +      return time((value1 + value2.dsec) % 86400)
261.1491 +    else
261.1492 +      error("Assertion failed")
261.1493 +    end
261.1494 +  else
261.1495 +    error("Left operand of '+' operator has wrong type.")
261.1496 +  end
261.1497 +end
261.1498 +
261.1499 +function time.__sub(value1, value2)
261.1500 +  if not getmetatable(value1) == time then
261.1501 +    error("Left operand of '-' operator has wrong type.")
261.1502 +  end
261.1503 +  if getmetatable(value2) == time then
261.1504 +    return value1.dsec - value2.dsec  -- TODO: transform to interval
261.1505 +  elseif type(value2) == "number" then
261.1506 +    return time((value1.dsec - value2) % 86400)
261.1507 +  else
261.1508 +    error("Right operand of '-' operator has wrong type.")
261.1509 +  end
261.1510 +end
261.1511 +
261.1512 +function time.__concat(value1, value2)
261.1513 +  local mt1, mt2 = getmetatable(value1), getmetatable(value2)
261.1514 +  if mt1 == date and mt2 == time then
261.1515 +    return timestamp{
261.1516 +      year = value1.year, month = value1.month, day = value1.day,
261.1517 +      hour = value2.hour, minute = value2.minute, second = value2.second
261.1518 +    }
261.1519 +  elseif mt1 == time and mt2 == date then
261.1520 +    return timestamp{
261.1521 +      year = value2.year, month = value2.month, day = value2.day,
261.1522 +      hour = value1.hour, minute = value1.minute, second = value1.second
261.1523 +    }
261.1524 +  elseif mt1 == time then
261.1525 +    error("Right operand of '..' operator has wrong type.")
261.1526 +  elseif mt2 == time then
261.1527 +    error("Left operand of '..' operator has wrong type.")
261.1528 +  else
261.1529 +    error("Assertion failed")
261.1530 +  end
261.1531 +end
261.1532 +
   262.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   262.2 +++ b/libraries/atom/benchmark.sh	Sun Oct 25 12:00:00 2009 +0100
   262.3 @@ -0,0 +1,12 @@
   262.4 +#!/bin/ksh
   262.5 +luac -o atom-compiled.lua atom.lua
   262.6 +luac -s -o atom-stripped.lua atom.lua
   262.7 +echo "100x loading source:"
   262.8 +time for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100; do lua -l atom -e ''; done
   262.9 +echo "100x loading precompiled bytecode:"
  262.10 +time for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100; do lua -l atom-compiled -e ''; done
  262.11 +echo "100x loading stripped bytecode:"
  262.12 +time for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100; do lua -l atom-stripped -e ''; done
  262.13 +echo "100x loading nothing:"
  262.14 +time for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100; do lua -e ''; done
  262.15 +rm atom-compiled.lua atom-stripped.lua
   263.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   263.2 +++ b/libraries/extos/Makefile	Sun Oct 25 12:00:00 2009 +0100
   263.3 @@ -0,0 +1,10 @@
   263.4 +include ../../Makefile.options
   263.5 +
   263.6 +extos.so: extos.o
   263.7 +	$(LD) $(LDFLAGS) -lrt -lcrypt -o extos.$(SLIB_EXT) extos.o 
   263.8 +
   263.9 +extos.o: extos.c
  263.10 +	$(CC) -c $(CFLAGS) -o extos.o extos.c
  263.11 +
  263.12 +clean::
  263.13 +	rm -f extos.so extos.o
   264.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   264.2 +++ b/libraries/extos/extos.c	Sun Oct 25 12:00:00 2009 +0100
   264.3 @@ -0,0 +1,351 @@
   264.4 +#include <lua.h>
   264.5 +#include <lauxlib.h>
   264.6 +#include <dirent.h>
   264.7 +#include <time.h>
   264.8 +#include <unistd.h>
   264.9 +#include <sys/types.h>
  264.10 +#include <sys/wait.h>
  264.11 +#include <signal.h>
  264.12 +#include <errno.h>
  264.13 +#include <stdio.h>
  264.14 +#include <string.h>
  264.15 +#include <fcntl.h>
  264.16 +#include <poll.h>
  264.17 +#include <stdlib.h>
  264.18 +
  264.19 +#define EXTOS_MAX_ERRLEN 80
  264.20 +#define EXTOS_EXEC_MAX_ARGS 64
  264.21 +
  264.22 +static lua_Number extos_monotonic_start_time;
  264.23 +
  264.24 +static int extos_pfilter(lua_State *L) {
  264.25 +  int i, result, exit_status, status_pipe_len;
  264.26 +  const char *in_buf;
  264.27 +  size_t in_len;
  264.28 +  size_t in_pos = 0;
  264.29 +  const char *filename;
  264.30 +  const char *args[EXTOS_EXEC_MAX_ARGS+2];
  264.31 +  int pipe_status[2];
  264.32 +  int pipe_in[2];
  264.33 +  int pipe_out[2];
  264.34 +  int pipe_err[2];
  264.35 +  pid_t child;
  264.36 +  char status_buf[1];
  264.37 +  char *out_buf = NULL;
  264.38 +  size_t out_len = 1024;
  264.39 +  size_t out_pos = 0;
  264.40 +  char *err_buf = NULL;
  264.41 +  size_t err_len = 1024;
  264.42 +  size_t err_pos = 0;
  264.43 +  void *old_sigpipe_action;
  264.44 +  struct pollfd fds[3];
  264.45 +  int in_closed = 0;
  264.46 +  int out_closed = 0;
  264.47 +  int err_closed = 0;
  264.48 +  void *newptr;
  264.49 +  char errmsg[EXTOS_MAX_ERRLEN+1];
  264.50 +  in_buf = luaL_optlstring(L, 1, "", &in_len);
  264.51 +  filename = luaL_checkstring(L, 2);
  264.52 +  args[0] = filename;
  264.53 +  for (i = 0; i < EXTOS_EXEC_MAX_ARGS; i++) {
  264.54 +    if (lua_isnoneornil(L, 3+i)) break;
  264.55 +    else args[i+1] = luaL_checkstring(L, 3+i);
  264.56 +  }
  264.57 +  if (!lua_isnoneornil(L, 3+i)) {
  264.58 +    return luaL_error(L, "Too many arguments for pfilter call.");
  264.59 +  }
  264.60 +  args[i+1] = 0;
  264.61 +  // status pipe for internal communication
  264.62 +  if (pipe(pipe_status) < 0) {
  264.63 +    strerror_r(errno, errmsg, EXTOS_MAX_ERRLEN+1);
  264.64 +    goto extos_pfilter_error_A0;
  264.65 +  }
  264.66 +  // stdin
  264.67 +  if (pipe(pipe_in) < 0) {
  264.68 +    strerror_r(errno, errmsg, EXTOS_MAX_ERRLEN+1);
  264.69 +    goto extos_pfilter_error_A1;
  264.70 +  }
  264.71 +  if (in_len) {
  264.72 +    do result = fcntl(pipe_in[1], F_SETFL, O_NONBLOCK); while (result < 0 && errno == EINTR);
  264.73 +  } else {
  264.74 +    do result = close(pipe_in[1]); while (result < 0 && errno == EINTR);
  264.75 +    in_closed = 1;
  264.76 +  }
  264.77 +  if (result < 0) {
  264.78 +    strerror_r(errno, errmsg, EXTOS_MAX_ERRLEN+1);
  264.79 +    goto extos_pfilter_error_A2;
  264.80 +  }
  264.81 +  // stdout
  264.82 +  if (pipe(pipe_out) < 0) {
  264.83 +    strerror_r(errno, errmsg, EXTOS_MAX_ERRLEN+1);
  264.84 +    goto extos_pfilter_error_A2;
  264.85 +  }
  264.86 +  do result = fcntl(pipe_out[0], F_SETFL, O_NONBLOCK); while (result < 0 && errno == EINTR);
  264.87 +  if (result < 0) {
  264.88 +    strerror_r(errno, errmsg, EXTOS_MAX_ERRLEN+1);
  264.89 +    goto extos_pfilter_error_A3;
  264.90 +  }
  264.91 +  // stderr
  264.92 +  if (pipe(pipe_err) < 0) {
  264.93 +    strerror_r(errno, errmsg, EXTOS_MAX_ERRLEN+1);
  264.94 +    goto extos_pfilter_error_A3;
  264.95 +  }
  264.96 +  do result = fcntl(pipe_err[0], F_SETFL, O_NONBLOCK); while (result < 0 && errno == EINTR);
  264.97 +  if (result < 0) {
  264.98 +    strerror_r(errno, errmsg, EXTOS_MAX_ERRLEN+1);
  264.99 +    goto extos_pfilter_error_A4;
 264.100 +  }
 264.101 +  // fork
 264.102 +  child = fork();
 264.103 +  if (child < 0) {
 264.104 +    strerror_r(errno, errmsg, EXTOS_MAX_ERRLEN+1);
 264.105 +    goto extos_pfilter_error_A4;
 264.106 +  }
 264.107 +  // skip error handling
 264.108 +  goto extos_pfilter_success_A;
 264.109 +  // error handling
 264.110 +  extos_pfilter_error_A4:
 264.111 +  do result = close(pipe_err[0]); while (result < 0 && errno == EINTR);
 264.112 +  do result = close(pipe_err[1]); while (result < 0 && errno == EINTR);  
 264.113 +  extos_pfilter_error_A3:
 264.114 +  do result = close(pipe_out[0]); while (result < 0 && errno == EINTR);
 264.115 +  do result = close(pipe_out[1]); while (result < 0 && errno == EINTR);  
 264.116 +  extos_pfilter_error_A2:
 264.117 +  do result = close(pipe_in[0]); while (result < 0 && errno == EINTR);
 264.118 +  do result = close(pipe_in[1]); while (result < 0 && errno == EINTR);
 264.119 +  extos_pfilter_error_A1:
 264.120 +  do result = close(pipe_status[0]); while (result < 0 && errno == EINTR);
 264.121 +  do result = close(pipe_status[1]); while (result < 0 && errno == EINTR);  
 264.122 +  extos_pfilter_error_A0:
 264.123 +  return luaL_error(L, "Unexpected error in pfilter: %s", errmsg);
 264.124 +  // end of error handling
 264.125 +  extos_pfilter_success_A:
 264.126 +  if (child) {  // parent
 264.127 +    old_sigpipe_action = signal(SIGPIPE, SIG_IGN);
 264.128 +    do result = close(pipe_status[1]); while (result < 0 && errno == EINTR);
 264.129 +    if (result < 0) goto extos_pfilter_error_B;
 264.130 +    do result = close(pipe_in[0]); while (result < 0 && errno == EINTR);
 264.131 +    if (result < 0) goto extos_pfilter_error_B;
 264.132 +    do result = close(pipe_out[1]); while (result < 0 && errno == EINTR);
 264.133 +    if (result < 0) goto extos_pfilter_error_B;
 264.134 +    do result = close(pipe_err[1]); while (result < 0 && errno == EINTR);
 264.135 +    if (result < 0) goto extos_pfilter_error_B;
 264.136 +    out_buf = malloc(out_len * sizeof(char));
 264.137 +    if (!out_buf) goto extos_pfilter_error_B;
 264.138 +    err_buf = malloc(err_len * sizeof(char));
 264.139 +    if (!err_buf) goto extos_pfilter_error_B;
 264.140 +    while (!in_closed || !out_closed || !err_closed) {
 264.141 +      i = 0;
 264.142 +      if (!in_closed) {
 264.143 +        fds[i].fd = pipe_in[1];
 264.144 +        fds[i].events = POLLOUT;
 264.145 +        i++;
 264.146 +      }
 264.147 +      if (!out_closed) {
 264.148 +        fds[i].fd = pipe_out[0];
 264.149 +        fds[i].events = POLLIN;
 264.150 +        i++;
 264.151 +      }
 264.152 +      if (!err_closed) {
 264.153 +        fds[i].fd = pipe_err[0];
 264.154 +        fds[i].events = POLLIN;
 264.155 +        i++;
 264.156 +      }
 264.157 +      do result = poll(fds, i, -1); while (result < 0 && errno == EINTR);
 264.158 +      if (result < 0) goto extos_pfilter_error_B;
 264.159 +      if (!in_closed) {
 264.160 +        do result = write(pipe_in[1], in_buf+in_pos, in_len-in_pos); while (result < 0 && errno == EINTR);
 264.161 +        if (result < 0) {
 264.162 +          if (errno == EPIPE) {
 264.163 +            do result = close(pipe_in[1]); while (result < 0 && errno == EINTR);
 264.164 +            in_closed = 1;
 264.165 +          } else if (errno != EAGAIN) {
 264.166 +            goto extos_pfilter_error_B;
 264.167 +          }
 264.168 +        } else {
 264.169 +          in_pos += result;
 264.170 +          if (in_pos == in_len) {
 264.171 +            do result = close(pipe_in[1]); while (result < 0 && errno == EINTR);
 264.172 +            in_closed = 1;
 264.173 +          }
 264.174 +        }
 264.175 +      }
 264.176 +      if (!out_closed) {
 264.177 +        do result = read(pipe_out[0], out_buf+out_pos, out_len-out_pos); while (result < 0 && errno == EINTR);
 264.178 +        if (result < 0) {
 264.179 +          if (errno != EAGAIN) goto extos_pfilter_error_B;
 264.180 +        } else if (result == 0) {
 264.181 +          do result = close(pipe_out[0]); while (result < 0 && errno == EINTR);
 264.182 +          out_closed = 1;
 264.183 +        } else {
 264.184 +          out_pos += result;
 264.185 +          if (out_pos == out_len) {
 264.186 +            out_len *= 2;
 264.187 +            newptr = realloc(out_buf, out_len * sizeof(char));
 264.188 +            if (!newptr) goto extos_pfilter_error_B;
 264.189 +            out_buf = newptr;
 264.190 +          }
 264.191 +        }
 264.192 +      }
 264.193 +      if (!err_closed) {
 264.194 +        do result = read(pipe_err[0], err_buf+err_pos, err_len-err_pos); while (result < 0 && errno == EINTR);
 264.195 +        if (result < 0) {
 264.196 +          if (errno != EAGAIN) goto extos_pfilter_error_B;
 264.197 +        } else if (result == 0) {
 264.198 +          do result = close(pipe_err[0]); while (result < 0 && errno == EINTR);
 264.199 +          err_closed = 1;
 264.200 +        } else {
 264.201 +          err_pos += result;
 264.202 +          if (err_pos == err_len) {
 264.203 +            err_len *= 2;
 264.204 +            newptr = realloc(err_buf, err_len * sizeof(char));
 264.205 +            if (!newptr) goto extos_pfilter_error_B;
 264.206 +            err_buf = newptr;
 264.207 +          }
 264.208 +        }
 264.209 +      }
 264.210 +    }
 264.211 +    lua_pushlstring(L, out_buf, out_pos);
 264.212 +    free(out_buf);
 264.213 +    out_buf = NULL;
 264.214 +    lua_pushlstring(L, err_buf, err_pos);
 264.215 +    free(err_buf);
 264.216 +    err_buf = NULL;
 264.217 +    do result = waitpid(child, &exit_status, 0); while (result < 0 && errno == EINTR);
 264.218 +    child = 0;
 264.219 +    if (result < 0) goto extos_pfilter_error_B;
 264.220 +    do status_pipe_len = read(pipe_status[0], status_buf, 1); while (status_pipe_len < 0 && errno == EINTR);
 264.221 +    if (status_pipe_len < 0) goto extos_pfilter_error_B;
 264.222 +    do result = close(pipe_status[0]); while (result < 0 && errno == EINTR);
 264.223 +    signal(SIGPIPE, old_sigpipe_action);
 264.224 +    if (status_pipe_len == 0) {
 264.225 +      if (WIFEXITED(exit_status)) lua_pushinteger(L, WEXITSTATUS(exit_status));
 264.226 +      else lua_pushinteger(L, -WTERMSIG(exit_status));
 264.227 +      return 3;
 264.228 +    } else if (status_buf[0] == 0) {
 264.229 +      return luaL_error(L, "Error in pfilter while reopening standard file descriptors in child process.");
 264.230 +    } else {
 264.231 +      strerror_r(status_buf[0], errmsg, EXTOS_MAX_ERRLEN+1);
 264.232 +      lua_pushnil(L);
 264.233 +      lua_pushfstring(L, "Could not execute \"%s\": %s", filename, errmsg);
 264.234 +      return 2;
 264.235 +    }
 264.236 +    extos_pfilter_error_B:
 264.237 +    signal(SIGPIPE, old_sigpipe_action);
 264.238 +    strerror_r(errno, errmsg, EXTOS_MAX_ERRLEN+1);
 264.239 +    if (out_buf) free(out_buf);
 264.240 +    if (err_buf) free(err_buf);
 264.241 +    if (!in_closed) {
 264.242 +      do result = close(pipe_in[1]); while (result < 0 && errno == EINTR);
 264.243 +    }
 264.244 +    if (!out_closed) {
 264.245 +      do result = close(pipe_out[0]); while (result < 0 && errno == EINTR);
 264.246 +    }
 264.247 +    if (!err_closed) {
 264.248 +      do result = close(pipe_err[0]); while (result < 0 && errno == EINTR);
 264.249 +    }
 264.250 +    if (child) do result = waitpid(child, &exit_status, 0); while (result < 0 && errno == EINTR);
 264.251 +    return luaL_error(L, "Unexpected error in pfilter: %s", errmsg);
 264.252 +  } else {  // child
 264.253 +    do result = close(pipe_status[0]); while (result < 0 && errno == EINTR);
 264.254 +    do result = close(pipe_in[1]); while (result < 0 && errno == EINTR);
 264.255 +    do result = close(pipe_out[0]); while (result < 0 && errno == EINTR);
 264.256 +    do result = close(0); while (result < 0 && errno == EINTR);
 264.257 +    do result = close(1); while (result < 0 && errno == EINTR);
 264.258 +    do result = close(2); while (result < 0 && errno == EINTR);
 264.259 +    do result = dup(pipe_in[0]); while (result < 0 && errno == EINTR);
 264.260 +    if (result != 0) goto extos_pfilter_error_fd_remapping;
 264.261 +    do result = dup(pipe_out[1]); while (result < 0 && errno == EINTR);
 264.262 +    if (result != 1) goto extos_pfilter_error_fd_remapping;
 264.263 +    do result = dup(pipe_err[1]); while (result < 0 && errno == EINTR);
 264.264 +    if (result != 2) goto extos_pfilter_error_fd_remapping;
 264.265 +    execvp(filename, args);
 264.266 +    status_buf[0] = errno;
 264.267 +    do result = write(pipe_status[1], status_buf, 1); while (result < 0 && errno == EINTR);
 264.268 +    _exit(0);
 264.269 +    extos_pfilter_error_fd_remapping:
 264.270 +    status_buf[0] = 0;
 264.271 +    do result = write(pipe_status[1], status_buf, 1); while (result < 0 && errno == EINTR);
 264.272 +    _exit(0);
 264.273 +  }
 264.274 +}
 264.275 +
 264.276 +static int extos_listdir(lua_State *L) {
 264.277 +  DIR *dir;
 264.278 +  int i = 1;
 264.279 +  struct dirent entry_buffer;
 264.280 +  struct dirent *entry;
 264.281 +  dir = opendir(luaL_checkstring(L, 1));
 264.282 +  if (!dir) {
 264.283 +    lua_pushnil(L);
 264.284 +    lua_pushliteral(L, "Could not list directory.");
 264.285 +    return 2;
 264.286 +  }
 264.287 +  lua_settop(L, 0);
 264.288 +  lua_newtable(L);  // 1
 264.289 +  while (1) {
 264.290 +    readdir_r(dir, &entry_buffer, &entry);
 264.291 +    if (!entry) break;
 264.292 +    // Linux doesn't have d_namlen
 264.293 +    //lua_pushlstring(L, entry->d_name, entry->d_namlen);
 264.294 +    lua_pushstring(L, entry->d_name);
 264.295 +    lua_rawseti(L, 1, i++);
 264.296 +  }
 264.297 +  closedir(dir);
 264.298 +  return 1;
 264.299 +}
 264.300 +
 264.301 +static int extos_crypt(lua_State *L) {
 264.302 +  char *key;
 264.303 +  char *salt;
 264.304 +  char *result;
 264.305 +  key = luaL_checkstring(L, 1);
 264.306 +  salt = luaL_checkstring(L, 2);
 264.307 +  result = crypt(key, salt);  // TODO: Call not thread safe
 264.308 +  if (result) lua_pushstring(L, result);
 264.309 +  else lua_pushnil(L);
 264.310 +  return 1;
 264.311 +}
 264.312 +
 264.313 +static int extos_hires_time(lua_State *L) {
 264.314 +  struct timespec tp;
 264.315 +  if (clock_gettime(CLOCK_REALTIME, &tp)) {
 264.316 +    return luaL_error(L, "Could not access CLOCK_REALTIME.");
 264.317 +  }
 264.318 +  lua_pushnumber(L, tp.tv_sec + 0.000000001 * tp.tv_nsec);
 264.319 +  return 1;
 264.320 +}
 264.321 +
 264.322 +// returns time in seconds since loading the library
 264.323 +static int extos_monotonic_hires_time(lua_State *L) {
 264.324 +  struct timespec tp;
 264.325 +  if (clock_gettime(CLOCK_MONOTONIC, &tp)) {
 264.326 +    return luaL_error(L, "Could not access CLOCK_MONOTONIC.");
 264.327 +  }
 264.328 +  lua_pushnumber(L,
 264.329 +    tp.tv_sec + 0.000000001 * tp.tv_nsec - extos_monotonic_start_time
 264.330 +  );
 264.331 +  return 1;
 264.332 +}
 264.333 +
 264.334 +int luaopen_extos(lua_State *L) {
 264.335 +  {
 264.336 +    struct timespec tp;
 264.337 +    if (clock_gettime(CLOCK_MONOTONIC, &tp)) {
 264.338 +      return luaL_error(L, "Could not access monotonic hires time.");
 264.339 +    }
 264.340 +    extos_monotonic_start_time = tp.tv_sec + 0.000000001 * tp.tv_nsec;
 264.341 +  }
 264.342 +  lua_getglobal(L, "os");
 264.343 +  lua_pushcfunction(L, extos_pfilter);
 264.344 +  lua_setfield(L, -2, "pfilter");
 264.345 +  lua_pushcfunction(L, extos_listdir);
 264.346 +  lua_setfield(L, -2, "listdir");
 264.347 +  lua_pushcfunction(L, extos_crypt);
 264.348 +  lua_setfield(L, -2, "crypt");
 264.349 +  lua_pushcfunction(L, extos_hires_time);
 264.350 +  lua_setfield(L, -2, "hires_time");
 264.351 +  lua_pushcfunction(L, extos_monotonic_hires_time);
 264.352 +  lua_setfield(L, -2, "monotonic_hires_time");
 264.353 +  return 0;
 264.354 +}
   265.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   265.2 +++ b/libraries/luatex/luatex.lua	Sun Oct 25 12:00:00 2009 +0100
   265.3 @@ -0,0 +1,148 @@
   265.4 +#!/usr/bin/env lua
   265.5 +
   265.6 +local _G             = _G
   265.7 +local _VERSION       = _VERSION
   265.8 +local assert         = assert
   265.9 +local collectgarbage = collectgarbage
  265.10 +local dofile         = dofile
  265.11 +local error          = error
  265.12 +local getfenv        = getfenv
  265.13 +local getmetatable   = getmetatable
  265.14 +local ipairs         = ipairs
  265.15 +local load           = load
  265.16 +local loadfile       = loadfile
  265.17 +local loadstring     = loadstring
  265.18 +local module         = module
  265.19 +local next           = next
  265.20 +local pairs          = pairs
  265.21 +local pcall          = pcall
  265.22 +local print          = print
  265.23 +local rawequal       = rawequal
  265.24 +local rawget         = rawget
  265.25 +local rawset         = rawset
  265.26 +local require        = require
  265.27 +local select         = select
  265.28 +local setfenv        = setfenv
  265.29 +local setmetatable   = setmetatable
  265.30 +local tonumber       = tonumber
  265.31 +local tostring       = tostring
  265.32 +local type           = type
  265.33 +local unpack         = unpack
  265.34 +local xpcall         = xpcall
  265.35 +
  265.36 +local coroutine = coroutine
  265.37 +local debug     = debug
  265.38 +local io        = io
  265.39 +local math      = math
  265.40 +local os        = os
  265.41 +local package   = package
  265.42 +local string    = string
  265.43 +local table     = table
  265.44 +
  265.45 +require("multirand")
  265.46 +local multirand = multirand
  265.47 +
  265.48 +module(...)
  265.49 +
  265.50 +temp_dir = false  -- has to be set to a private directory (/tmp can be unsafe)
  265.51 +
  265.52 +function escape(str)
  265.53 +  return (
  265.54 +    string.gsub(
  265.55 +      str,
  265.56 +      "[\001-\031\127\\#$&~_^%%{}]",
  265.57 +      function(char)
  265.58 +        local b = string.byte(char)
  265.59 +        if (b > 1 and b < 31) or b == 127 then
  265.60 +          return " "
  265.61 +        elseif
  265.62 +          char == "#" or char == "$" or char == "&" or char == "_" or
  265.63 +          char == "%" or char == "{" or char == "}"
  265.64 +        then
  265.65 +          return "\\" .. char
  265.66 +        else
  265.67 +          return "\\symbol{" .. b .. "}"
  265.68 +        end
  265.69 +      end
  265.70 +    )
  265.71 +  )
  265.72 +end
  265.73 +
  265.74 +document_methods = {}
  265.75 +
  265.76 +document_mt = {
  265.77 +  __index = document_methods,
  265.78 +  __call = function(...) return document_methods.write(...) end
  265.79 +}
  265.80 +
  265.81 +function new_document()
  265.82 +  return setmetatable({}, document_mt)
  265.83 +end
  265.84 +
  265.85 +function document_methods:write(...)
  265.86 +  local i = 1
  265.87 +  while true do
  265.88 +    local v = select(i, ...)
  265.89 +    if v == nil then
  265.90 +      break
  265.91 +    end
  265.92 +    self[#self+1] = v
  265.93 +    i = i + 1
  265.94 +  end
  265.95 +end
  265.96 +
  265.97 +function document_methods:get_latex()
  265.98 +  local str = table.concat(self)
  265.99 +  for i in ipairs(self) do
 265.100 +    self[i] = nil
 265.101 +  end
 265.102 +  self[1] = str
 265.103 +  return str
 265.104 +end
 265.105 +
 265.106 +function document_methods:get_pdf()
 265.107 +  -- TODO: proper escaping of shell commands (should not be a real risk)
 265.108 +  if not temp_dir then
 265.109 +    error("luatex.temp_dir not set")
 265.110 +  end
 265.111 +  local basename = temp_dir .. "/tmp.luatex_" .. multirand.string(16)
 265.112 +  local latex_file = assert(io.open(basename .. ".tex", "w"))
 265.113 +  latex_file:write(self:get_latex())
 265.114 +  latex_file:close()
 265.115 +  local result = os.execute(
 265.116 +    'latex -output-format=pdf "-output-directory=' .. temp_dir .. '" ' ..
 265.117 +    basename .. '< /dev/null > /dev/null 2> /dev/null'
 265.118 +  )
 265.119 +  if result ~= 0 then
 265.120 +    error('LaTeX failed, see "' .. basename .. '.log" for details.')
 265.121 +  end
 265.122 +  local pdf_file = assert(io.open(basename .. ".pdf", "r"))
 265.123 +  local pdf_data = pdf_file:read("*a")
 265.124 +  pdf_file:close()
 265.125 +  os.execute('rm -f "' .. basename .. '.*"')
 265.126 +  return pdf_data
 265.127 +end
 265.128 +
 265.129 +--[[
 265.130 +
 265.131 +require("luatex")
 265.132 +luatex.temp_dir = "."
 265.133 +
 265.134 +local tex = luatex.new_document()
 265.135 +
 265.136 +tex "\\documentclass[a4paper,12pt]{article}\n"
 265.137 +tex "\\usepackage{german}\n"
 265.138 +tex "\\usepackage{amsfonts}\n"
 265.139 +tex "\\usepackage{amssymb}\n"
 265.140 +tex "\\usepackage{ulem}\n"
 265.141 +tex "\\pagestyle{headings}\n"
 265.142 +tex "\\begin{document}\n"
 265.143 +tex "\\title{Demo}\n"
 265.144 +tex "\\author{John Doe}\n"
 265.145 +tex "\\date{\\small 25. August 2008}\n"
 265.146 +tex "\\maketitle\n"
 265.147 +tex "\\end{document}\n"
 265.148 +
 265.149 +local pdf = tex:get_pdf()
 265.150 +
 265.151 +--]]
   266.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   266.2 +++ b/libraries/mondelefant/Makefile	Sun Oct 25 12:00:00 2009 +0100
   266.3 @@ -0,0 +1,13 @@
   266.4 +include ../../Makefile.options
   266.5 +
   266.6 +mondelefant_native.so: mondelefant_native.o
   266.7 +	$(LD) $(LDFLAGS) $(LDFLAGS_PGSQL) -o mondelefant_native.$(SLIB_EXT) mondelefant_native.o -lpq
   266.8 +
   266.9 +mondelefant_native.o: mondelefant_native.c
  266.10 +	$(CC) -c $(CFLAGS) $(CFLAGS_PGSQL) -o mondelefant_native.o mondelefant_native.c
  266.11 +
  266.12 +test:: mondelefant_native.so mondelefant.lua
  266.13 +	lua -l mondelefant
  266.14 +
  266.15 +clean::
  266.16 +	rm -f mondelefant_native.so mondelefant_native.o
   267.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   267.2 +++ b/libraries/mondelefant/example.lua	Sun Oct 25 12:00:00 2009 +0100
   267.3 @@ -0,0 +1,86 @@
   267.4 +#!/usr/bin/env lua
   267.5 +require("mondelefant")
   267.6 +
   267.7 +-- Standarddatenbankverbindung ist globale Variable 'db'
   267.8 +function mondelefant.class_prototype:get_db_conn() return db end
   267.9 +
  267.10 +-- Verbindung aufbauen
  267.11 +db = assert(mondelefant.connect{engine='postgresql', dbname='test'})
  267.12 +
  267.13 +Product = mondelefant.new_class{ table = "product" }
  267.14 +ProductVariant = mondelefant.new_class{ table = "product_variant" }
  267.15 +
  267.16 +Product:add_reference{
  267.17 +  mode     = "1m",
  267.18 +  to       = ProductVariant,
  267.19 +  this_key = "number",
  267.20 +  that_key = "product_number",
  267.21 +  ref      = "product_variants",
  267.22 +  back_ref = "product",
  267.23 +  --default_order = '"number"'
  267.24 +}
  267.25 +
  267.26 +ProductVariant:add_reference{
  267.27 +  mode     = "m1",
  267.28 +  to       = Product,
  267.29 +  this_key = "product_number",
  267.30 +  that_key = "number",
  267.31 +  ref      = "product",
  267.32 +  back_ref = nil,
  267.33 +  --default_order = '"id"'
  267.34 +}
  267.35 +
  267.36 +p = Product:new_selector():single_object_mode():add_where{"name=?", "Noodles"}:exec()
  267.37 +
  267.38 +
  267.39 +--[[
  267.40 +-- Neue Datenbankklasse definieren
  267.41 +Product = mondelefant.new_class{ table = '"product"' }
  267.42 +
  267.43 +-- Methode der Klasse, um sofort eine alphabetische Liste aller Produkte
  267.44 +-- zu bekommen
  267.45 +function Product:get_all_ordered_by_name()
  267.46 +  local selector = self:new_selector()
  267.47 +  selector:add_order_by('"name"')
  267.48 +  selector:add_order_by('"id"')
  267.49 +  return selector:exec()
  267.50 +end
  267.51 +
  267.52 +function Product.object_get:name_length(key)
  267.53 +  local value = #self.name
  267.54 +  self._data.name_length = value
  267.55 +  return value
  267.56 +end
  267.57 +
  267.58 +function Product.object_set:quality(value)
  267.59 +  if value == "perfect" or value == "good" or value == "trash" then
  267.60 +    self._data.quality = value
  267.61 +  else
  267.62 +    self._data.quality = nil
  267.63 +  end
  267.64 +  self._dirty.quality = true
  267.65 +end
  267.66 +
  267.67 +-- Methode der Listen, um sie auszugeben
  267.68 +function Product.list:print()
  267.69 +  for i, product in ipairs(self) do
  267.70 +    print(product.id, product.name, product.name_length)
  267.71 +  end
  267.72 +end
  267.73 +
  267.74 +products = Product:get_all_ordered_by_name()
  267.75 +products:print()
  267.76 +products[1].quality = "perfect"
  267.77 +print(products[1].quality)
  267.78 +products[2].quality = "I don't know."
  267.79 +print(products[2].quality)
  267.80 +products[3].name = "Produkt Eins!"
  267.81 +products[3].quality = "perfect"
  267.82 +products[3]:save()
  267.83 +
  267.84 +sel = db:new_selector()
  267.85 +sel:from('"product_variant"')
  267.86 +sel:add_field("*")
  267.87 +sel:attach("m1", products, "product_id", "id", "product", "product_variants")
  267.88 +product_variants = sel:exec()
  267.89 +--]]
  267.90 \ No newline at end of file
   268.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   268.2 +++ b/libraries/mondelefant/mondelefant.lua	Sun Oct 25 12:00:00 2009 +0100
   268.3 @@ -0,0 +1,845 @@
   268.4 +#!/usr/bin/env lua
   268.5 +
   268.6 +
   268.7 +---------------------------
   268.8 +-- module initialization --
   268.9 +---------------------------
  268.10 +
  268.11 +local _G              = _G
  268.12 +local _VERSION        = _VERSION
  268.13 +local assert          = assert
  268.14 +local collectgarbage  = collectgarbage
  268.15 +local dofile          = dofile
  268.16 +local error           = error
  268.17 +local getfenv         = getfenv
  268.18 +local getmetatable    = getmetatable
  268.19 +local ipairs          = ipairs
  268.20 +local load            = load
  268.21 +local loadfile        = loadfile
  268.22 +local loadstring      = loadstring
  268.23 +local next            = next
  268.24 +local pairs           = pairs
  268.25 +local pcall           = pcall
  268.26 +local print           = print
  268.27 +local rawequal        = rawequal
  268.28 +local rawget          = rawget
  268.29 +local rawset          = rawset
  268.30 +local select          = select
  268.31 +local setfenv         = setfenv
  268.32 +local setmetatable    = setmetatable
  268.33 +local tonumber        = tonumber
  268.34 +local tostring        = tostring
  268.35 +local type            = type
  268.36 +local unpack          = unpack
  268.37 +local xpcall          = xpcall
  268.38 +
  268.39 +local coroutine       = coroutine
  268.40 +local io              = io
  268.41 +local math            = math
  268.42 +local os              = os
  268.43 +local string          = string
  268.44 +local table           = table
  268.45 +
  268.46 +local add             = table.insert
  268.47 +
  268.48 +_G[...] = require("mondelefant_native")
  268.49 +module(...)
  268.50 +
  268.51 +
  268.52 +
  268.53 +---------------
  268.54 +-- selectors --
  268.55 +---------------
  268.56 +
  268.57 +selector_metatable = {}
  268.58 +selector_prototype = {}
  268.59 +selector_metatable.__index = selector_prototype
  268.60 +
  268.61 +local function init_selector(self, db_conn)
  268.62 +  self._db_conn = db_conn
  268.63 +  self._mode = "list"
  268.64 +  self._fields = { sep = ", " }
  268.65 +  self._distinct = false
  268.66 +  self._distinct_on = {sep = ", ", expression}
  268.67 +  self._from = { sep = " " }
  268.68 +  self._where = { sep = " AND " }
  268.69 +  self._group_by = { sep = ", " }
  268.70 +  self._having = { sep = " AND " }
  268.71 +  self._combine = { sep = " " }
  268.72 +  self._order_by = { sep = ", " }
  268.73 +  self._limit = nil
  268.74 +  self._offset = nil
  268.75 +  --[[
  268.76 +  self._lock = nil
  268.77 +  self._lock_tables = { sep = ", " }
  268.78 +  --]]
  268.79 +  self._class = nil
  268.80 +  self._attach = nil
  268.81 +  return self
  268.82 +end
  268.83 +
  268.84 +function connection_prototype:new_selector()
  268.85 +  return init_selector(setmetatable({}, selector_metatable), self)
  268.86 +end
  268.87 +
  268.88 +function selector_prototype:get_db_conn()
  268.89 +  return self._db_conn
  268.90 +end
  268.91 +
  268.92 +-- TODO: selector clone?
  268.93 +
  268.94 +function selector_prototype:single_object_mode()
  268.95 +  self._mode = "object"
  268.96 +  return self
  268.97 +end
  268.98 +
  268.99 +function selector_prototype:optional_object_mode()
 268.100 +  self._mode = "opt_object"
 268.101 +  return self
 268.102 +end
 268.103 +
 268.104 +function selector_prototype:empty_list_mode()
 268.105 +  self._mode = "empty_list"
 268.106 +  return self
 268.107 +end
 268.108 +
 268.109 +function selector_prototype:add_distinct_on(expression)
 268.110 +  if self._distinct then
 268.111 +    error("Can not combine DISTINCT with DISTINCT ON.")
 268.112 +  end
 268.113 +  add(self._distinct_on, expression)
 268.114 +  return self
 268.115 +end
 268.116 +
 268.117 +function selector_prototype:set_distinct()
 268.118 +  if #self._distinct_on > 0 then
 268.119 +    error("Can not combine DISTINCT with DISTINCT ON.")
 268.120 +  end
 268.121 +  self._distinct = true
 268.122 +  return self
 268.123 +end
 268.124 +
 268.125 +function selector_prototype:add_from(expression, alias, condition)
 268.126 +  local first = (#self._from == 0)
 268.127 +  if not first then
 268.128 +    if condition then
 268.129 +      add(self._from, "INNER JOIN")
 268.130 +    else
 268.131 +      add(self._from, "CROSS JOIN")
 268.132 +    end
 268.133 +  end
 268.134 +  if getmetatable(expression) == selector_metatable then
 268.135 +    if alias then
 268.136 +      add(self._from, {'($) AS "$"', {expression}, {alias}})
 268.137 +    else
 268.138 +      add(self._from, {'($) AS "subquery"', {expression}})
 268.139 +    end
 268.140 +  else
 268.141 +    if alias then
 268.142 +      add(self._from, {'$ AS "$"', {expression}, {alias}})
 268.143 +    else
 268.144 +      add(self._from, expression)
 268.145 +    end
 268.146 +  end
 268.147 +  if condition then
 268.148 +    if first then
 268.149 +      self:condition(condition)
 268.150 +    else
 268.151 +      add(self._from, "ON")
 268.152 +      add(self._from, condition)
 268.153 +    end
 268.154 +  end
 268.155 +  return self
 268.156 +end
 268.157 +
 268.158 +function selector_prototype:add_where(expression)
 268.159 +  add(self._where, expression)
 268.160 +  return self
 268.161 +end
 268.162 +
 268.163 +function selector_prototype:add_group_by(expression)
 268.164 +  add(self._group_by, expression)
 268.165 +  return self
 268.166 +end
 268.167 +
 268.168 +function selector_prototype:add_having(expression)
 268.169 +  add(self._having, expression)
 268.170 +  return self
 268.171 +end
 268.172 +
 268.173 +function selector_prototype:add_combine(expression)
 268.174 +  add(self._combine, expression)
 268.175 +  return self
 268.176 +end
 268.177 +
 268.178 +function selector_prototype:add_order_by(expression)
 268.179 +  add(self._order_by, expression)
 268.180 +  return self
 268.181 +end
 268.182 +
 268.183 +function selector_prototype:limit(count)
 268.184 +  if type(count) ~= "number" or count % 1 ~= 0 then
 268.185 +    error("LIMIT must be an integer.")
 268.186 +  end
 268.187 +  self._limit = count
 268.188 +  return self
 268.189 +end
 268.190 +
 268.191 +function selector_prototype:offset(count)
 268.192 +  if type(count) ~= "number" or count % 1 ~= 0 then
 268.193 +    error("OFFSET must be an integer.")
 268.194 +  end
 268.195 +  self._offset = count
 268.196 +  return self
 268.197 +end
 268.198 +
 268.199 +function selector_prototype:reset_fields()
 268.200 +  for idx in ipairs(self._fields) do
 268.201 +    self._fields[idx] = nil
 268.202 +  end
 268.203 +  return self
 268.204 +end
 268.205 +
 268.206 +function selector_prototype:add_field(expression, alias, options)
 268.207 +  if alias then
 268.208 +    add(self._fields, {'$ AS "$"', {expression}, {alias}})
 268.209 +  else
 268.210 +    add(self._fields, expression)
 268.211 +  end
 268.212 +  if options then
 268.213 +    for i, option in ipairs(options) do
 268.214 +      if option == "distinct" then
 268.215 +        if alias then
 268.216 +          self:add_distinct_on('"' .. alias .. '"')
 268.217 +        else
 268.218 +          self:add_distinct_on(expression)
 268.219 +        end
 268.220 +      elseif option == "grouped" then
 268.221 +        if alias then
 268.222 +          self:add_group_by('"' .. alias .. '"')
 268.223 +        else
 268.224 +          self:add_group_by(expression)
 268.225 +        end
 268.226 +      else
 268.227 +        error("Unknown option '" .. option .. "' to add_field method.")
 268.228 +      end
 268.229 +    end
 268.230 +  end
 268.231 +  return self
 268.232 +end
 268.233 +
 268.234 +function selector_prototype:join(...)  -- NOTE: alias for add_from
 268.235 +  return self:add_from(...)
 268.236 +end
 268.237 +
 268.238 +function selector_prototype:from(expression, alias, condition)
 268.239 +  if #self._from > 0 then
 268.240 +    error("From-clause already existing (hint: try join).")
 268.241 +  end
 268.242 +  return self:join(expression, alias, condition)
 268.243 +end
 268.244 +
 268.245 +function selector_prototype:left_join(expression, alias, condition)
 268.246 +  local first = (#self._from == 0)
 268.247 +  if not first then
 268.248 +    add(self._from, "LEFT OUTER JOIN")
 268.249 +  end
 268.250 +  if alias then
 268.251 +    add(self._from, {'$ AS "$"', {expression}, {alias}})
 268.252 +  else
 268.253 +    add(self._from, expression)
 268.254 +  end
 268.255 +  if condition then
 268.256 +    if first then
 268.257 +      self:condition(condition)
 268.258 +    else
 268.259 +      add(self._from, "ON")
 268.260 +      add(self._from, condition)
 268.261 +    end
 268.262 +  end
 268.263 +  return self
 268.264 +end
 268.265 +
 268.266 +function selector_prototype:union(expression)
 268.267 +  self:add_combine{"UNION $", {expression}}
 268.268 +  return self
 268.269 +end
 268.270 +
 268.271 +function selector_prototype:union_all(expression)
 268.272 +  self:add_combine{"UNION ALL $", {expression}}
 268.273 +  return self
 268.274 +end
 268.275 +
 268.276 +function selector_prototype:intersect(expression)
 268.277 +  self:add_combine{"INTERSECT $", {expression}}
 268.278 +  return self
 268.279 +end
 268.280 +
 268.281 +function selector_prototype:intersect_all(expression)
 268.282 +  self:add_combine{"INTERSECT ALL $", {expression}}
 268.283 +  return self
 268.284 +end
 268.285 +
 268.286 +function selector_prototype:except(expression)
 268.287 +  self:add_combine{"EXCEPT $", {expression}}
 268.288 +  return self
 268.289 +end
 268.290 +
 268.291 +function selector_prototype:except_all(expression)
 268.292 +  self:add_combine{"EXCEPT ALL $", {expression}}
 268.293 +  return self
 268.294 +end
 268.295 +
 268.296 +function selector_prototype:set_class(class)
 268.297 +  self._class = class
 268.298 +  return self
 268.299 +end
 268.300 +
 268.301 +function selector_prototype:attach(mode, data2, field1, field2, ref1, ref2)
 268.302 +  self._attach = {
 268.303 +    mode = mode,
 268.304 +    data2 = data2,
 268.305 +    field1 = field1,
 268.306 +    field2 = field2,
 268.307 +    ref1 = ref1,
 268.308 +    ref2 = ref2
 268.309 +  }
 268.310 +  return self
 268.311 +end
 268.312 +
 268.313 +-- TODO: many-to-many relations
 268.314 +
 268.315 +function selector_metatable:__tostring()
 268.316 +  local parts = {sep = " "}
 268.317 +  add(parts, "SELECT")
 268.318 +  if self._distinct then
 268.319 +    add(parts, "DISTINCT")
 268.320 +  elseif #self._distinct_on > 0 then
 268.321 +    add(parts, {"DISTINCT ON ($)", self._distinct_on})
 268.322 +  end
 268.323 +  add(parts, {"$", self._fields})
 268.324 +  if #self._from > 0 then
 268.325 +    add(parts, {"FROM $", self._from})
 268.326 +  end
 268.327 +  if #self._mode == "empty_list" then
 268.328 +    add(parts, "WHERE FALSE")
 268.329 +  elseif #self._where > 0 then
 268.330 +    add(parts, {"WHERE $", self._where})
 268.331 +  end
 268.332 +  if #self._group_by > 0 then
 268.333 +    add(parts, {"GROUP BY $", self._group_by})
 268.334 +  end
 268.335 +  if #self._having > 0 then
 268.336 +    add(parts, {"HAVING $", self._having})
 268.337 +  end
 268.338 +  for i, v in ipairs(self._combine) do
 268.339 +    add(parts, v)
 268.340 +  end
 268.341 +  if #self._order_by > 0 then
 268.342 +    add(parts, {"ORDER BY $", self._order_by})
 268.343 +  end
 268.344 +  if self._mode == "empty_list" then
 268.345 +    add(parts, "LIMIT 0")
 268.346 +  elseif self._mode ~= "list" then
 268.347 +    add(parts, "LIMIT 1")
 268.348 +  elseif self._limit then
 268.349 +    add(parts, "LIMIT " .. self._limit)
 268.350 +  end
 268.351 +  if self._offset then
 268.352 +    add(parts, "OFFSET " .. self._offset)
 268.353 +  end
 268.354 +  return self._db_conn:assemble_command{"$", parts}
 268.355 +end
 268.356 +
 268.357 +function selector_prototype:try_exec()
 268.358 +  if self._mode == "empty_list" then
 268.359 +    if self._class then
 268.360 +      return nil, self._class:create_list()
 268.361 +    else
 268.362 +       return nil, self._db_conn:create_list()
 268.363 +    end
 268.364 +  end
 268.365 +  local db_error, db_result = self._db_conn:try_query(self, self._mode)
 268.366 +  if db_error then
 268.367 +    return db_error
 268.368 +  elseif db_result then
 268.369 +    if self._class then set_class(db_result, self._class) end
 268.370 +    if self._attach then
 268.371 +      attach(
 268.372 +        self._attach.mode,
 268.373 +        db_result,
 268.374 +        self._attach.data2,
 268.375 +        self._attach.field1,
 268.376 +        self._attach.field2,
 268.377 +        self._attach.ref1,
 268.378 +        self._attach.ref2
 268.379 +      )
 268.380 +    end
 268.381 +    return nil, db_result
 268.382 +  else
 268.383 +    return nil
 268.384 +  end
 268.385 +end
 268.386 +
 268.387 +function selector_prototype:exec()
 268.388 +  local db_error, result = self:try_exec()
 268.389 +  if db_error then
 268.390 +    db_error:escalate()
 268.391 +  else
 268.392 +    return result
 268.393 +  end
 268.394 +end
 268.395 +
 268.396 +
 268.397 +
 268.398 +-----------------
 268.399 +-- attachments --
 268.400 +-----------------
 268.401 +
 268.402 +local function attach_key(row, fields)
 268.403 +  local t = type(fields)
 268.404 +  if t == "string" then
 268.405 +    return tostring(row[fields])
 268.406 +  elseif t == "table" then
 268.407 +    local r = {}
 268.408 +    for idx, field in ipairs(fields) do
 268.409 +      r[idx] = string.format("%q", row[field])
 268.410 +    end
 268.411 +    return table.concat(r)
 268.412 +  else
 268.413 +    error("Field information for 'mondelefant.attach' is neither a string nor a table.")
 268.414 +  end
 268.415 +end
 268.416 +
 268.417 +function attach(mode, data1, data2, key1, key2, ref1, ref2)
 268.418 +  local many1, many2
 268.419 +  if mode == "11" then
 268.420 +    many1 = false
 268.421 +    many2 = false
 268.422 +  elseif mode == "1m" then
 268.423 +    many1 = false
 268.424 +    many2 = true
 268.425 +  elseif mode == "m1" then
 268.426 +    many1 = true
 268.427 +    many2 = false
 268.428 +  elseif mode == "mm" then
 268.429 +    many1 = true
 268.430 +    many2 = true
 268.431 +  else
 268.432 +    error("Unknown mode specified for 'mondelefant.attach'.")
 268.433 +  end
 268.434 +  local list1, list2
 268.435 +  if data1._type == "object" then
 268.436 +    list1 = { data1 }
 268.437 +  elseif data1._type == "list" then
 268.438 +    list1 = data1
 268.439 +  else
 268.440 +    error("First result data given to 'mondelefant.attach' is invalid.")
 268.441 +  end
 268.442 +  if data2._type == "object" then
 268.443 +    list2 = { data2 }
 268.444 +  elseif data2._type == "list" then
 268.445 +    list2 = data2
 268.446 +  else
 268.447 +    error("Second result data given to 'mondelefant.attach' is invalid.")
 268.448 +  end
 268.449 +  local hash1 = {}
 268.450 +  local hash2 = {}
 268.451 +  if ref2 then
 268.452 +    for i, row in ipairs(list1) do
 268.453 +      local key = attach_key(row, key1)
 268.454 +      local list = hash1[key]
 268.455 +      if not list then list = {}; hash1[key] = list end
 268.456 +      list[#list + 1] = row
 268.457 +    end
 268.458 +  end
 268.459 +  if ref1 then
 268.460 +    for i, row in ipairs(list2) do
 268.461 +      local key = attach_key(row, key2)
 268.462 +      local list = hash2[key]
 268.463 +      if not list then list = {}; hash2[key] = list end
 268.464 +      list[#list + 1] = row
 268.465 +    end
 268.466 +    for i, row in ipairs(list1) do
 268.467 +      local key = attach_key(row, key1)
 268.468 +      local matching_rows = hash2[key]
 268.469 +      if many2 then
 268.470 +        local list = data2._connection:create_list(matching_rows)
 268.471 +        list._class = data2._class
 268.472 +        row._ref[ref1] = list
 268.473 +      elseif matching_rows and #matching_rows == 1 then
 268.474 +        row._ref[ref1] = matching_rows[1]
 268.475 +      else
 268.476 +        row._ref[ref1] = false
 268.477 +      end
 268.478 +    end
 268.479 +  end
 268.480 +  if ref2 then
 268.481 +    for i, row in ipairs(list2) do
 268.482 +      local key = attach_key(row, key2)
 268.483 +      local matching_rows = hash1[key]
 268.484 +      if many1 then
 268.485 +        local list = data1._connection:create_list(matching_rows)
 268.486 +        list._class = data1._class
 268.487 +        row._ref[ref2] = list
 268.488 +      elseif matching_rows and #matching_rows == 1 then
 268.489 +        row._ref[ref2] = matching_rows[1]
 268.490 +      else
 268.491 +        row._ref[ref2] = false
 268.492 +      end
 268.493 +    end
 268.494 +  end
 268.495 +end
 268.496 +
 268.497 +
 268.498 +
 268.499 +------------------
 268.500 +-- model system --
 268.501 +------------------
 268.502 +
 268.503 +class_prototype.primary_key = "id"
 268.504 +
 268.505 +function class_prototype:get_db_conn()
 268.506 +  error(
 268.507 +    "Method mondelefant class(_prototype):get_db_conn() " ..
 268.508 +    "has to be implemented."
 268.509 +  )
 268.510 +end
 268.511 +
 268.512 +function class_prototype:get_qualified_table()
 268.513 +  if not self.table then error "Table unknown." end
 268.514 +  if self.schema then
 268.515 +    return '"' .. self.schema .. '"."' .. self.table .. '"'
 268.516 +  else
 268.517 +    return '"' .. self.table .. '"'
 268.518 +  end
 268.519 +end
 268.520 +
 268.521 +function class_prototype:get_qualified_table_literal()
 268.522 +  if not self.table then error "Table unknown." end
 268.523 +  if self.schema then
 268.524 +    return self.schema .. '.' .. self.table
 268.525 +  else
 268.526 +    return self.table
 268.527 +  end
 268.528 +end
 268.529 +
 268.530 +function class_prototype:get_primary_key_list()
 268.531 +  local primary_key = self.primary_key
 268.532 +  if type(primary_key) == "string" then
 268.533 +    return {primary_key}
 268.534 +  else
 268.535 +    return primary_key
 268.536 +  end
 268.537 +end
 268.538 +
 268.539 +function class_prototype:get_columns()
 268.540 +  if self._columns then
 268.541 +    return self._columns
 268.542 +  end
 268.543 +  local selector = self:get_db_conn():new_selector()
 268.544 +  selector:set_class(self)
 268.545 +  selector:from(self:get_qualified_table())
 268.546 +  selector:add_field("*")
 268.547 +  selector:add_where("FALSE")
 268.548 +  local db_result = selector:exec()
 268.549 +  local connection = db_result._connection
 268.550 +  local columns = {}
 268.551 +  for idx, info in ipairs(db_result._column_info) do
 268.552 +    local key   = info.field_name
 268.553 +    local value = {
 268.554 +      name = key,
 268.555 +      type = connection.type_mappings[info.type]
 268.556 +    }
 268.557 +    columns[key] = value
 268.558 +    table.insert(columns, value)
 268.559 +  end
 268.560 +  self._columns = columns
 268.561 +  return columns
 268.562 +end
 268.563 +
 268.564 +function class_prototype:new_selector(db_conn)
 268.565 +  local selector = (db_conn or self:get_db_conn()):new_selector()
 268.566 +  selector:set_class(self)
 268.567 +  selector:from(self:get_qualified_table())
 268.568 +  selector:add_field(self:get_qualified_table() .. ".*")
 268.569 +  return selector
 268.570 +end
 268.571 +
 268.572 +function class_prototype:create_list()
 268.573 +  local list = self:get_db_conn():create_list()
 268.574 +  list._class = self
 268.575 +  return list
 268.576 +end
 268.577 +
 268.578 +function class_prototype:new()
 268.579 +  local object = self:get_db_conn():create_object()
 268.580 +  object._class = self
 268.581 +  object._new = true
 268.582 +  return object
 268.583 +end
 268.584 +
 268.585 +function class_prototype.object:try_save()
 268.586 +  if not self._class then
 268.587 +    error("Cannot save object: No class information available.")
 268.588 +  end
 268.589 +  local primary_key = self._class:get_primary_key_list()
 268.590 +  local primary_key_sql = { sep = ", " }
 268.591 +  for idx, value in ipairs(primary_key) do
 268.592 +    primary_key_sql[idx] = '"' .. value .. '"'
 268.593 +  end
 268.594 +  if self._new then
 268.595 +    local fields = {sep = ", "}
 268.596 +    local values = {sep = ", "}
 268.597 +    for key, dummy in pairs(self._dirty or {}) do
 268.598 +      add(fields, {'"$"', {key}})
 268.599 +      add(values, {'?', self[key]})
 268.600 +    end
 268.601 +    if compat_returning then  -- compatibility for PostgreSQL 8.1
 268.602 +      local db_error, db_result1, db_result2 = self._connection:try_query(
 268.603 +        {
 268.604 +          'INSERT INTO $ ($) VALUES ($)',
 268.605 +          {self._class:get_qualified_table()},
 268.606 +          fields,
 268.607 +          values,
 268.608 +          primary_key_sql
 268.609 +        },
 268.610 +        "list",
 268.611 +        {
 268.612 +          'SELECT currval(?)',
 268.613 +          self._class.table .. '_id_seq'
 268.614 +        },
 268.615 +        "object"
 268.616 +      )
 268.617 +      if db_error then
 268.618 +        return db_error
 268.619 +      end
 268.620 +      self.id = db_result2.id
 268.621 +    else
 268.622 +      local db_error, db_result = self._connection:try_query(
 268.623 +        {
 268.624 +          'INSERT INTO $ ($) VALUES ($) RETURNING ($)',
 268.625 +          {self._class:get_qualified_table()},
 268.626 +          fields,
 268.627 +          values,
 268.628 +          primary_key_sql
 268.629 +        },
 268.630 +        "object"
 268.631 +      )
 268.632 +      if db_error then
 268.633 +        return db_error
 268.634 +      end
 268.635 +      for idx, value in ipairs(primary_key) do
 268.636 +        self[value] = db_result[value]
 268.637 +      end
 268.638 +    end
 268.639 +    self._new = false
 268.640 +  else
 268.641 +    local command_sets = {sep = ", "}
 268.642 +    for key, dummy in pairs(self._dirty or {}) do
 268.643 +      add(command_sets, {'"$" = ?', {key}, self[key]})
 268.644 +    end
 268.645 +    if #command_sets >= 1 then
 268.646 +      local primary_key_compare = {sep = " AND "}
 268.647 +      for idx, value in ipairs(primary_key) do
 268.648 +        primary_key_compare[idx] = {
 268.649 +          "$ = ?",
 268.650 +          {'"' .. value .. '"'},
 268.651 +          self[value]
 268.652 +        }
 268.653 +      end
 268.654 +      local db_error = self._connection:try_query{
 268.655 +        'UPDATE $ SET $ WHERE $',
 268.656 +        {self._class:get_qualified_table()},
 268.657 +        command_sets,
 268.658 +        primary_key_compare
 268.659 +      }
 268.660 +      if db_error then
 268.661 +        return db_error
 268.662 +      end
 268.663 +    end
 268.664 +  end
 268.665 +  return nil
 268.666 +end
 268.667 +
 268.668 +function class_prototype.object:save()
 268.669 +  local db_error = self:try_save()
 268.670 +  if db_error then
 268.671 +    db_error:escalate()
 268.672 +  end
 268.673 +  return self
 268.674 +end
 268.675 +
 268.676 +function class_prototype.object:try_destroy()
 268.677 +  if not self._class then
 268.678 +    error("Cannot destroy object: No class information available.")
 268.679 +  end
 268.680 +  local primary_key = self._class:get_primary_key_list()
 268.681 +  local primary_key_compare = {sep = " AND "}
 268.682 +  for idx, value in ipairs(primary_key) do
 268.683 +    primary_key_compare[idx] = {
 268.684 +      "$ = ?",
 268.685 +      {'"' .. value .. '"'},
 268.686 +      self[value]
 268.687 +    }
 268.688 +  end
 268.689 +  return self._connection:try_query{
 268.690 +    'DELETE FROM $ WHERE $',
 268.691 +    {self._class:get_qualified_table()},
 268.692 +    primary_key_compare
 268.693 +  }
 268.694 +end
 268.695 +
 268.696 +function class_prototype.object:destroy()
 268.697 +  local db_error = self:try_destroy()
 268.698 +  if db_error then
 268.699 +    db_error:escalate()
 268.700 +  end
 268.701 +  return self
 268.702 +end
 268.703 +
 268.704 +function class_prototype.list:get_reference_selector(
 268.705 +  ref_name, options, ref_alias, back_ref_alias
 268.706 +)
 268.707 +  local ref_info = self._class.references[ref_name]
 268.708 +  if not ref_info then
 268.709 +    error('Reference with name "' .. ref_name .. '" not found.')
 268.710 +  end
 268.711 +  local selector = ref_info.selector_generator(self, options or {})
 268.712 +  local mode = ref_info.mode
 268.713 +  if mode == "mm" or mode == "1m" then
 268.714 +    mode = "m1"
 268.715 +  elseif mode == "m1" then
 268.716 +    mode = "1m"
 268.717 +  end
 268.718 +  local ref_alias = ref_alias
 268.719 +  if ref_alias == false then
 268.720 +    ref_alias = nil
 268.721 +  elseif ref_alias == nil then
 268.722 +    ref_alias = ref_name
 268.723 +  end
 268.724 +  local back_ref_alias
 268.725 +  if back_ref_alias == false then
 268.726 +    back_ref_alias = nil
 268.727 +  elseif back_ref_alias == nil then
 268.728 +    back_ref_alias = ref_info.back_ref
 268.729 +  end
 268.730 +  selector:attach(
 268.731 +    mode,
 268.732 +    self,
 268.733 +    ref_info.that_key,                   ref_info.this_key,
 268.734 +    back_ref_alias or ref_info.back_ref, ref_alias or ref_name
 268.735 +  )
 268.736 +  return selector
 268.737 +end
 268.738 +
 268.739 +function class_prototype.list.load(...)
 268.740 +  return class_prototype.list.get_reference_selector(...):exec()
 268.741 +end
 268.742 +
 268.743 +function class_prototype.object:get_reference_selector(...)
 268.744 +  local list = self._class:create_list()
 268.745 +  list[1] = self
 268.746 +  return list:get_reference_selector(...)
 268.747 +end
 268.748 +
 268.749 +function class_prototype.object.load(...)
 268.750 +  return class_prototype.object.get_reference_selector(...):exec()
 268.751 +end
 268.752 +
 268.753 +
 268.754 +function class_prototype:add_reference(args)
 268.755 +  local selector_generator    = args.selector_generator
 268.756 +  local mode                  = args.mode
 268.757 +  local to                    = args.to
 268.758 +  local this_key              = args.this_key
 268.759 +  local that_key              = args.that_key
 268.760 +  local connected_by_table    = args.connected_by_table  -- TODO: split to table and schema
 268.761 +  local connected_by_this_key = args.connected_by_this_key
 268.762 +  local connected_by_that_key = args.connected_by_that_key
 268.763 +  local ref                   = args.ref
 268.764 +  local back_ref              = args.back_ref
 268.765 +  local default_order         = args.default_order
 268.766 +  local model
 268.767 +  local function get_model()
 268.768 +    if not model then
 268.769 +      if type(to) == "string" then
 268.770 +        model = _G
 268.771 +        for path_element in string.gmatch(to, "[^.]+") do
 268.772 +          model = model[path_element]
 268.773 +        end
 268.774 +      elseif type(to) == "function" then
 268.775 +        model = to()
 268.776 +      else
 268.777 +        model = to
 268.778 +      end
 268.779 +    end
 268.780 +    if not model or model == _G then
 268.781 +      error("Could not get model for reference.")
 268.782 +    end
 268.783 +    return model
 268.784 +  end
 268.785 +  self.references[ref] = {
 268.786 +    mode     = mode,
 268.787 +    this_key = this_key,
 268.788 +    that_key = connected_by_table and "mm_ref_" or that_key,
 268.789 +    ref      = ref,
 268.790 +    back_ref = back_ref,
 268.791 +    selector_generator = selector_generator or function(list, options)
 268.792 +      -- TODO: support tuple keys
 268.793 +      local options = options or {}
 268.794 +      local model = get_model()
 268.795 +      -- TODO: too many records cause PostgreSQL command stack overflow
 268.796 +      local ids = { sep = ", " }
 268.797 +      for i, object in ipairs(list) do
 268.798 +        local id = object[this_key]
 268.799 +        if id ~= nil then
 268.800 +          ids[#ids+1] = {"?", id}
 268.801 +        end
 268.802 +      end
 268.803 +      if #ids == 0 then
 268.804 +        return model:new_selector():empty_list_mode()
 268.805 +      end
 268.806 +      local selector = model:new_selector()
 268.807 +      if connected_by_table then
 268.808 +        selector:join(
 268.809 +          connected_by_table,
 268.810 +          nil,
 268.811 +          {
 268.812 +            '$."$" = $."$"',
 268.813 +            {connected_by_table},
 268.814 +            {connected_by_that_key},
 268.815 +            {model:get_qualified_table()},
 268.816 +            {that_key}
 268.817 +          }
 268.818 +        )
 268.819 +        selector:add_field(
 268.820 +          {
 268.821 +            '$."$"',
 268.822 +            {connected_by_table},
 268.823 +            {connected_by_this_key}
 268.824 +          },
 268.825 +          'mm_ref_'
 268.826 +        )
 268.827 +        selector:add_where{
 268.828 +          '$."$" IN ($)',
 268.829 +          {connected_by_table},
 268.830 +          {connected_by_this_key},
 268.831 +          ids
 268.832 +        }
 268.833 +      else
 268.834 +        selector:add_where{'"$" IN ($)', {that_key}, ids}
 268.835 +      end
 268.836 +      if options.order == nil and default_order then
 268.837 +        selector:add_order_by(default_order)
 268.838 +      elseif options.order then
 268.839 +        selector:add_order_by(options.order)
 268.840 +      end
 268.841 +      return selector
 268.842 +    end
 268.843 +  }
 268.844 +  if mode == "m1" or mode == "11" then
 268.845 +    self.foreign_keys[this_key] = ref
 268.846 +  end
 268.847 +  return self
 268.848 +end
   269.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   269.2 +++ b/libraries/mondelefant/mondelefant_atom_connector.lua	Sun Oct 25 12:00:00 2009 +0100
   269.3 @@ -0,0 +1,189 @@
   269.4 +#!/usr/bin/env lua
   269.5 +
   269.6 +local _G             = _G
   269.7 +local _VERSION       = _VERSION
   269.8 +local assert         = assert
   269.9 +local collectgarbage = collectgarbage
  269.10 +local dofile         = dofile
  269.11 +local error          = error
  269.12 +local getfenv        = getfenv
  269.13 +local getmetatable   = getmetatable
  269.14 +local ipairs         = ipairs
  269.15 +local load           = load
  269.16 +local loadfile       = loadfile
  269.17 +local loadstring     = loadstring
  269.18 +local module         = module
  269.19 +local next           = next
  269.20 +local pairs          = pairs
  269.21 +local pcall          = pcall
  269.22 +local print          = print
  269.23 +local rawequal       = rawequal
  269.24 +local rawget         = rawget
  269.25 +local rawset         = rawset
  269.26 +local require        = require
  269.27 +local select         = select
  269.28 +local setfenv        = setfenv
  269.29 +local setmetatable   = setmetatable
  269.30 +local tonumber       = tonumber
  269.31 +local tostring       = tostring
  269.32 +local type           = type
  269.33 +local unpack         = unpack
  269.34 +local xpcall         = xpcall
  269.35 +
  269.36 +local coroutine = coroutine
  269.37 +local debug     = debug
  269.38 +local io        = io
  269.39 +local math      = math
  269.40 +local os        = os
  269.41 +local package   = package
  269.42 +local string    = string
  269.43 +
  269.44 +local mondelefant = require("mondelefant")
  269.45 +local atom        = require("atom")
  269.46 +
  269.47 +module(...)
  269.48 +
  269.49 +
  269.50 +input_converters = setmetatable({}, { __mode = "k" })
  269.51 +
  269.52 +input_converters["boolean"] = function(conn, value)
  269.53 +  if value then return "TRUE" else return "FALSE" end
  269.54 +end
  269.55 +
  269.56 +input_converters["number"] = function(conn, value)
  269.57 +  local str = tostring(value)
  269.58 +  if string.find(str, "^[0-9%.e%-]+$") then
  269.59 +    return str
  269.60 +  else
  269.61 +    return "'NaN'"
  269.62 +  end
  269.63 +end
  269.64 +
  269.65 +input_converters[atom.fraction] = function(conn, value)
  269.66 +  if value.invalid then
  269.67 +    return "'NaN'"
  269.68 +  else
  269.69 +    local n, d = tostring(value.numerator), tostring(value.denominator)
  269.70 +    if string.find(n, "^%-?[0-9]+$") and string.find(d, "^%-?[0-9]+$") then
  269.71 +      return "(" .. n .. "::numeric / " .. d .. "::numeric)"
  269.72 +    else
  269.73 +      return "'NaN'"
  269.74 +    end
  269.75 +  end
  269.76 +end
  269.77 +
  269.78 +input_converters[atom.date] = function(conn, value)
  269.79 +  return conn:quote_string(tostring(value)) .. "::date"
  269.80 +end
  269.81 +
  269.82 +input_converters[atom.timestamp] = function(conn, value)
  269.83 +  return conn:quote_string(tostring(value))  -- don't define type
  269.84 +end
  269.85 +
  269.86 +input_converters[atom.time] = function(conn, value)
  269.87 +  return conn:quote_string(tostring(value)) .. "::time"
  269.88 +end
  269.89 +
  269.90 +
  269.91 +output_converters = setmetatable({}, { __mode = "k" })
  269.92 +
  269.93 +output_converters.int8 = function(str) return atom.integer:load(str) end
  269.94 +output_converters.int4 = function(str) return atom.integer:load(str) end
  269.95 +output_converters.int2 = function(str) return atom.integer:load(str) end
  269.96 +
  269.97 +output_converters.numeric = function(str) return atom.number:load(str) end
  269.98 +output_converters.float4  = function(str) return atom.number:load(str) end
  269.99 +output_converters.float8  = function(str) return atom.number:load(str) end
 269.100 +
 269.101 +output_converters.bool = function(str) return atom.boolean:load(str) end
 269.102 +
 269.103 +output_converters.date = function(str) return atom.date:load(str) end
 269.104 +
 269.105 +local timestamp_loader_func = function(str)
 269.106 +  local hour, minute, second = string.match(
 269.107 +    str,
 269.108 +    "^([0-9]?[0-9]):([0-9][0-9]):([0-9][0-9])"
 269.109 +  )
 269.110 +  if hour then
 269.111 +    return atom.timestamp{
 269.112 +      hour   = tonumber(hour),
 269.113 +      minute = tonumber(minute),
 269.114 +      second = tonumber(second)
 269.115 +    }
 269.116 +  else
 269.117 +    return atom.timestamp.invalid
 269.118 +  end
 269.119 +end
 269.120 +output_converters.timestamp = timestamp_loader_func
 269.121 +output_converters.timestamptz = timestamp_loader_func
 269.122 +
 269.123 +local time_loader_func = function(str)
 269.124 +  local year, month, day, hour, minute, second = string.match(
 269.125 +    str,
 269.126 +    "^([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9]) ([0-9]?[0-9]):([0-9][0-9]):([0-9][0-9])"
 269.127 +  )
 269.128 +  if year then
 269.129 +    return atom.time{
 269.130 +      year   = tonumber(year),
 269.131 +      month  = tonumber(month),
 269.132 +      day    = tonumber(day),
 269.133 +      hour   = tonumber(hour),
 269.134 +      minute = tonumber(minute),
 269.135 +      second = tonumber(second)
 269.136 +    }
 269.137 +  else
 269.138 +    return atom.time.invalid
 269.139 +  end
 269.140 +end
 269.141 +output_converters.time = time_loader_func
 269.142 +output_converters.timetz = time_loader_func
 269.143 +
 269.144 +mondelefant.postgresql_connection_prototype.type_mappings = {
 269.145 +  int8 = atom.integer,
 269.146 +  int4 = atom.integer,
 269.147 +  int2 = atom.integer,
 269.148 +  bool = atom.boolean,
 269.149 +  date = atom.date,
 269.150 +  timestamp = atom.timestamp,
 269.151 +  time = atom.time,
 269.152 +  text = atom.string,
 269.153 +  varchar = atom.string,
 269.154 +}
 269.155 +
 269.156 +
 269.157 +function mondelefant.postgresql_connection_prototype.input_converter(conn, value, info)
 269.158 +  if value == nil then
 269.159 +    return "NULL"
 269.160 +  else
 269.161 +    local converter =
 269.162 +      input_converters[getmetatable(value)] or
 269.163 +      input_converters[type(value)]
 269.164 +    if converter then
 269.165 +      return converter(conn, value)
 269.166 +    else
 269.167 +      return conn:quote_string(tostring(value))
 269.168 +    end
 269.169 +  end
 269.170 +end
 269.171 +
 269.172 +function mondelefant.postgresql_connection_prototype.output_converter(conn, value, info)
 269.173 +  if value == nil then
 269.174 +    return nil
 269.175 +  else
 269.176 +    local converter = output_converters[info.type]
 269.177 +    if converter then
 269.178 +      return converter(value)
 269.179 +    else
 269.180 +      return value
 269.181 +    end
 269.182 +  end
 269.183 +end
 269.184 +
 269.185 +
 269.186 +--[[
 269.187 +
 269.188 +db = assert(mondelefant.connect{engine='postgresql', dbname='test'})
 269.189 +result = db:query{'SELECT ? + 1', atom.date{ year=1999, month=12, day=31}}
 269.190 +print(result[1][1].year)
 269.191 +
 269.192 +--]]
   270.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   270.2 +++ b/libraries/mondelefant/mondelefant_native.c	Sun Oct 25 12:00:00 2009 +0100
   270.3 @@ -0,0 +1,1558 @@
   270.4 +#include <lua.h>
   270.5 +#include <lauxlib.h>
   270.6 +#include <libpq-fe.h>
   270.7 +#include <postgres.h>
   270.8 +#include <catalog/pg_type.h>
   270.9 +#include <stdint.h>
  270.10 +
  270.11 +#define MONDELEFANT_REGKEY "e449ba8d9a53d353_mondelefant"
  270.12 +
  270.13 +#define MONDELEFANT_MODULE_REGKEY (MONDELEFANT_REGKEY "_module")
  270.14 +#define MONDELEFANT_CONN_MT_REGKEY (MONDELEFANT_REGKEY "_connection")
  270.15 +#define MONDELEFANT_CONN_DATA_REGKEY (MONDELEFANT_REGKEY "_connection_data")
  270.16 +#define MONDELEFANT_RESULT_MT_REGKEY (MONDELEFANT_REGKEY "_result")
  270.17 +#define MONDELEFANT_ERROROBJECT_MT_REGKEY (MONDELEFANT_REGKEY "_errorobject")
  270.18 +#define MONDELEFANT_CLASS_MT_REGKEY (MONDELEFANT_REGKEY "_class")
  270.19 +#define MONDELEFANT_CLASS_PROTO_REGKEY (MONDELEFANT_REGKEY "_class_proto")
  270.20 +
  270.21 +#define MONDELEFANT_SERVER_ENCODING_ASCII 0
  270.22 +#define MONDELEFANT_SERVER_ENCODING_UTF8  1
  270.23 +
  270.24 +typedef struct {
  270.25 +  PGconn *pgconn;
  270.26 +  int server_encoding;
  270.27 +} mondelefant_conn_t;
  270.28 +
  270.29 +static size_t utf8_position_to_byte(const char *str, size_t utf8pos) {
  270.30 +  size_t bytepos;
  270.31 +  for (bytepos = 0; utf8pos > 0; bytepos++) {
  270.32 +    uint8_t c;
  270.33 +    c = ((const uint8_t *)str)[bytepos];
  270.34 +    if (!c) break;
  270.35 +    if (c <= 0x7f || c >= 0xc0) utf8pos--;
  270.36 +  }
  270.37 +  return bytepos;
  270.38 +}
  270.39 +
  270.40 +#define MONDELEFANT_POSTGRESQL_BINARY_OID ((Oid)17)
  270.41 +
  270.42 +static const char *mondelefant_oid_to_typestr(Oid oid) {
  270.43 +  switch (oid) {
  270.44 +    case 16: return "bool";
  270.45 +    case 17: return "bytea";
  270.46 +    case 18: return "char";
  270.47 +    case 19: return "name";
  270.48 +    case 20: return "int8";
  270.49 +    case 21: return "int2";
  270.50 +    case 23: return "int4";
  270.51 +    case 25: return "text";
  270.52 +    case 26: return "oid";
  270.53 +    case 27: return "tid";
  270.54 +    case 28: return "xid";
  270.55 +    case 29: return "cid";
  270.56 +    case 600: return "point";
  270.57 +    case 601: return "lseg";
  270.58 +    case 602: return "path";
  270.59 +    case 603: return "box";
  270.60 +    case 604: return "polygon";
  270.61 +    case 628: return "line";
  270.62 +    case 700: return "float4";
  270.63 +    case 701: return "float8";
  270.64 +    case 705: return "unknown";
  270.65 +    case 718: return "circle";
  270.66 +    case 790: return "money";
  270.67 +    case 829: return "macaddr";
  270.68 +    case 869: return "inet";
  270.69 +    case 650: return "cidr";
  270.70 +    case 1042: return "bpchar";
  270.71 +    case 1043: return "varchar";
  270.72 +    case 1082: return "date";
  270.73 +    case 1083: return "time";
  270.74 +    case 1114: return "timestamp";
  270.75 +    case 1184: return "timestamptz";
  270.76 +    case 1186: return "interval";
  270.77 +    case 1266: return "timetz";
  270.78 +    case 1560: return "bit";
  270.79 +    case 1562: return "varbit";
  270.80 +    case 1700: return "numeric";
  270.81 +    default: return NULL;
  270.82 +  }
  270.83 +}
  270.84 +
  270.85 +#define mondelefant_errcode_item(incode, outcode) \
  270.86 +  if (!strncmp(pgcode, (incode), strlen(incode))) return outcode; else
  270.87 +
  270.88 +#define MONDELEFANT_ERRCODE_UNKNOWN "unknown"
  270.89 +#define MONDELEFANT_ERRCODE_CONNECTION "ConnectionException"
  270.90 +#define MONDELEFANT_ERRCODE_RESULTCOUNT_LOW "WrongResultSetCount.ResultSetMissing"
  270.91 +#define MONDELEFANT_ERRCODE_RESULTCOUNT_HIGH "WrongResultSetCount.TooManyResults"
  270.92 +#define MONDELEFANT_ERRCODE_QUERY1_NO_ROWS "NoData.OneRowExpected"
  270.93 +#define MONDELEFANT_ERRCODE_QUERY1_MULTIPLE_ROWS "CardinalityViolation.OneRowExpected"
  270.94 +
  270.95 +static const char *mondelefant_translate_errcode(const char *pgcode) {
  270.96 +  if (!pgcode) abort();  // should not happen
  270.97 +  mondelefant_errcode_item("02", "NoData")
  270.98 +  mondelefant_errcode_item("03", "SqlStatementNotYetComplete")
  270.99 +  mondelefant_errcode_item("08", "ConnectionException")
 270.100 +  mondelefant_errcode_item("09", "TriggeredActionException")
 270.101 +  mondelefant_errcode_item("0A", "FeatureNotSupported")
 270.102 +  mondelefant_errcode_item("0B", "InvalidTransactionInitiation")
 270.103 +  mondelefant_errcode_item("0F", "LocatorException")
 270.104 +  mondelefant_errcode_item("0L", "InvalidGrantor")
 270.105 +  mondelefant_errcode_item("0P", "InvalidRoleSpecification")
 270.106 +  mondelefant_errcode_item("21", "CardinalityViolation")
 270.107 +  mondelefant_errcode_item("22", "DataException")
 270.108 +  mondelefant_errcode_item("23001", "IntegrityConstraintViolation.RestrictViolation")
 270.109 +  mondelefant_errcode_item("23502", "IntegrityConstraintViolation.NotNullViolation")
 270.110 +  mondelefant_errcode_item("23503", "IntegrityConstraintViolation.ForeignKeyViolation")
 270.111 +  mondelefant_errcode_item("23505", "IntegrityConstraintViolation.UniqueViolation")
 270.112 +  mondelefant_errcode_item("23514", "IntegrityConstraintViolation.CheckViolation")
 270.113 +  mondelefant_errcode_item("23",    "IntegrityConstraintViolation")
 270.114 +  mondelefant_errcode_item("24", "InvalidCursorState")
 270.115 +  mondelefant_errcode_item("25", "InvalidTransactionState")
 270.116 +  mondelefant_errcode_item("26", "InvalidSqlStatementName")
 270.117 +  mondelefant_errcode_item("27", "TriggeredDataChangeViolation")
 270.118 +  mondelefant_errcode_item("28", "InvalidAuthorizationSpecification")
 270.119 +  mondelefant_errcode_item("2B", "DependentPrivilegeDescriptorsStillExist")
 270.120 +  mondelefant_errcode_item("2D", "InvalidTransactionTermination")
 270.121 +  mondelefant_errcode_item("2F", "SqlRoutineException")
 270.122 +  mondelefant_errcode_item("34", "InvalidCursorName")
 270.123 +  mondelefant_errcode_item("38", "ExternalRoutineException")
 270.124 +  mondelefant_errcode_item("39", "ExternalRoutineInvocationException")
 270.125 +  mondelefant_errcode_item("3B", "SavepointException")
 270.126 +  mondelefant_errcode_item("3D", "InvalidCatalogName")
 270.127 +  mondelefant_errcode_item("3F", "InvalidSchemaName")
 270.128 +  mondelefant_errcode_item("40", "TransactionRollback")
 270.129 +  mondelefant_errcode_item("42", "SyntaxErrorOrAccessRuleViolation")
 270.130 +  mondelefant_errcode_item("44", "WithCheckOptionViolation")
 270.131 +  mondelefant_errcode_item("53", "InsufficientResources")
 270.132 +  mondelefant_errcode_item("54", "ProgramLimitExceeded")
 270.133 +  mondelefant_errcode_item("55", "ObjectNotInPrerequisiteState")
 270.134 +  mondelefant_errcode_item("57", "OperatorIntervention")
 270.135 +  mondelefant_errcode_item("58", "SystemError")
 270.136 +  mondelefant_errcode_item("F0", "ConfigurationFileError")
 270.137 +  mondelefant_errcode_item("P0", "PlpgsqlError")
 270.138 +  mondelefant_errcode_item("XX", "InternalError")
 270.139 +  return "unknown";
 270.140 +}
 270.141 +
 270.142 +static int mondelefant_check_error_class(
 270.143 +  const char *errcode, const char *errclass
 270.144 +) {
 270.145 +  size_t i = 0;
 270.146 +  while (1) {
 270.147 +    if (errclass[i] == 0) {
 270.148 +      if (errcode[i] == 0 || errcode[i] == '.') return 1;
 270.149 +      else return 0;
 270.150 +    }
 270.151 +    if (errcode[i] != errclass[i]) return 0;
 270.152 +    i++;
 270.153 +  }
 270.154 +}
 270.155 +
 270.156 +static void mondelefant_push_first_line(lua_State *L, const char *str) {
 270.157 +  char *str2;
 270.158 +  size_t i = 0;
 270.159 +  if (!str) abort();  // should not happen
 270.160 +  str2 = strdup(str);
 270.161 +  while (1) {
 270.162 +    char c = str2[i];
 270.163 +    if (c == '\n' || c == '\r' || c == 0) { str2[i] = 0; break; }
 270.164 +    i++;
 270.165 +  };
 270.166 +  lua_pushstring(L, str2);
 270.167 +  free(str2);
 270.168 +}
 270.169 +
 270.170 +static int mondelefant_connect(lua_State *L) {
 270.171 +  luaL_Buffer buf;
 270.172 +  const char *conninfo;
 270.173 +  PGconn *pgconn;
 270.174 +  mondelefant_conn_t *conn;
 270.175 +  lua_settop(L, 1);
 270.176 +  lua_getfield(L, 1, "engine");  // 2
 270.177 +  if (!lua_toboolean(L, 2)) {
 270.178 +    return luaL_error(L, "No database engine selected.");
 270.179 +  }
 270.180 +  lua_pushliteral(L, "postgresql");  // 3
 270.181 +  if (!lua_rawequal(L, 2, 3)) {
 270.182 +    return luaL_error(L,
 270.183 +      "Only database engine 'postgresql' is supported."
 270.184 +    );
 270.185 +  }
 270.186 +  lua_settop(L, 1);
 270.187 +  lua_pushnil(L);  // slot for key at stack position 2
 270.188 +  lua_pushnil(L);  // slot for value at stack position 3
 270.189 +  luaL_buffinit(L, &buf);
 270.190 +  {
 270.191 +    int need_seperator = 0;
 270.192 +    while (lua_pushvalue(L, 2), lua_next(L, 1)) {
 270.193 +      lua_replace(L, 3);
 270.194 +      lua_replace(L, 2);
 270.195 +      // NOTE: numbers will be converted to strings automatically here,
 270.196 +      // but perhaps this will change in future versions of lua
 270.197 +      luaL_argcheck(L,
 270.198 +        lua_isstring(L, 2) && lua_isstring(L, 3), 1, "non-string contained"
 270.199 +      );
 270.200 +      lua_pushvalue(L, 2);
 270.201 +      lua_pushliteral(L, "engine");
 270.202 +      if (!lua_rawequal(L, -2, -1)) {
 270.203 +        const char *value;
 270.204 +        size_t value_len;
 270.205 +        size_t value_pos = 0;
 270.206 +        lua_pop(L, 1);
 270.207 +        if (need_seperator) luaL_addchar(&buf, ' ');
 270.208 +        luaL_addvalue(&buf);
 270.209 +        luaL_addchar(&buf, '=');
 270.210 +        luaL_addchar(&buf, '\'');
 270.211 +        value = lua_tolstring(L, 3, &value_len);
 270.212 +        do {
 270.213 +          char c;
 270.214 +          c = value[value_pos++];
 270.215 +          if (c == '\'') luaL_addchar(&buf, '\\');
 270.216 +          luaL_addchar(&buf, c);
 270.217 +        } while (value_pos < value_len);
 270.218 +        luaL_addchar(&buf, '\'');
 270.219 +        need_seperator = 1;
 270.220 +      } else {
 270.221 +        lua_pop(L, 1);
 270.222 +      }
 270.223 +    }
 270.224 +  }
 270.225 +  luaL_pushresult(&buf);
 270.226 +  lua_replace(L, 2);
 270.227 +  lua_settop(L, 2);
 270.228 +  conninfo = lua_tostring(L, 2);
 270.229 +  pgconn = PQconnectdb(conninfo);
 270.230 +  if (!pgconn) {
 270.231 +    return luaL_error(L,
 270.232 +      "Error in libpq while creating 'PGconn' structure."
 270.233 +    );
 270.234 +  }
 270.235 +  if (PQstatus(pgconn) != CONNECTION_OK) {
 270.236 +    const char *errmsg;
 270.237 +    lua_pushnil(L);
 270.238 +    errmsg = PQerrorMessage(pgconn);
 270.239 +    if (errmsg) {
 270.240 +      mondelefant_push_first_line(L, errmsg);
 270.241 +    } else {
 270.242 +      lua_pushliteral(L,
 270.243 +        "Error while connecting to database, but no error message given."
 270.244 +      );
 270.245 +    }
 270.246 +    lua_pushliteral(L, MONDELEFANT_ERRCODE_CONNECTION);
 270.247 +    PQfinish(pgconn);
 270.248 +    return 3;
 270.249 +  }
 270.250 +  lua_settop(L, 0);
 270.251 +  conn = lua_newuserdata(L, sizeof(*conn));  // 1
 270.252 +  conn->pgconn = pgconn;
 270.253 +  {
 270.254 +    const char *charset;
 270.255 +    charset = PQparameterStatus(pgconn, "server_encoding");
 270.256 +    if (charset && !strcmp(charset, "UTF8")) {
 270.257 +      conn->server_encoding = MONDELEFANT_SERVER_ENCODING_UTF8;
 270.258 +    } else {
 270.259 +      conn->server_encoding = MONDELEFANT_SERVER_ENCODING_ASCII;
 270.260 +    }
 270.261 +  }
 270.262 +
 270.263 +  luaL_getmetatable(L, MONDELEFANT_CONN_MT_REGKEY);  // 2
 270.264 +  lua_setmetatable(L, 1);
 270.265 +
 270.266 +  lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY);  // 2
 270.267 +  lua_pushvalue(L, 1);  // 3
 270.268 +  lua_newtable(L);  // 4
 270.269 +  lua_settable(L, 2);
 270.270 +  lua_settop(L, 1);
 270.271 +
 270.272 +  lua_pushliteral(L, "postgresql");
 270.273 +  lua_setfield(L, 1, "engine");
 270.274 +  return 1;
 270.275 +}
 270.276 +
 270.277 +static mondelefant_conn_t *mondelefant_get_conn(lua_State *L, int index) {
 270.278 +  mondelefant_conn_t *conn;
 270.279 +  conn = luaL_checkudata(L, index, MONDELEFANT_CONN_MT_REGKEY);
 270.280 +  if (!conn->pgconn) {
 270.281 +    luaL_error(L, "PostgreSQL connection has been closed.");
 270.282 +    return NULL;
 270.283 +  }
 270.284 +  return conn;
 270.285 +}
 270.286 +
 270.287 +static int mondelefant_conn_index(lua_State *L) {
 270.288 +  lua_settop(L, 2);
 270.289 +  lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY);  // 3
 270.290 +  lua_pushvalue(L, 1);  // 4
 270.291 +  lua_gettable(L, 3);  // 4
 270.292 +  lua_remove(L, 3);  // connection specific data-table at stack position 3
 270.293 +  lua_pushvalue(L, 2);  // 4
 270.294 +  lua_gettable(L, 3);  // 4
 270.295 +  if (!lua_isnil(L, 4)) return 1;
 270.296 +  lua_settop(L, 3);
 270.297 +  lua_getfield(L, 3, "prototype");  // 4
 270.298 +  if (lua_toboolean(L, 4)) {
 270.299 +    lua_pushvalue(L, 2);  // 5
 270.300 +    lua_gettable(L, 4);  // 5
 270.301 +    if (!lua_isnil(L, 5)) return 1;
 270.302 +  }
 270.303 +  lua_settop(L, 2);
 270.304 +  lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_MODULE_REGKEY);  // 3
 270.305 +  lua_getfield(L, 3, "postgresql_connection_prototype");  // 4
 270.306 +  if (lua_toboolean(L, 4)) {
 270.307 +    lua_pushvalue(L, 2);  // 5
 270.308 +    lua_gettable(L, 4);  // 5
 270.309 +    if (!lua_isnil(L, 5)) return 1;
 270.310 +  }
 270.311 +  lua_settop(L, 3);
 270.312 +  lua_getfield(L, 3, "connection_prototype");  // 4
 270.313 +  if (lua_toboolean(L, 4)) {
 270.314 +    lua_pushvalue(L, 2);  // 5
 270.315 +    lua_gettable(L, 4);  // 5
 270.316 +    if (!lua_isnil(L, 5)) return 1;
 270.317 +  }
 270.318 +  return 0;
 270.319 +}
 270.320 +
 270.321 +static int mondelefant_conn_newindex(lua_State *L) {
 270.322 +  lua_settop(L, 3);
 270.323 +  lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY);  // 4
 270.324 +  lua_pushvalue(L, 1);  // 5
 270.325 +  lua_gettable(L, 4);  // 5
 270.326 +  lua_remove(L, 4);  // connection specific data-table  at stack position 4
 270.327 +  lua_pushvalue(L, 2);
 270.328 +  lua_pushvalue(L, 3);
 270.329 +  lua_settable(L, 4);
 270.330 +  return 0;
 270.331 +}
 270.332 +
 270.333 +static int mondelefant_conn_free(lua_State *L) {
 270.334 +  mondelefant_conn_t *conn;
 270.335 +  conn = luaL_checkudata(L, 1, MONDELEFANT_CONN_MT_REGKEY);
 270.336 +  if (conn->pgconn) PQfinish(conn->pgconn);
 270.337 +  conn->pgconn = NULL;
 270.338 +  return 0;
 270.339 +}
 270.340 +
 270.341 +static int mondelefant_conn_close(lua_State *L) {
 270.342 +  mondelefant_conn_t *conn;
 270.343 +  lua_settop(L, 1);
 270.344 +  conn = mondelefant_get_conn(L, 1);
 270.345 +  PQfinish(conn->pgconn);
 270.346 +  conn->pgconn = NULL;
 270.347 +  return 0;
 270.348 +}
 270.349 +
 270.350 +static int mondelefant_conn_is_ok(lua_State *L) {
 270.351 +  mondelefant_conn_t *conn;
 270.352 +  lua_settop(L, 1);
 270.353 +  conn = mondelefant_get_conn(L, 1);
 270.354 +  lua_pushboolean(L, PQstatus(conn->pgconn) == CONNECTION_OK);
 270.355 +  return 1;
 270.356 +}
 270.357 +
 270.358 +static int mondelefant_conn_get_transaction_status(lua_State *L) {
 270.359 +  mondelefant_conn_t *conn;
 270.360 +  lua_settop(L, 1);
 270.361 +  conn = mondelefant_get_conn(L, 1);
 270.362 +  switch (PQtransactionStatus(conn->pgconn)) {
 270.363 +  case PQTRANS_IDLE:
 270.364 +    lua_pushliteral(L, "idle");
 270.365 +    break;
 270.366 +  case PQTRANS_ACTIVE:
 270.367 +    lua_pushliteral(L, "active");
 270.368 +    break;
 270.369 +  case PQTRANS_INTRANS:
 270.370 +    lua_pushliteral(L, "intrans");
 270.371 +    break;
 270.372 +  case PQTRANS_INERROR:
 270.373 +    lua_pushliteral(L, "inerror");
 270.374 +    break;
 270.375 +  default:
 270.376 +    lua_pushliteral(L, "unknown");
 270.377 +  }
 270.378 +  return 1;
 270.379 +}
 270.380 +
 270.381 +static int mondelefant_conn_create_list(lua_State *L) {
 270.382 +  lua_settop(L, 2);
 270.383 +  luaL_checkudata(L, 1, MONDELEFANT_CONN_MT_REGKEY);
 270.384 +  if (!lua_toboolean(L, 2)) {
 270.385 +    lua_newtable(L);
 270.386 +    lua_replace(L, 2);  // new result at stack position 2
 270.387 +  }
 270.388 +  lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_RESULT_MT_REGKEY);  // 3
 270.389 +  lua_setmetatable(L, 2);
 270.390 +  lua_pushvalue(L, 1);  // 3
 270.391 +  lua_setfield(L, 2, "_connection");
 270.392 +  lua_pushliteral(L, "list");  // 3
 270.393 +  lua_setfield(L, 2, "_type");
 270.394 +  return 1;
 270.395 +}
 270.396 +
 270.397 +static int mondelefant_conn_create_object(lua_State *L) {
 270.398 +  lua_settop(L, 2);
 270.399 +  luaL_checkudata(L, 1, MONDELEFANT_CONN_MT_REGKEY);
 270.400 +  if (!lua_toboolean(L, 2)) {
 270.401 +    lua_newtable(L);
 270.402 +    lua_replace(L, 2);  // new result at stack position 2
 270.403 +  }
 270.404 +  lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_RESULT_MT_REGKEY);  // 3
 270.405 +  lua_setmetatable(L, 2);
 270.406 +  lua_pushvalue(L, 1);  // 3
 270.407 +  lua_setfield(L, 2, "_connection");
 270.408 +  lua_pushliteral(L, "object");  // 3
 270.409 +  lua_setfield(L, 2, "_type");  // "object" or "list"
 270.410 +  lua_newtable(L);  // 3
 270.411 +  lua_setfield(L, 2, "_data");
 270.412 +  lua_newtable(L);  // 3
 270.413 +  lua_setfield(L, 2, "_dirty");
 270.414 +  lua_newtable(L);  // 3
 270.415 +  lua_setfield(L, 2, "_ref");  // nil=no info, false=nil, else table
 270.416 +  return 1;
 270.417 +}
 270.418 +
 270.419 +static int mondelefant_conn_quote_string(lua_State *L) {
 270.420 +  mondelefant_conn_t *conn;
 270.421 +  const char *input;
 270.422 +  size_t input_len;
 270.423 +  char *output;
 270.424 +  size_t output_len;
 270.425 +  lua_settop(L, 2);
 270.426 +  conn = mondelefant_get_conn(L, 1);
 270.427 +  input = luaL_checklstring(L, 2, &input_len);
 270.428 +  if (input_len > (SIZE_MAX / sizeof(char) - 3) / 2) {
 270.429 +    return luaL_error(L, "String to be escaped is too long.");
 270.430 +  }
 270.431 +  output = malloc((2 * input_len + 3) * sizeof(char));
 270.432 +  if (!output) {
 270.433 +    return luaL_error(L, "Could not allocate memory for string quoting.");
 270.434 +  }
 270.435 +  output[0] = '\'';
 270.436 +  output_len = PQescapeStringConn(
 270.437 +    conn->pgconn, output + 1, input, input_len, NULL
 270.438 +  );
 270.439 +  output[output_len + 1] = '\'';
 270.440 +  output[output_len + 2] = 0;
 270.441 +  lua_pushlstring(L, output, output_len + 2);
 270.442 +  free(output);
 270.443 +  return 1;
 270.444 +}
 270.445 +
 270.446 +static int mondelefant_conn_quote_binary(lua_State *L) {
 270.447 +  mondelefant_conn_t *conn;
 270.448 +  const char *input;
 270.449 +  size_t input_len;
 270.450 +  char *output;
 270.451 +  size_t output_len;
 270.452 +  luaL_Buffer buf;
 270.453 +  lua_settop(L, 2);
 270.454 +  conn = mondelefant_get_conn(L, 1);
 270.455 +  input = luaL_checklstring(L, 2, &input_len);
 270.456 +  output = (char *)PQescapeByteaConn(
 270.457 +    conn->pgconn, (const unsigned char *)input, input_len, &output_len
 270.458 +  );
 270.459 +  if (!output) {
 270.460 +    return luaL_error(L, "Could not allocate memory for binary quoting.");
 270.461 +  }
 270.462 +  luaL_buffinit(L, &buf);
 270.463 +  luaL_addchar(&buf, '\'');
 270.464 +  luaL_addlstring(&buf, output, output_len - 1);
 270.465 +  luaL_addchar(&buf, '\'');
 270.466 +  luaL_pushresult(&buf);
 270.467 +  PQfreemem(output);
 270.468 +  return 1;
 270.469 +}
 270.470 +
 270.471 +static int mondelefant_conn_assemble_command(lua_State *L) {
 270.472 +  mondelefant_conn_t *conn;
 270.473 +  int paramidx = 2;
 270.474 +  const char *template;
 270.475 +  size_t template_pos = 0;
 270.476 +  luaL_Buffer buf;
 270.477 +  lua_settop(L, 2);
 270.478 +  conn = mondelefant_get_conn(L, 1);
 270.479 +  if (lua_isstring(L, 2)) {
 270.480 +    lua_tostring(L, 2);
 270.481 +    return 1;
 270.482 +  }
 270.483 +  // extra feature for objects with __tostring meta-method:
 270.484 +  if (luaL_callmeta(L, 2, "__tostring")) return 1;
 270.485 +  luaL_checktype(L, 2, LUA_TTABLE);
 270.486 +  lua_rawgeti(L, 2, 1);  // 3
 270.487 +  luaL_argcheck(L,
 270.488 +    lua_isstring(L, 3),
 270.489 +    2,
 270.490 +    "First entry of SQL command structure is not a string."
 270.491 +  );
 270.492 +  template = lua_tostring(L, 3);
 270.493 +  lua_pushliteral(L, "input_converter");  // 4
 270.494 +  lua_gettable(L, 1);  // input_converter at stack position 4
 270.495 +  lua_pushnil(L);  // free space at stack position 5
 270.496 +  lua_pushnil(L);  // free space at stack position 6
 270.497 +  luaL_buffinit(L, &buf);
 270.498 +  while (1) {
 270.499 +    char c;
 270.500 +    c = template[template_pos++];
 270.501 +    if (!c) break;
 270.502 +    if (c == '?' || c == '$') {
 270.503 +      if (template[template_pos] == c) {
 270.504 +        template_pos++;
 270.505 +        luaL_addchar(&buf, c);
 270.506 +      } else {
 270.507 +        luaL_Buffer keybuf;
 270.508 +        int subcmd;
 270.509 +        subcmd = (c == '$');
 270.510 +        luaL_buffinit(L, &keybuf);
 270.511 +        while (1) {
 270.512 +          c = template[template_pos];
 270.513 +          if (
 270.514 +            (c < 'A' || c > 'Z') &&
 270.515 +            (c < 'a' || c > 'z') &&
 270.516 +            (c < '0' || c > '9') &&
 270.517 +            (c != '_')
 270.518 +          ) break;
 270.519 +          luaL_addchar(&keybuf, c);
 270.520 +          template_pos++;
 270.521 +        }
 270.522 +        luaL_pushresult(&keybuf);
 270.523 +        if (lua_objlen(L, -1)) {
 270.524 +          lua_pushvalue(L, -1);           // save key on stack
 270.525 +          lua_gettable(L, 2);             // fetch value
 270.526 +        } else {
 270.527 +          lua_pop(L, 1);
 270.528 +          lua_pushnil(L);                 // put nil on key position
 270.529 +          lua_rawgeti(L, 2, paramidx++);  // fetch value
 270.530 +        }
 270.531 +        // stack: ..., <buffer>, key, pre-value
 270.532 +        if (subcmd) {
 270.533 +          size_t i;
 270.534 +          size_t count;
 270.535 +          lua_replace(L, 5);  // sub-structure at stack position 5
 270.536 +          lua_pop(L, 1);      // drop stored key
 270.537 +          // stack: ..., <buffer>
 270.538 +          luaL_argcheck(L,
 270.539 +            !lua_isnil(L, 5),
 270.540 +            2,
 270.541 +            "SQL sub-structure not found."
 270.542 +          );
 270.543 +          luaL_argcheck(L,
 270.544 +            lua_type(L, 5) == LUA_TTABLE,
 270.545 +            2,
 270.546 +            "SQL sub-structure must be a table."
 270.547 +          );
 270.548 +          // stack: ..., <buffer>
 270.549 +          lua_getfield(L, 5, "sep");
 270.550 +          lua_replace(L, 6);  // seperator at stack position 6
 270.551 +          if (lua_isnil(L, 6)) {
 270.552 +            lua_pushstring(L, ", ");
 270.553 +            lua_replace(L, 6);
 270.554 +          } else {
 270.555 +            luaL_argcheck(L,
 270.556 +              lua_isstring(L, 6),
 270.557 +              2,
 270.558 +              "Seperator of SQL sub-structure has to be a string."
 270.559 +            );
 270.560 +          }
 270.561 +          count = lua_objlen(L, 5);
 270.562 +          for (i = 0; i < count; i++) {
 270.563 +            if (i) {
 270.564 +              lua_pushvalue(L, 6);
 270.565 +              luaL_addvalue(&buf);
 270.566 +            }
 270.567 +            lua_pushcfunction(L, mondelefant_conn_assemble_command);
 270.568 +            lua_pushvalue(L, 1);
 270.569 +            lua_rawgeti(L, 5, i+1);
 270.570 +            lua_call(L, 2, 1);
 270.571 +            luaL_addvalue(&buf);
 270.572 +          }
 270.573 +        } else {
 270.574 +          if (lua_toboolean(L, 4)) {
 270.575 +            // call input_converter with connection handle, value and info
 270.576 +            lua_pushvalue(L, 4);
 270.577 +            lua_pushvalue(L, 1);
 270.578 +            lua_pushvalue(L, -3);
 270.579 +            lua_newtable(L);
 270.580 +            lua_pushvalue(L, -6);
 270.581 +            lua_setfield(L, -2, "field_name");
 270.582 +            lua_call(L, 3, 1);
 270.583 +            // stack: ..., <buffer>, key, pre-value, final-value
 270.584 +            lua_remove(L, -2);
 270.585 +            lua_remove(L, -2);
 270.586 +            // stack: ..., <buffer>, final-value
 270.587 +            if (!lua_isstring(L, -1)) {
 270.588 +              return luaL_error(L, "input_converter returned non-string.");
 270.589 +            }
 270.590 +          } else {
 270.591 +            lua_remove(L, -2);
 270.592 +            // stack: ..., <buffer>, pre-value
 270.593 +            if (lua_isnil(L, -1)) {
 270.594 +              lua_pushliteral(L, "NULL");
 270.595 +            } else if (lua_type(L, -1) == LUA_TBOOLEAN) {
 270.596 +              lua_pushstring(L, lua_toboolean(L, -1) ? "TRUE" : "FALSE");
 270.597 +            } else if (lua_isstring(L, -1)) {
 270.598 +              // NOTE: In this version of lua a number will be converted
 270.599 +              lua_tostring(L, -1);
 270.600 +              lua_pushcfunction(L, mondelefant_conn_quote_string);
 270.601 +              lua_pushvalue(L, 1);
 270.602 +              lua_pushvalue(L, -3);
 270.603 +              lua_call(L, 2, 1);
 270.604 +            } else {
 270.605 +              return luaL_error(L,
 270.606 +                "Unable to convert SQL value due to unknown type "
 270.607 +                "or missing input_converter."
 270.608 +              );
 270.609 +            }
 270.610 +            // stack: ..., <buffer>, pre-value, final-value
 270.611 +            lua_remove(L, -2);
 270.612 +            // stack: ..., <buffer>, final-value
 270.613 +          }
 270.614 +          luaL_addvalue(&buf);
 270.615 +        }
 270.616 +      }
 270.617 +    } else {
 270.618 +      luaL_addchar(&buf, c);
 270.619 +    }
 270.620 +  }
 270.621 +  luaL_pushresult(&buf);
 270.622 +  return 1;
 270.623 +}
 270.624 +
 270.625 +#define MONDELEFANT_MAX_COMMAND_COUNT 64
 270.626 +#define MONDELEFANT_MAX_COLUMN_COUNT 1024
 270.627 +#define MONDELEFANT_QUERY_MODE_LIST 1
 270.628 +#define MONDELEFANT_QUERY_MODE_OBJECT 2
 270.629 +#define MONDELEFANT_QUERY_MODE_OPT_OBJECT 3
 270.630 +
 270.631 +static int mondelefant_conn_try_query(lua_State *L) {
 270.632 +  mondelefant_conn_t *conn;
 270.633 +  int command_count;
 270.634 +  int command_idx;
 270.635 +  int modes[MONDELEFANT_MAX_COMMAND_COUNT];
 270.636 +  luaL_Buffer buf;
 270.637 +  int sent_success;
 270.638 +  PGresult *res;
 270.639 +  int rows, cols, row, col;
 270.640 +  conn = mondelefant_get_conn(L, 1);
 270.641 +  command_count = lua_gettop(L) / 2;
 270.642 +  lua_pushnil(L);  // needed, if last mode was omitted
 270.643 +  if (command_count > MONDELEFANT_MAX_COMMAND_COUNT) {
 270.644 +    return luaL_error(L, "Exceeded maximum command count in one query.");
 270.645 +  }
 270.646 +  luaL_buffinit(L, &buf);
 270.647 +  for (command_idx = 0; command_idx < command_count; command_idx++) {
 270.648 +    int mode;
 270.649 +    int mode_idx;  // stack index of mode string
 270.650 +    if (command_idx) luaL_addchar(&buf, ' ');
 270.651 +    lua_pushcfunction(L, mondelefant_conn_assemble_command);
 270.652 +    lua_pushvalue(L, 1);
 270.653 +    lua_pushvalue(L, 2 + 2 * command_idx);
 270.654 +    lua_call(L, 2, 1);
 270.655 +    luaL_addvalue(&buf);
 270.656 +    luaL_addchar(&buf, ';');
 270.657 +    mode_idx = 3 + 2 * command_idx;
 270.658 +    if (lua_isnil(L, mode_idx)) {
 270.659 +      mode = MONDELEFANT_QUERY_MODE_LIST;
 270.660 +    } else {
 270.661 +      const char *modestr;
 270.662 +      modestr = luaL_checkstring(L, mode_idx);
 270.663 +      if (!strcmp(modestr, "list")) {
 270.664 +        mode = MONDELEFANT_QUERY_MODE_LIST;
 270.665 +      } else if (!strcmp(modestr, "object")) {
 270.666 +        mode = MONDELEFANT_QUERY_MODE_OBJECT;
 270.667 +      } else if (!strcmp(modestr, "opt_object")) {
 270.668 +        mode = MONDELEFANT_QUERY_MODE_OPT_OBJECT;
 270.669 +      } else {
 270.670 +        return luaL_error(L, "Unknown query mode specified.");
 270.671 +      }
 270.672 +    }
 270.673 +    modes[command_idx] = mode;
 270.674 +  }
 270.675 +  luaL_pushresult(&buf);  // stack position unknown
 270.676 +  lua_replace(L, 2);  // SQL command string to stack position 2
 270.677 +  lua_settop(L, 2);
 270.678 +  lua_getfield(L, 1, "sql_tracer");  // tracer at stack position 3
 270.679 +  if (lua_toboolean(L, 3)) {
 270.680 +    lua_pushvalue(L, 1);  // 4
 270.681 +    lua_pushvalue(L, 2);  // 5
 270.682 +    lua_call(L, 2, 1);  // trace callback at stack position 3
 270.683 +  }
 270.684 +  sent_success = PQsendQuery(conn->pgconn, lua_tostring(L, 2));
 270.685 +  lua_newtable(L);  // results in table at stack position 4
 270.686 +  for (command_idx = 0; ; command_idx++) {
 270.687 +    int mode;
 270.688 +    char binary[MONDELEFANT_MAX_COLUMN_COUNT];
 270.689 +    ExecStatusType pgstatus;
 270.690 +    mode = modes[command_idx];
 270.691 +    if (sent_success) {
 270.692 +      res = PQgetResult(conn->pgconn);
 270.693 +      if (command_idx >= command_count && !res) break;
 270.694 +      if (res) {
 270.695 +        pgstatus = PQresultStatus(res);
 270.696 +        rows = PQntuples(res);
 270.697 +        cols = PQnfields(res);
 270.698 +      }
 270.699 +    }
 270.700 +    if (
 270.701 +      !sent_success || command_idx >= command_count || !res ||
 270.702 +      (pgstatus != PGRES_TUPLES_OK && pgstatus != PGRES_COMMAND_OK) ||
 270.703 +      (rows < 1 && mode == MONDELEFANT_QUERY_MODE_OBJECT) ||
 270.704 +      (rows > 1 && mode != MONDELEFANT_QUERY_MODE_LIST)
 270.705 +    ) {
 270.706 +      const char *command;
 270.707 +      command = lua_tostring(L, 2);
 270.708 +      lua_newtable(L);  // 5
 270.709 +      lua_getfield(L,
 270.710 +        LUA_REGISTRYINDEX,
 270.711 +        MONDELEFANT_ERROROBJECT_MT_REGKEY
 270.712 +      );
 270.713 +      lua_setmetatable(L, 5);
 270.714 +      lua_pushvalue(L, 1);
 270.715 +      lua_setfield(L, 5, "connection");
 270.716 +      lua_pushinteger(L, command_idx + 1);
 270.717 +      lua_setfield(L, 5, "command_number");
 270.718 +      lua_pushvalue(L, 2);
 270.719 +      lua_setfield(L, 5, "sql_command");
 270.720 +      if (!res) {
 270.721 +        lua_pushliteral(L, MONDELEFANT_ERRCODE_RESULTCOUNT_LOW);
 270.722 +        lua_setfield(L, 5, "code");
 270.723 +        lua_pushliteral(L, "Received too few database result sets.");
 270.724 +        lua_setfield(L, 5, "message");
 270.725 +      } else if (command_idx >= command_count) {
 270.726 +        lua_pushliteral(L, MONDELEFANT_ERRCODE_RESULTCOUNT_HIGH);
 270.727 +        lua_setfield(L, 5, "code");
 270.728 +        lua_pushliteral(L, "Received too many database result sets.");
 270.729 +        lua_setfield(L, 5, "message");
 270.730 +      } else if (
 270.731 +        pgstatus != PGRES_TUPLES_OK && pgstatus != PGRES_COMMAND_OK
 270.732 +      ) {
 270.733 +        const char *sqlstate;
 270.734 +        const char *errmsg;
 270.735 +        lua_pushstring(L, PQresultErrorField(res, PG_DIAG_SEVERITY));
 270.736 +        lua_setfield(L, 5, "pg_severity");
 270.737 +        sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
 270.738 +        if (sqlstate) {
 270.739 +          lua_pushstring(L, sqlstate);
 270.740 +          lua_setfield(L, 5, "pg_sqlstate");
 270.741 +          lua_pushstring(L, mondelefant_translate_errcode(sqlstate));
 270.742 +          lua_setfield(L, 5, "code");
 270.743 +        } else {
 270.744 +          lua_pushliteral(L, MONDELEFANT_ERRCODE_UNKNOWN);
 270.745 +          lua_setfield(L, 5, "code");
 270.746 +        }
 270.747 +        errmsg = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
 270.748 +        if (errmsg) {
 270.749 +          mondelefant_push_first_line(L, errmsg);
 270.750 +          lua_setfield(L, 5, "message");
 270.751 +          lua_pushstring(L, errmsg);
 270.752 +          lua_setfield(L, 5, "pg_message_primary");
 270.753 +        } else {
 270.754 +          lua_pushliteral(L,
 270.755 +            "Error while fetching result, but no error message given."
 270.756 +          );
 270.757 +          lua_setfield(L, 5, "message");
 270.758 +        }
 270.759 +        lua_pushstring(L, PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL));
 270.760 +        lua_setfield(L, 5, "pg_message_detail");
 270.761 +        lua_pushstring(L, PQresultErrorField(res, PG_DIAG_MESSAGE_HINT));
 270.762 +        lua_setfield(L, 5, "pg_message_hint");
 270.763 +        // NOTE: "position" and "pg_internal_position" are recalculated to
 270.764 +        // byte offsets, as Lua 5.1 is not Unicode aware.
 270.765 +        {
 270.766 +          char *tmp;
 270.767 +          tmp = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION);
 270.768 +          if (tmp) {
 270.769 +            int pos;
 270.770 +            pos = atoi(tmp) - 1;
 270.771 +            if (conn->server_encoding == MONDELEFANT_SERVER_ENCODING_UTF8) {
 270.772 +              pos = utf8_position_to_byte(command, pos);
 270.773 +            }
 270.774 +            lua_pushinteger(L, pos + 1);
 270.775 +            lua_setfield(L, 5, "position");
 270.776 +          }
 270.777 +        }
 270.778 +        {
 270.779 +          const char *internal_query;
 270.780 +          internal_query = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
 270.781 +          lua_pushstring(L, internal_query);
 270.782 +          lua_setfield(L, 5, "pg_internal_query");
 270.783 +          char *tmp;
 270.784 +          tmp = PQresultErrorField(res, PG_DIAG_INTERNAL_POSITION);
 270.785 +          if (tmp) {
 270.786 +            int pos;
 270.787 +            pos = atoi(tmp) - 1;
 270.788 +            if (conn->server_encoding == MONDELEFANT_SERVER_ENCODING_UTF8) {
 270.789 +              pos = utf8_position_to_byte(internal_query, pos);
 270.790 +            }
 270.791 +            lua_pushinteger(L, pos + 1);
 270.792 +            lua_setfield(L, 5, "pg_internal_position");
 270.793 +          }
 270.794 +        }
 270.795 +        lua_pushstring(L, PQresultErrorField(res, PG_DIAG_CONTEXT));
 270.796 +        lua_setfield(L, 5, "pg_context");
 270.797 +        lua_pushstring(L, PQresultErrorField(res, PG_DIAG_SOURCE_FILE));
 270.798 +        lua_setfield(L, 5, "pg_source_file");
 270.799 +        lua_pushstring(L, PQresultErrorField(res, PG_DIAG_SOURCE_LINE));
 270.800 +        lua_setfield(L, 5, "pg_source_line");
 270.801 +        lua_pushstring(L, PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION));
 270.802 +        lua_setfield(L, 5, "pg_source_function");
 270.803 +      } else if (rows < 1 && mode == MONDELEFANT_QUERY_MODE_OBJECT) {
 270.804 +        lua_pushliteral(L, MONDELEFANT_ERRCODE_QUERY1_NO_ROWS);
 270.805 +        lua_setfield(L, 5, "code");
 270.806 +        lua_pushliteral(L, "Expected one row, but got empty set.");
 270.807 +        lua_setfield(L, 5, "message");
 270.808 +      } else if (rows > 1 && mode != MONDELEFANT_QUERY_MODE_LIST) {
 270.809 +        lua_pushliteral(L, MONDELEFANT_ERRCODE_QUERY1_MULTIPLE_ROWS);
 270.810 +        lua_setfield(L, 5, "code");
 270.811 +        lua_pushliteral(L, "Got more than one result row.");
 270.812 +        lua_setfield(L, 5, "message");
 270.813 +      } else {
 270.814 +        // should not happen
 270.815 +        abort();
 270.816 +      }
 270.817 +      if (res) {
 270.818 +        PQclear(res);
 270.819 +        while ((res = PQgetResult(conn->pgconn))) PQclear(res);
 270.820 +      }
 270.821 +      if (lua_toboolean(L, 3)) {
 270.822 +        lua_pushvalue(L, 3);
 270.823 +        lua_pushvalue(L, 5);
 270.824 +        lua_call(L, 1, 0);
 270.825 +      }
 270.826 +      return 1;
 270.827 +    }
 270.828 +    rows = PQntuples(res);
 270.829 +    cols = PQnfields(res);
 270.830 +    if (modes[command_idx] == MONDELEFANT_QUERY_MODE_LIST) {
 270.831 +      lua_pushcfunction(L, mondelefant_conn_create_list);  // 5
 270.832 +      lua_pushvalue(L, 1);  // 6
 270.833 +      lua_call(L, 1, 1);  // 5
 270.834 +    } else {
 270.835 +      lua_pushcfunction(L, mondelefant_conn_create_object);  // 5
 270.836 +      lua_pushvalue(L, 1);  // 6
 270.837 +      lua_call(L, 1, 1);  // 5
 270.838 +    }
 270.839 +    lua_newtable(L);  // column_info at atack position 6
 270.840 +    for (col = 0; col < cols; col++) {
 270.841 +      lua_newtable(L);  // 7
 270.842 +      lua_pushstring(L, PQfname(res, col));
 270.843 +      lua_setfield(L, 7, "field_name");
 270.844 +      {
 270.845 +        Oid tmp;
 270.846 +        tmp = PQftable(res, col);
 270.847 +        if (tmp == InvalidOid) lua_pushnil(L);
 270.848 +        else lua_pushinteger(L, tmp);
 270.849 +        lua_setfield(L, 7, "table_oid");
 270.850 +      }
 270.851 +      {
 270.852 +        int tmp;
 270.853 +        tmp = PQftablecol(res, col);
 270.854 +        if (tmp == 0) lua_pushnil(L);
 270.855 +        else lua_pushinteger(L, tmp);
 270.856 +        lua_setfield(L, 7, "table_column_number");
 270.857 +      }
 270.858 +      {
 270.859 +        Oid tmp;
 270.860 +        tmp = PQftype(res, col);
 270.861 +        binary[col] = (tmp == MONDELEFANT_POSTGRESQL_BINARY_OID);
 270.862 +        lua_pushinteger(L, tmp);
 270.863 +        lua_setfield(L, 7, "type_oid");
 270.864 +        lua_pushstring(L, mondelefant_oid_to_typestr(tmp));
 270.865 +        lua_setfield(L, 7, "type");
 270.866 +      }
 270.867 +      {
 270.868 +        int tmp;
 270.869 +        tmp = PQfmod(res, col);
 270.870 +        if (tmp == -1) lua_pushnil(L);
 270.871 +        else lua_pushinteger(L, tmp);
 270.872 +        lua_setfield(L, 7, "type_modifier");
 270.873 +      }
 270.874 +      lua_rawseti(L, 6, col+1);
 270.875 +    }
 270.876 +    lua_setfield(L, 5, "_column_info");  // stack at position 5 with result
 270.877 +    {
 270.878 +      char *tmp;
 270.879 +      tmp = PQcmdTuples(res);
 270.880 +      if (tmp[0]) {
 270.881 +        lua_pushinteger(L, atoi(tmp));
 270.882 +        lua_setfield(L, 5, "_rows_affected");
 270.883 +      }
 270.884 +    }
 270.885 +    {
 270.886 +      Oid tmp;
 270.887 +      tmp = PQoidValue(res);
 270.888 +      if (tmp != InvalidOid) {
 270.889 +        lua_pushinteger(L, tmp);
 270.890 +        lua_setfield(L, 5, "_oid");
 270.891 +      }
 270.892 +    }
 270.893 +    if (modes[command_idx] == MONDELEFANT_QUERY_MODE_LIST) {
 270.894 +      for (row = 0; row < rows; row++) {
 270.895 +        lua_pushcfunction(L, mondelefant_conn_create_object);  // 6
 270.896 +        lua_pushvalue(L, 1);  // 7
 270.897 +        lua_call(L, 1, 1);  // 6
 270.898 +        for (col = 0; col < cols; col++) {
 270.899 +          if (PQgetisnull(res, row, col)) {
 270.900 +            lua_pushnil(L);
 270.901 +          } else if (binary[col]) {
 270.902 +            size_t binlen;
 270.903 +            char *binval;
 270.904 +            binval = (char *)PQunescapeBytea(
 270.905 +              (unsigned char *)PQgetvalue(res, row, col), &binlen
 270.906 +            );
 270.907 +            if (!binval) {
 270.908 +              return luaL_error(L,
 270.909 +                "Could not allocate memory for binary unescaping."
 270.910 +              );
 270.911 +            }
 270.912 +            lua_pushlstring(L, binval, binlen);
 270.913 +            PQfreemem(binval);
 270.914 +          } else {
 270.915 +            lua_pushstring(L, PQgetvalue(res, row, col));
 270.916 +          }
 270.917 +          lua_rawseti(L, 6, col+1);
 270.918 +        }
 270.919 +        lua_rawseti(L, 5, row+1);
 270.920 +      }
 270.921 +    } else if (rows == 1) {
 270.922 +      for (col = 0; col < cols; col++) {
 270.923 +        if (PQgetisnull(res, 0, col)) {
 270.924 +          lua_pushnil(L);
 270.925 +        } else if (binary[col]) {
 270.926 +          size_t binlen;
 270.927 +          char *binval;
 270.928 +          binval = (char *)PQunescapeBytea(
 270.929 +            (unsigned char *)PQgetvalue(res, 0, col), &binlen
 270.930 +          );
 270.931 +          if (!binval) {
 270.932 +            return luaL_error(L,
 270.933 +              "Could not allocate memory for binary unescaping."
 270.934 +            );
 270.935 +          }
 270.936 +          lua_pushlstring(L, binval, binlen);
 270.937 +          PQfreemem(binval);
 270.938 +        } else {
 270.939 +          lua_pushstring(L, PQgetvalue(res, 0, col));
 270.940 +        }
 270.941 +        lua_rawseti(L, 5, col+1);
 270.942 +      }
 270.943 +    } else {
 270.944 +      // no row in optrow mode
 270.945 +      lua_pop(L, 1);
 270.946 +      lua_pushnil(L);
 270.947 +    }
 270.948 +    lua_rawseti(L, 4, command_idx+1);
 270.949 +    if (lua_gettop(L) != 4) abort();  // should not happen
 270.950 +    PQclear(res);
 270.951 +  }
 270.952 +  // trace callback at stack position 3
 270.953 +  // result at stack position 4 (top of stack)
 270.954 +  if (lua_toboolean(L, 3)) {
 270.955 +    lua_pushvalue(L, 3);
 270.956 +    lua_call(L, 0, 0);
 270.957 +  }
 270.958 +  lua_replace(L, 3);  // result at stack position 3
 270.959 +  lua_getfield(L, 1, "output_converter");  // output converter at stack position 4
 270.960 +  for (command_idx = 0; command_idx < command_count; command_idx++) {
 270.961 +    int mode;
 270.962 +    mode = modes[command_idx];
 270.963 +    lua_rawgeti(L, 3, command_idx+1);  // raw result at stack position 5
 270.964 +    if (lua_toboolean(L, 5)) {
 270.965 +      lua_getfield(L, 5, "_column_info");  // column_info list at position 6
 270.966 +      cols = lua_objlen(L, 6);
 270.967 +      if (mode == MONDELEFANT_QUERY_MODE_LIST) {
 270.968 +        rows = lua_objlen(L, 5);
 270.969 +        for (row = 0; row < rows; row++) {
 270.970 +          lua_rawgeti(L, 5, row+1);  // row at stack position 7
 270.971 +          lua_getfield(L, 7, "_data");  // _data table at stack position 8
 270.972 +          for (col = 0; col < cols; col++) {
 270.973 +            lua_rawgeti(L, 6, col+1);  // this column info at position 9
 270.974 +            lua_getfield(L, 9, "field_name");  // 10
 270.975 +            if (lua_toboolean(L, 4)) {
 270.976 +              lua_pushvalue(L, 4);  // output-converter
 270.977 +              lua_pushvalue(L, 1);  // connection
 270.978 +              lua_rawgeti(L, 7, col+1);  // raw-value
 270.979 +              lua_pushvalue(L, 9);  // this column info
 270.980 +              lua_call(L, 3, 1);  // converted value at position 11
 270.981 +            } else {
 270.982 +              lua_rawgeti(L, 7, col+1);  // raw-value at position 11
 270.983 +            }
 270.984 +            lua_pushvalue(L, 11);  // 12
 270.985 +            lua_rawseti(L, 7, col+1);
 270.986 +            lua_rawset(L, 8);
 270.987 +            lua_settop(L, 8);
 270.988 +          }
 270.989 +          lua_settop(L, 6);
 270.990 +        }
 270.991 +      } else {
 270.992 +        lua_getfield(L, 5, "_data");  // _data table at stack position 7
 270.993 +        for (col = 0; col < cols; col++) {
 270.994 +          lua_rawgeti(L, 6, col+1);  // this column info at position 8
 270.995 +          lua_getfield(L, 8, "field_name");  // 9
 270.996 +          if (lua_toboolean(L, 4)) {
 270.997 +            lua_pushvalue(L, 4);  // output-converter
 270.998 +            lua_pushvalue(L, 1);  // connection
 270.999 +            lua_rawgeti(L, 5, col+1);  // raw-value
270.1000 +            lua_pushvalue(L, 8);  // this column info
270.1001 +            lua_call(L, 3, 1);  // converted value at position 10
270.1002 +          } else {
270.1003 +            lua_rawgeti(L, 5, col+1);  // raw-value at position 10
270.1004 +          }
270.1005 +          lua_pushvalue(L, 10);  // 11
270.1006 +          lua_rawseti(L, 5, col+1);
270.1007 +          lua_rawset(L, 7);
270.1008 +          lua_settop(L, 7);
270.1009 +        }
270.1010 +      }
270.1011 +    }
270.1012 +    lua_settop(L, 4);
270.1013 +  }
270.1014 +  lua_settop(L, 3);
270.1015 +  lua_pushnil(L);
270.1016 +  for (command_idx = 0; command_idx < command_count; command_idx++) {
270.1017 +    lua_rawgeti(L, 3, command_idx+1);
270.1018 +  }
270.1019 +  return command_count+1;
270.1020 +}
270.1021 +
270.1022 +static int mondelefant_errorobject_escalate(lua_State *L) {
270.1023 +  lua_settop(L, 1);
270.1024 +  lua_getfield(L, 1, "connection");  // 2
270.1025 +  lua_getfield(L, 2, "error_objects");  // 3
270.1026 +  if (lua_toboolean(L, 3)) {
270.1027 +    lua_settop(L, 1);
270.1028 +    return lua_error(L);
270.1029 +  } else {
270.1030 +    lua_getfield(L, 1, "message");  // 4
270.1031 +    if (lua_isnil(L, 4)) {
270.1032 +      return luaL_error(L, "No error message given for escalation.");
270.1033 +    }
270.1034 +    return lua_error(L);
270.1035 +  }
270.1036 +}
270.1037 +
270.1038 +static int mondelefant_errorobject_is_kind_of(lua_State *L) {
270.1039 +  lua_settop(L, 2);
270.1040 +  lua_getfield(L, 1, "code");  // 3
270.1041 +  if (lua_isstring(L, 3)) {
270.1042 +    lua_pushboolean(L,
270.1043 +      mondelefant_check_error_class(
270.1044 +        lua_tostring(L, 3), luaL_checkstring(L, 2)
270.1045 +      )
270.1046 +    );
270.1047 +  } else {
270.1048 +    // only happens for errors where code is not set
270.1049 +    lua_pushboolean(L, 0);
270.1050 +  }
270.1051 +  return 1;
270.1052 +}
270.1053 +
270.1054 +static int mondelefant_conn_query(lua_State *L) {
270.1055 +  int argc;
270.1056 +  argc = lua_gettop(L);
270.1057 +  lua_pushvalue(L, 1);
270.1058 +  lua_insert(L, 1);
270.1059 +  lua_pushcfunction(L, mondelefant_conn_try_query);
270.1060 +  lua_insert(L, 2);
270.1061 +  lua_call(L, argc, LUA_MULTRET);  // results (with error) starting at index 2
270.1062 +  if (lua_toboolean(L, 2)) {
270.1063 +    lua_pushcfunction(L, mondelefant_errorobject_escalate);
270.1064 +    lua_pushvalue(L, 2);
270.1065 +    lua_call(L, 1, 0);  // will raise an error
270.1066 +    return 0;  // should not be executed
270.1067 +  } else {
270.1068 +    return lua_gettop(L) - 2;
270.1069 +  }
270.1070 +}
270.1071 +
270.1072 +static int mondelefant_set_class(lua_State *L) {
270.1073 +  lua_settop(L, 2);
270.1074 +  lua_getmetatable(L, 1);  // 3
270.1075 +  lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_RESULT_MT_REGKEY);  // 4
270.1076 +  luaL_argcheck(L, lua_equal(L, 3, 4), 1, "not a database result");
270.1077 +  lua_settop(L, 2);
270.1078 +  lua_getmetatable(L, 2);  // 3
270.1079 +  lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CLASS_MT_REGKEY);  // 4
270.1080 +  luaL_argcheck(L, lua_equal(L, 3, 4), 2, "not a database class");
270.1081 +  lua_settop(L, 2);
270.1082 +  lua_pushvalue(L, 2);  // 3
270.1083 +  lua_setfield(L, 1, "_class");
270.1084 +  lua_getfield(L, 1, "_type");  // 3
270.1085 +  lua_pushliteral(L, "list");  // 4
270.1086 +  if (lua_rawequal(L, 3, 4)) {
270.1087 +    int i;
270.1088 +    for (i=0; i < lua_objlen(L, 1); i++) {
270.1089 +      lua_settop(L, 2);
270.1090 +      lua_rawgeti(L, 1, i+1);  // 3
270.1091 +      lua_pushvalue(L, 2);  // 4
270.1092 +      lua_setfield(L, 3, "_class");
270.1093 +    }
270.1094 +  }
270.1095 +  lua_settop(L, 1);
270.1096 +  return 1;
270.1097 +}
270.1098 +
270.1099 +static int mondelefant_new_class(lua_State *L) {
270.1100 +  lua_settop(L, 1);
270.1101 +  if (!lua_toboolean(L, 1)) {
270.1102 +    lua_settop(L, 0);
270.1103 +    lua_newtable(L);  // 1
270.1104 +  }
270.1105 +  lua_getfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CLASS_MT_REGKEY);  // 2
270.1106 +  lua_setmetatable(L, 1);
270.1107 +  lua_pushliteral(L, "prototype");  // 2
270.1108 +  lua_rawget(L, 1);  // 2
270.1109 +  if (!lua_toboolean(L, 2)) {
270.1110 +    lua_pushliteral(L, "prototype");  // 3
270.1111 +    lua_getfield(L,
270.1112 +      LUA_REGISTRYINDEX,
270.1113 +      MONDELEFANT_CLASS_PROTO_REGKEY
270.1114 +    );  // 4
270.1115 +    lua_rawset(L, 1);
270.1116 +  }
270.1117 +  lua_settop(L, 1);
270.1118 +  lua_pushliteral(L, "object");  // 2
270.1119 +  lua_rawget(L, 1);  // 2
270.1120 +  if (!lua_toboolean(L, 2)) {
270.1121 +    lua_pushliteral(L, "object");  // 3
270.1122 +    lua_newtable(L);  // 4
270.1123 +    lua_rawset(L, 1);
270.1124 +  }
270.1125 +  lua_settop(L, 1);
270.1126 +  lua_pushliteral(L, "object_get");  // 2
270.1127 +  lua_rawget(L, 1);  // 2
270.1128 +  if (!lua_toboolean(L, 2)) {
270.1129 +    lua_pushliteral(L, "object_get");  // 3
270.1130 +    lua_newtable(L);  // 4
270.1131 +    lua_rawset(L, 1);
270.1132 +  }
270.1133 +  lua_settop(L, 1);
270.1134 +  lua_pushliteral(L, "object_set");  // 2
270.1135 +  lua_rawget(L, 1);  // 2
270.1136 +  if (!lua_toboolean(L, 2)) {
270.1137 +    lua_pushliteral(L, "object_set");  // 3
270.1138 +    lua_newtable(L);  // 4
270.1139 +    lua_rawset(L, 1);
270.1140 +  }
270.1141 +  lua_settop(L, 1);
270.1142 +  lua_pushliteral(L, "list");  // 2
270.1143 +  lua_rawget(L, 1);  // 2
270.1144 +  if (!lua_toboolean(L, 2)) {
270.1145 +    lua_pushliteral(L, "list");  // 3
270.1146 +    lua_newtable(L);  // 4
270.1147 +    lua_rawset(L, 1);
270.1148 +  }
270.1149 +  lua_settop(L, 1);
270.1150 +  lua_pushliteral(L, "references");  // 2
270.1151 +  lua_rawget(L, 1);  // 2
270.1152 +  if (!lua_toboolean(L, 2)) {
270.1153 +    lua_pushliteral(L, "references");  // 3
270.1154 +    lua_newtable(L);  // 4
270.1155 +    lua_rawset(L, 1);
270.1156 +  }
270.1157 +  lua_settop(L, 1);
270.1158 +  lua_pushliteral(L, "foreign_keys");  // 2
270.1159 +  lua_rawget(L, 1);  // 2
270.1160 +  if (!lua_toboolean(L, 2)) {
270.1161 +    lua_pushliteral(L, "foreign_keys");  // 3
270.1162 +    lua_newtable(L);  // 4
270.1163 +    lua_rawset(L, 1);
270.1164 +  }
270.1165 +  lua_settop(L, 1);
270.1166 +  return 1;
270.1167 +}
270.1168 +
270.1169 +static int mondelefant_class_get_reference(lua_State *L) {
270.1170 +  lua_settop(L, 2);
270.1171 +  while (lua_toboolean(L, 1)) {
270.1172 +    lua_getfield(L, 1, "references");  // 3
270.1173 +    lua_pushvalue(L, 2);  // 4
270.1174 +    lua_gettable(L, 3);  // 4
270.1175 +    if (!lua_isnil(L, 4)) return 1;
270.1176 +    lua_settop(L, 2);
270.1177 +    lua_pushliteral(L, "prototype");  // 3
270.1178 +    lua_rawget(L, 1);  // 3
270.1179 +    lua_replace(L, 1);
270.1180 +  }
270.1181 +  return 0;
270.1182 +}
270.1183 +
270.1184 +static int mondelefant_class_iterate_over_references(lua_State *L) {
270.1185 +  return luaL_error(L, "Reference iterator not implemented yet.");  // TODO
270.1186 +}
270.1187 +
270.1188 +static int mondelefant_class_get_foreign_key_reference_name(lua_State *L) {
270.1189 +  lua_settop(L, 2);
270.1190 +  while (lua_toboolean(L, 1)) {
270.1191 +    lua_getfield(L, 1, "foreign_keys");  // 3
270.1192 +    lua_pushvalue(L, 2);  // 4
270.1193 +    lua_gettable(L, 3);  // 4
270.1194 +    if (!lua_isnil(L, 4)) return 1;
270.1195 +    lua_settop(L, 2);
270.1196 +    lua_pushliteral(L, "prototype");  // 3
270.1197 +    lua_rawget(L, 1);  // 3
270.1198 +    lua_replace(L, 1);
270.1199 +  }
270.1200 +  return 0;
270.1201 +}
270.1202 +
270.1203 +static int mondelefant_result_index(lua_State *L) {
270.1204 +  const char *result_type;
270.1205 +  lua_settop(L, 2);
270.1206 +  if (lua_type(L, 2) != LUA_TSTRING || lua_tostring(L, 2)[0] == '_') {
270.1207 +    lua_rawget(L, 1);
270.1208 +    return 1;
270.1209 +  }
270.1210 +  lua_getfield(L, 1, "_class");  // 3
270.1211 +  if (!lua_toboolean(L, 3)) {
270.1212 +    lua_settop(L, 2);
270.1213 +    lua_getfield(L,
270.1214 +      LUA_REGISTRYINDEX,
270.1215 +      MONDELEFANT_CLASS_PROTO_REGKEY
270.1216 +    );  // 3
270.1217 +  }
270.1218 +  lua_getfield(L, 1, "_type");  // 4
270.1219 +  result_type = lua_tostring(L, 4);
270.1220 +  if (result_type && !strcmp(result_type, "object")) {
270.1221 +    lua_settop(L, 3);
270.1222 +    // try inherited attributes, methods or getter functions:
270.1223 +    while (lua_toboolean(L, 3)) {
270.1224 +      lua_getfield(L, 3, "object");  // 4
270.1225 +      lua_pushvalue(L, 2);  // 5
270.1226 +      lua_gettable(L, 4);  // 5
270.1227 +      if (!lua_isnil(L, 5)) return 1;
270.1228 +      lua_settop(L, 3);
270.1229 +      lua_getfield(L, 3, "object_get");  // 4
270.1230 +      lua_pushvalue(L, 2);  // 5
270.1231 +      lua_gettable(L, 4);  // 5
270.1232 +      if (lua_toboolean(L, 5)) {
270.1233 +        lua_pushvalue(L, 1);  // 6
270.1234 +        lua_call(L, 1, 1);  // 5
270.1235 +        return 1;
270.1236 +      }
270.1237 +      lua_settop(L, 3);
270.1238 +      lua_pushliteral(L, "prototype");  // 4
270.1239 +      lua_rawget(L, 3);  // 4
270.1240 +      lua_replace(L, 3);
270.1241 +    }
270.1242 +    lua_settop(L, 2);
270.1243 +    // try primary keys of referenced objects:
270.1244 +    lua_pushcfunction(L,
270.1245 +      mondelefant_class_get_foreign_key_reference_name
270.1246 +    );  // 3
270.1247 +    lua_getfield(L, 1, "_class");  // 4
270.1248 +    lua_pushvalue(L, 2);  // 5
270.1249 +    lua_call(L, 2, 1);  // 3
270.1250 +    if (!lua_isnil(L, 3)) {
270.1251 +      // reference name at stack position 3
270.1252 +      lua_pushcfunction(L, mondelefant_class_get_reference);  // 4
270.1253 +      lua_getfield(L, 1, "_class");  // 5
270.1254 +      lua_pushvalue(L, 3);  // 6
270.1255 +      lua_call(L, 2, 1);  // reference info at stack position 4
270.1256 +      lua_getfield(L, 1, "_ref");  // 5
270.1257 +      lua_getfield(L, 4, "ref");  // 6
270.1258 +      lua_gettable(L, 5);  // 6
270.1259 +      if (!lua_isnil(L, 6)) {
270.1260 +        if (lua_toboolean(L, 6)) {
270.1261 +          lua_getfield(L, 4, "that_key");  // 7
270.1262 +          if (lua_isnil(L, 7)) {
270.1263 +            return luaL_error(L, "Missing 'that_key' entry in model reference.");
270.1264 +          }
270.1265 +          lua_gettable(L, 6);  // 7
270.1266 +        } else {
270.1267 +          lua_pushnil(L);
270.1268 +        }
270.1269 +        return 1;
270.1270 +      }
270.1271 +    }
270.1272 +    lua_settop(L, 2);
270.1273 +    // try normal data field info:
270.1274 +    lua_getfield(L, 1, "_data");  // 3
270.1275 +    lua_pushvalue(L, 2);  // 4
270.1276 +    lua_gettable(L, 3);  // 4
270.1277 +    if (!lua_isnil(L, 4)) return 1;
270.1278 +    lua_settop(L, 2);
270.1279 +    // try cached referenced object (or cached NULL reference):
270.1280 +    lua_getfield(L, 1, "_ref");  // 3
270.1281 +    lua_pushvalue(L, 2);  // 4
270.1282 +    lua_gettable(L, 3);  // 4
270.1283 +    if (lua_isboolean(L, 4) && !lua_toboolean(L, 4)) {
270.1284 +      lua_pushnil(L);
270.1285 +      return 1;
270.1286 +    } else if (!lua_isnil(L, 4)) {
270.1287 +      return 1;
270.1288 +    }
270.1289 +    lua_settop(L, 2);
270.1290 +    // try to load a referenced object:
270.1291 +    lua_pushcfunction(L, mondelefant_class_get_reference);  // 3
270.1292 +    lua_getfield(L, 1, "_class");  // 4
270.1293 +    lua_pushvalue(L, 2);  // 5
270.1294 +    lua_call(L, 2, 1);  // 3
270.1295 +    if (!lua_isnil(L, 3)) {
270.1296 +      lua_settop(L, 2);
270.1297 +      lua_getfield(L, 1, "load");  // 3
270.1298 +      lua_pushvalue(L, 1);  // 4 (self)
270.1299 +      lua_pushvalue(L, 2);  // 5
270.1300 +      lua_call(L, 2, 0);
270.1301 +      lua_settop(L, 2);
270.1302 +      lua_getfield(L, 1, "_ref");  // 3
270.1303 +      lua_pushvalue(L, 2);  // 4
270.1304 +      lua_gettable(L, 3);  // 4
270.1305 +      if (lua_isboolean(L, 4) && !lua_toboolean(L, 4)) lua_pushnil(L);  // TODO: use special object instead of false
270.1306 +      return 1;
270.1307 +    }
270.1308 +    return 0;
270.1309 +  } else if (result_type && !strcmp(result_type, "list")) {
270.1310 +    lua_settop(L, 3);
270.1311 +    // try inherited list attributes or methods:
270.1312 +    while (lua_toboolean(L, 3)) {
270.1313 +      lua_getfield(L, 3, "list");  // 4
270.1314 +      lua_pushvalue(L, 2);  // 5
270.1315 +      lua_gettable(L, 4);  // 5
270.1316 +      if (!lua_isnil(L, 5)) return 1;
270.1317 +      lua_settop(L, 3);
270.1318 +      lua_pushliteral(L, "prototype");  // 4
270.1319 +      lua_rawget(L, 3);  // 4
270.1320 +      lua_replace(L, 3);
270.1321 +    }
270.1322 +  }
270.1323 +  return 0;
270.1324 +}
270.1325 +
270.1326 +static int mondelefant_result_newindex(lua_State *L) {
270.1327 +  const char *result_type;
270.1328 +  lua_settop(L, 3);
270.1329 +  if (lua_type(L, 2) != LUA_TSTRING || lua_tostring(L, 2)[0] == '_') {
270.1330 +    lua_rawset(L, 1);
270.1331 +    return 1;
270.1332 +  }
270.1333 +  lua_getfield(L, 1, "_class");  // 4
270.1334 +  if (!lua_toboolean(L, 4)) {
270.1335 +    lua_settop(L, 3);
270.1336 +    lua_getfield(L,
270.1337 +      LUA_REGISTRYINDEX,
270.1338 +      MONDELEFANT_CLASS_PROTO_REGKEY
270.1339 +    );  // 4
270.1340 +  }
270.1341 +  lua_getfield(L, 1, "_type");  // 5
270.1342 +  result_type = lua_tostring(L, 5);
270.1343 +  if (result_type && !strcmp(result_type, "object")) {
270.1344 +    lua_settop(L, 4);
270.1345 +    // try object setter functions:
270.1346 +    while (lua_toboolean(L, 4)) {
270.1347 +      lua_getfield(L, 4, "object_set");  // 5
270.1348 +      lua_pushvalue(L, 2);  // 6
270.1349 +      lua_gettable(L, 5);  // 6
270.1350 +      if (lua_toboolean(L, 6)) {
270.1351 +        lua_pushvalue(L, 1);  // 7
270.1352 +        lua_pushvalue(L, 3);  // 8
270.1353 +        lua_call(L, 2, 0);
270.1354 +        return 0;
270.1355 +      }
270.1356 +      lua_settop(L, 4);
270.1357 +      lua_pushliteral(L, "prototype");  // 5
270.1358 +      lua_rawget(L, 4);  // 5
270.1359 +      lua_replace(L, 4);
270.1360 +    }
270.1361 +    lua_settop(L, 3);
270.1362 +    // check, if a object reference is changed:
270.1363 +    lua_pushcfunction(L, mondelefant_class_get_reference);  // 4
270.1364 +    lua_getfield(L, 1, "_class");  // 5
270.1365 +    lua_pushvalue(L, 2);  // 6
270.1366 +    lua_call(L, 2, 1);  // 4
270.1367 +    if (!lua_isnil(L, 4)) {
270.1368 +      // store object in _ref table (use false for nil):  // TODO: use special object instead of false
270.1369 +      lua_getfield(L, 1, "_ref");  // 5
270.1370 +      lua_pushvalue(L, 2);  // 6
270.1371 +      if (lua_isnil(L, 3)) lua_pushboolean(L, 0);  // 7
270.1372 +      else lua_pushvalue(L, 3);  // 7
270.1373 +      lua_settable(L, 5);
270.1374 +      lua_settop(L, 4);
270.1375 +      // delete referencing key from _data table:
270.1376 +      lua_getfield(L, 4, "this_key");  // 5
270.1377 +      if (lua_isnil(L, 5)) {
270.1378 +        return luaL_error(L, "Missing 'this_key' entry in model reference.");
270.1379 +      }
270.1380 +      lua_getfield(L, 1, "_data");  // 6
270.1381 +      lua_pushvalue(L, 5);  // 7
270.1382 +      lua_pushnil(L);  // 8
270.1383 +      lua_settable(L, 6);
270.1384 +      lua_settop(L, 5);
270.1385 +      lua_getfield(L, 1, "_dirty");  // 6
270.1386 +      lua_pushvalue(L, 5);  // 7
270.1387 +      lua_pushboolean(L, 1);  // 8
270.1388 +      lua_settable(L, 6);
270.1389 +      return 0;
270.1390 +    }
270.1391 +    lua_settop(L, 3);
270.1392 +    // store value in data field info:
270.1393 +    lua_getfield(L, 1, "_data");  // 4
270.1394 +    lua_pushvalue(L, 2);  // 5
270.1395 +    lua_pushvalue(L, 3);  // 6
270.1396 +    lua_settable(L, 4);
270.1397 +    lua_settop(L, 3);
270.1398 +    // mark field as dirty (needs to be UPDATEd on save):
270.1399 +    lua_getfield(L, 1, "_dirty");  // 4
270.1400 +    lua_pushvalue(L, 2);  // 5
270.1401 +    lua_pushboolean(L, 1);  // 6
270.1402 +    lua_settable(L, 4);
270.1403 +    lua_settop(L, 3);
270.1404 +    // reset reference cache, if neccessary:
270.1405 +    lua_pushcfunction(L,
270.1406 +      mondelefant_class_get_foreign_key_reference_name
270.1407 +    );  // 4
270.1408 +    lua_getfield(L, 1, "_class");  // 5
270.1409 +    lua_pushvalue(L, 2);  // 6
270.1410 +    lua_call(L, 2, 1);  // 4
270.1411 +    if (!lua_isnil(L, 4)) {
270.1412 +      lua_getfield(L, 1, "_ref");  // 5
270.1413 +      lua_pushvalue(L, 4);  // 6
270.1414 +      lua_pushnil(L);  // 7
270.1415 +      lua_settable(L, 5);
270.1416 +    }
270.1417 +    return 0;
270.1418 +  } else {
270.1419 +    lua_settop(L, 3);
270.1420 +    lua_rawset(L, 1);
270.1421 +    return 0;
270.1422 +  }
270.1423 +  return 0;
270.1424 +}
270.1425 +
270.1426 +static int mondelefant_class_index(lua_State *L) {
270.1427 +  lua_settop(L, 2);
270.1428 +  lua_pushliteral(L, "prototype");  // 3
270.1429 +  lua_rawget(L, 1);  // 3
270.1430 +  lua_pushvalue(L, 2);  // 4
270.1431 +  lua_gettable(L, 3);  // 4
270.1432 +  return 1;
270.1433 +}
270.1434 +
270.1435 +static const struct luaL_Reg mondelefant_module_functions[] = {
270.1436 +  {"connect", mondelefant_connect},
270.1437 +  {"set_class", mondelefant_set_class},
270.1438 +  {"new_class", mondelefant_new_class},
270.1439 +  {NULL, NULL}
270.1440 +};
270.1441 +
270.1442 +static const struct luaL_Reg mondelefant_conn_mt_functions[] = {
270.1443 +  {"__gc", mondelefant_conn_free},
270.1444 +  {"__index", mondelefant_conn_index},
270.1445 +  {"__newindex", mondelefant_conn_newindex},
270.1446 +  {NULL, NULL}
270.1447 +};
270.1448 +
270.1449 +static const struct luaL_Reg mondelefant_conn_methods[] = {
270.1450 +  {"close", mondelefant_conn_close},
270.1451 +  {"is_ok", mondelefant_conn_is_ok},
270.1452 +  {"get_transaction_status", mondelefant_conn_get_transaction_status},
270.1453 +  {"create_list", mondelefant_conn_create_list},
270.1454 +  {"create_object", mondelefant_conn_create_object},
270.1455 +  {"quote_string", mondelefant_conn_quote_string},
270.1456 +  {"quote_binary", mondelefant_conn_quote_binary},
270.1457 +  {"assemble_command", mondelefant_conn_assemble_command},
270.1458 +  {"try_query", mondelefant_conn_try_query},
270.1459 +  {"query", mondelefant_conn_query},
270.1460 +  {NULL, NULL}
270.1461 +};
270.1462 +
270.1463 +static const struct luaL_Reg mondelefant_errorobject_mt_functions[] = {
270.1464 +  {NULL, NULL}
270.1465 +};
270.1466 +
270.1467 +static const struct luaL_Reg mondelefant_errorobject_methods[] = {
270.1468 +  {"escalate", mondelefant_errorobject_escalate},
270.1469 +  {"is_kind_of", mondelefant_errorobject_is_kind_of},
270.1470 +  {NULL, NULL}
270.1471 +};
270.1472 +
270.1473 +static const struct luaL_Reg mondelefant_result_mt_functions[] = {
270.1474 +  {"__index", mondelefant_result_index},
270.1475 +  {"__newindex", mondelefant_result_newindex},
270.1476 +  {NULL, NULL}
270.1477 +};
270.1478 +
270.1479 +static const struct luaL_Reg mondelefant_class_mt_functions[] = {
270.1480 +  {"__index", mondelefant_class_index},
270.1481 +  {NULL, NULL}
270.1482 +};
270.1483 +
270.1484 +static const struct luaL_Reg mondelefant_class_methods[] = {
270.1485 +  {"get_reference", mondelefant_class_get_reference},
270.1486 +  {"iterate_over_references", mondelefant_class_iterate_over_references},
270.1487 +  {"get_foreign_key_reference_name",
270.1488 +    mondelefant_class_get_foreign_key_reference_name},
270.1489 +  {NULL, NULL}
270.1490 +};
270.1491 +
270.1492 +static const struct luaL_Reg mondelefant_object_methods[] = {
270.1493 +  {NULL, NULL}
270.1494 +};
270.1495 +
270.1496 +static const struct luaL_Reg mondelefant_list_methods[] = {
270.1497 +  {NULL, NULL}
270.1498 +};
270.1499 +
270.1500 +int luaopen_mondelefant_native(lua_State *L) {
270.1501 +  lua_settop(L, 0);
270.1502 +  lua_newtable(L);  // module at stack position 1
270.1503 +  luaL_register(L, NULL, mondelefant_module_functions);
270.1504 +
270.1505 +  lua_pushvalue(L, 1);  // 2
270.1506 +  lua_setfield(L, LUA_REGISTRYINDEX, MONDELEFANT_MODULE_REGKEY);
270.1507 +
270.1508 +  lua_newtable(L);  // 2
270.1509 +  // NOTE: only PostgreSQL is supported yet:
270.1510 +  luaL_register(L, NULL, mondelefant_conn_methods);
270.1511 +  lua_setfield(L, 1, "postgresql_connection_prototype");
270.1512 +  lua_newtable(L);  // 2
270.1513 +  lua_setfield(L, 1, "connection_prototype");
270.1514 +
270.1515 +  luaL_newmetatable(L, MONDELEFANT_CONN_MT_REGKEY);  // 2
270.1516 +  luaL_register(L, NULL, mondelefant_conn_mt_functions);
270.1517 +  lua_settop(L, 1);
270.1518 +  luaL_newmetatable(L, MONDELEFANT_RESULT_MT_REGKEY);  // 2
270.1519 +  luaL_register(L, NULL, mondelefant_result_mt_functions);
270.1520 +  lua_setfield(L, 1, "result_metatable");
270.1521 +  luaL_newmetatable(L, MONDELEFANT_CLASS_MT_REGKEY);  // 2
270.1522 +  luaL_register(L, NULL, mondelefant_class_mt_functions);
270.1523 +  lua_setfield(L, 1, "class_metatable");
270.1524 +
270.1525 +  lua_newtable(L);  // 2
270.1526 +  lua_newtable(L);  // 3
270.1527 +  luaL_register(L, NULL, mondelefant_object_methods);
270.1528 +  lua_setfield(L, 2, "object");
270.1529 +  lua_newtable(L);  // 3
270.1530 +  lua_setfield(L, 2, "object_get");
270.1531 +  lua_newtable(L);  // 3
270.1532 +  lua_setfield(L, 2, "object_set");
270.1533 +  lua_newtable(L);  // 3
270.1534 +  luaL_register(L, NULL, mondelefant_list_methods);
270.1535 +  lua_setfield(L, 2, "list");
270.1536 +  lua_newtable(L);  // 3
270.1537 +  lua_setfield(L, 2, "references");
270.1538 +  lua_newtable(L);  // 3
270.1539 +  lua_setfield(L, 2, "foreign_keys");
270.1540 +  lua_pushvalue(L, 2);  // 3
270.1541 +  lua_setfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CLASS_PROTO_REGKEY);
270.1542 +  lua_setfield(L, 1, "class_prototype");
270.1543 +
270.1544 +  lua_newtable(L);  // 2
270.1545 +  lua_pushliteral(L, "k");  // 3
270.1546 +  lua_setfield(L, 2, "__mode");
270.1547 +  lua_newtable(L);  // 3
270.1548 +  lua_pushvalue(L, 2);  // 4
270.1549 +  lua_setmetatable(L, 3);
270.1550 +  lua_setfield(L, LUA_REGISTRYINDEX, MONDELEFANT_CONN_DATA_REGKEY);
270.1551 +  lua_settop(L, 1);
270.1552 +
270.1553 +  luaL_newmetatable(L, MONDELEFANT_ERROROBJECT_MT_REGKEY);  // 2
270.1554 +  luaL_register(L, NULL, mondelefant_errorobject_mt_functions);
270.1555 +  lua_newtable(L);  // 3
270.1556 +  luaL_register(L, NULL, mondelefant_errorobject_methods);
270.1557 +  lua_setfield(L, 2, "__index");
270.1558 +  lua_setfield(L, 1, "errorobject_metatable");
270.1559 +
270.1560 +  return 1;
270.1561 +}
   271.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   271.2 +++ b/libraries/multirand/Makefile	Sun Oct 25 12:00:00 2009 +0100
   271.3 @@ -0,0 +1,10 @@
   271.4 +include ../../Makefile.options
   271.5 +
   271.6 +multirand.so: multirand.o
   271.7 +	$(LD) $(LDFLAGS) -o multirand.$(SLIB_EXT) multirand.o
   271.8 +
   271.9 +multirand.o: multirand.c
  271.10 +	$(CC) -c $(CFLAGS) -o multirand.o multirand.c
  271.11 +
  271.12 +clean::
  271.13 +	rm -f multirand.so multirand.o
   272.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   272.2 +++ b/libraries/multirand/multirand.c	Sun Oct 25 12:00:00 2009 +0100
   272.3 @@ -0,0 +1,124 @@
   272.4 +#include <lua.h>
   272.5 +#include <lauxlib.h>
   272.6 +#include <dirent.h>
   272.7 +
   272.8 +static FILE *multirand_dev;
   272.9 +
  272.10 +static lua_Integer multirand_range(
  272.11 +  lua_State *L, lua_Integer from, lua_Integer to
  272.12 +) {
  272.13 +  lua_Integer range;
  272.14 +  int bits = 0;
  272.15 +  lua_Integer bit_mask = 0;
  272.16 +  if (to < from) return luaL_error(L, "Assertion failed in C.");
  272.17 +  range = to - from;
  272.18 +  {
  272.19 +    lua_Integer tmp;
  272.20 +    tmp = range;
  272.21 +    while (tmp) {
  272.22 +      bits++;
  272.23 +      bit_mask <<= 1;
  272.24 +      bit_mask |= 1;
  272.25 +      tmp >>= 1;
  272.26 +    }
  272.27 +  }
  272.28 +  while (1) {
  272.29 +    int i;
  272.30 +    lua_Integer rnd = 0;
  272.31 +    for (i = 0; i < (bits + 7) / 8; i++) {
  272.32 +      int b;
  272.33 +      b = getc(multirand_dev);
  272.34 +      if (b == EOF) {
  272.35 +        return luaL_error(L, "I/O error while reading random.");
  272.36 +      }
  272.37 +      rnd = (rnd << 8) | (unsigned char)b;
  272.38 +    }
  272.39 +    rnd &= bit_mask;
  272.40 +    if (rnd <= range) return from + rnd;
  272.41 +  }
  272.42 +}
  272.43 +
  272.44 +static int multirand_integer(lua_State *L) {
  272.45 +  lua_Integer arg1, arg2;
  272.46 +  lua_settop(L, 2);
  272.47 +  arg1 = luaL_checkinteger(L, 1);
  272.48 +  if (lua_toboolean(L, 2)) {
  272.49 +    arg2 = luaL_optinteger(L, 2, 0);
  272.50 +    if (arg1 > arg2) {
  272.51 +      return luaL_error(L,
  272.52 +        "Upper boundary is smaller than lower boundary."
  272.53 +      );
  272.54 +    } else if (arg1 == arg2) {
  272.55 +      lua_pushinteger(L, arg1);
  272.56 +    } else {
  272.57 +      lua_pushinteger(L, multirand_range(L, arg1, arg2));
  272.58 +    }
  272.59 +  } else {
  272.60 +    luaL_argcheck(L, arg1 >= 1, 1, "smaller than 1");
  272.61 +    lua_pushinteger(L, multirand_range(L, 1, arg1));
  272.62 +  }
  272.63 +  return 1;
  272.64 +}
  272.65 +
  272.66 +static int multirand_fraction(lua_State *L) {
  272.67 +  int i, j;
  272.68 +  lua_settop(L, 0);
  272.69 +  lua_Number rnd = 0.0;
  272.70 +  for (i = 0; i < 8; i++) {
  272.71 +    int b;
  272.72 +    unsigned char c;
  272.73 +    b = getc(multirand_dev);
  272.74 +    if (b == EOF) return luaL_error(L, "I/O error while reading random.");
  272.75 +    c = (unsigned char) b;
  272.76 +    for (j = 0; j < 8; j++) {
  272.77 +      rnd /= 2.0;
  272.78 +      if (c & 1) rnd += 0.5;
  272.79 +      c >>= 1;
  272.80 +    }
  272.81 +  }
  272.82 +  lua_pushnumber(L, rnd);
  272.83 +  return 1;
  272.84 +}
  272.85 +
  272.86 +static int multirand_string(lua_State *L) {
  272.87 +  int length;
  272.88 +  const char *charset;
  272.89 +  size_t charset_size;
  272.90 +  luaL_Buffer buf;
  272.91 +  lua_settop(L, 2);
  272.92 +  length = luaL_checkint(L, 1);
  272.93 +  charset = luaL_optlstring(L, 2, "abcdefghijklmnopqrstuvwxyz", &charset_size);
  272.94 +  if (charset_size > 32767) {
  272.95 +    return luaL_error(L, "Set of chars is too big.");
  272.96 +  }
  272.97 +  luaL_buffinit(L, &buf);
  272.98 +  for (; length > 0; length--) {
  272.99 +    luaL_addchar(&buf,
 272.100 +      charset[multirand_range(L, 0, (lua_Integer)charset_size - 1)]
 272.101 +    );
 272.102 +  }
 272.103 +  luaL_pushresult(&buf);
 272.104 +  return 1;
 272.105 +}
 272.106 +
 272.107 +static const struct luaL_Reg multirand_module_functions[] = {
 272.108 +  {"integer",  multirand_integer},
 272.109 +  {"fraction", multirand_fraction},
 272.110 +  {"string",   multirand_string},
 272.111 +  {NULL, NULL}
 272.112 +};
 272.113 +
 272.114 +int luaopen_multirand(lua_State *L) {
 272.115 +  const char *module_name;
 272.116 +  lua_settop(L, 1);
 272.117 +  module_name = lua_tostring(L, 1);
 272.118 +  if (module_name) {
 272.119 +    luaL_register(L, module_name, multirand_module_functions);
 272.120 +  } else {
 272.121 +    luaL_register(L, "multirand", multirand_module_functions);
 272.122 +  }
 272.123 +  lua_replace(L, 1);
 272.124 +  multirand_dev = fopen("/dev/urandom", "r");
 272.125 +  if (!multirand_dev) return luaL_error(L, "Could not open /dev/urandom.");
 272.126 +  return 1;
 272.127 +}
   273.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   273.2 +++ b/libraries/nihil/nihil.lua	Sun Oct 25 12:00:00 2009 +0100
   273.3 @@ -0,0 +1,52 @@
   273.4 +#!/usr/bin/env lua
   273.5 +
   273.6 +local error          = error
   273.7 +local getmetatable   = getmetatable
   273.8 +local module         = module
   273.9 +local rawset         = rawset
  273.10 +local setmetatable   = setmetatable
  273.11 +
  273.12 +module(...)
  273.13 +
  273.14 +metatable = {
  273.15 +  __tostring = function(self)
  273.16 +    return "nil" .. self[1]
  273.17 +  end,
  273.18 +  __newindex = function()
  273.19 +    error("Objects representing nil are immutable.")
  273.20 +  end
  273.21 +}
  273.22 +
  273.23 +nils = setmetatable({}, {
  273.24 +  __mode = "v",
  273.25 +  __index = function(self, level)
  273.26 +    if level > 0 then
  273.27 +      local result = setmetatable({ level }, metatable)
  273.28 +      rawset(self, level, result)
  273.29 +      return result
  273.30 +    end
  273.31 +  end,
  273.32 +  __newindex = function()
  273.33 +    error("Table is immutable.")
  273.34 +  end
  273.35 +})
  273.36 +
  273.37 +function lift(value)
  273.38 +  if value == nil then
  273.39 +    return nils[1]
  273.40 +  elseif getmetatable(value) == metatable then
  273.41 +    return nils[value[1]+1]
  273.42 +  else
  273.43 +    return value
  273.44 +  end
  273.45 +end
  273.46 +
  273.47 +function lower(value)
  273.48 +  if value == nil then
  273.49 +    error("Cannot lower nil.")
  273.50 +  elseif getmetatable(value) == metatable then
  273.51 +    return nils[value[1]-1]
  273.52 +  else
  273.53 +    return value
  273.54 +  end
  273.55 +end
   274.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   274.2 +++ b/libraries/rocketcgi/rocketcgi.lua	Sun Oct 25 12:00:00 2009 +0100
   274.3 @@ -0,0 +1,219 @@
   274.4 +#!/usr/bin/env lua
   274.5 +
   274.6 +local assert       = assert
   274.7 +local error        = error
   274.8 +local getfenv      = getfenv
   274.9 +local getmetatable = getmetatable
  274.10 +local ipairs       = ipairs
  274.11 +local next         = next
  274.12 +local pairs        = pairs
  274.13 +local pcall        = pcall
  274.14 +local print        = print
  274.15 +local rawequal     = rawequal
  274.16 +local rawget       = rawget
  274.17 +local rawset       = rawset
  274.18 +local select       = select
  274.19 +local setfenv      = setfenv
  274.20 +local setmetatable = setmetatable
  274.21 +local tonumber     = tonumber
  274.22 +local tostring     = tostring
  274.23 +local type         = type
  274.24 +local unpack       = unpack
  274.25 +local xpcall       = xpcall
  274.26 +
  274.27 +local io     = io
  274.28 +local math   = math
  274.29 +local os     = os
  274.30 +local string = string
  274.31 +local table  = table
  274.32 +
  274.33 +module(...)
  274.34 +
  274.35 +data_sent = false
  274.36 +
  274.37 +function add_header(...)
  274.38 +  if data_sent then
  274.39 +    error("Can not add header after data has been sent.", 2)
  274.40 +  end
  274.41 +  io.stdout:write(...)
  274.42 +  io.stdout:write("\r\n")
  274.43 +end
  274.44 +
  274.45 +function send_data(...)
  274.46 +  if not data_sent then
  274.47 +    io.stdout:write("\r\n")
  274.48 +    data_sent = true
  274.49 +  end
  274.50 +  io.stdout:write(...)
  274.51 +end
  274.52 +
  274.53 +function set_status(status)
  274.54 +  add_header("Status: ", status)
  274.55 +end
  274.56 +
  274.57 +function redirect(location)
  274.58 +  set_status("303 See Other")
  274.59 +  add_header("Location: ", location)
  274.60 +end
  274.61 +
  274.62 +function set_content_type(content_type)
  274.63 +  add_header("Content-Type: ", content_type)
  274.64 +end
  274.65 +
  274.66 +method = os.getenv("REQUEST_METHOD") or false
  274.67 +query = os.getenv("QUERY_STRING") or false
  274.68 +cookie_data = os.getenv("HTTP_COOKIE") or false
  274.69 +post_data = io.stdin:read("*a") or false
  274.70 +post_contenttype = os.getenv("CONTENT_TYPE") or false
  274.71 +params = {}
  274.72 +get_params = {}
  274.73 +cookies = {}
  274.74 +post_params = {}
  274.75 +post_filenames = {}
  274.76 +post_types = {}
  274.77 +
  274.78 +local urldecode
  274.79 +do
  274.80 +  local b0 = string.byte("0")
  274.81 +  local b9 = string.byte("9")
  274.82 +  local bA = string.byte("A")
  274.83 +  local bF = string.byte("F")
  274.84 +  local ba = string.byte("a")
  274.85 +  local bf = string.byte("f")
  274.86 +  function urldecode(str)
  274.87 +    return (
  274.88 +      string.gsub(
  274.89 +        string.gsub(str, "%+", " "),
  274.90 +        "%%([0-9A-Fa-f][0-9A-Fa-f])",
  274.91 +        function(hex)
  274.92 +          local n1, n2 = string.byte(hex, 1, 2)
  274.93 +          if n1 >= b0 and n1 <= b9 then n1 = n1 - b0
  274.94 +          elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10
  274.95 +          elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10
  274.96 +          else return end
  274.97 +          if n2 >= b0 and n2 <= b9 then n2 = n2 - b0
  274.98 +          elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10
  274.99 +          elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10
 274.100 +          else return end
 274.101 +          return string.char(n1 * 16 + n2)
 274.102 +        end
 274.103 +      )
 274.104 +    )
 274.105 +  end
 274.106 +end
 274.107 +
 274.108 +local function proc_param(tbl, key, value)
 274.109 +  if string.find(key, "%[%]$") then
 274.110 +    if tbl[key] then
 274.111 +      table.insert(tbl[key], value)
 274.112 +    else
 274.113 +      local list = { value }
 274.114 +      params[key] = list
 274.115 +      tbl[key] = list
 274.116 +    end
 274.117 +  else
 274.118 +    params[key] = value
 274.119 +    tbl[key] = value
 274.120 +  end
 274.121 +end
 274.122 +
 274.123 +local function read_urlencoded_form(tbl, data)
 274.124 +  for rawkey, rawvalue in string.gmatch(data, "([^=&]*)=([^=&]*)") do
 274.125 +    proc_param(tbl, urldecode(rawkey), urldecode(rawvalue))
 274.126 +  end
 274.127 +end
 274.128 +
 274.129 +if query then
 274.130 +  read_urlencoded_form(get_params, query)
 274.131 +end
 274.132 +
 274.133 +if cookie_data then
 274.134 +  for rawkey, rawvalue in string.gmatch(cookie_data, "([^=; ]*)=([^=; ]*)") do
 274.135 +    cookies[urldecode(rawkey)] = urldecode(rawvalue)
 274.136 +  end
 274.137 +end
 274.138 +
 274.139 +if post_contenttype == "application/x-www-form-urlencoded" then
 274.140 +  read_urlencoded_form(post_params, post_data)
 274.141 +elseif post_contenttype then
 274.142 +  local boundary = string.match(
 274.143 +    post_contenttype,
 274.144 +    '^multipart/form%-data[ \t]*;[ \t]*boundary="([^"]+)"'
 274.145 +  ) or string.match(
 274.146 +      post_contenttype,
 274.147 +      '^multipart/form%-data[ \t]*;[ \t]*boundary=([^"; \t]+)'
 274.148 +  )
 274.149 +  if boundary then
 274.150 +    local parts = {}
 274.151 +    do
 274.152 +      local boundary = "\r\n--" .. boundary
 274.153 +      local post_data = "\r\n" .. post_data
 274.154 +      local pos1, pos2 = string.find(post_data, boundary, 1, true)
 274.155 +      while true do
 274.156 +        local ind = string.sub(post_data, pos2 + 1, pos2 + 2)
 274.157 +        if ind == "\r\n" then
 274.158 +          local pos3, pos4 = string.find(post_data, boundary, pos2 + 1, true)
 274.159 +          if pos3 then
 274.160 +            parts[#parts + 1] = string.sub(post_data, pos2 + 3, pos3 - 1)
 274.161 +          else
 274.162 +            error("Illegal POST data.")
 274.163 +          end
 274.164 +          pos1, pos2 = pos3, pos4
 274.165 +        elseif ind == "--" then
 274.166 +          break
 274.167 +        else
 274.168 +          error("Illegal POST data.")
 274.169 +        end
 274.170 +      end
 274.171 +    end
 274.172 +    for i, part in ipairs(parts) do
 274.173 +      local pos = 1
 274.174 +      local name, filename, contenttype
 274.175 +      while true do
 274.176 +        local header
 274.177 +        do
 274.178 +          local oldpos = pos
 274.179 +          pos = string.find(part, "\r\n", oldpos, true)
 274.180 +          if not pos then
 274.181 +            error("Illegal POST data.")
 274.182 +          end
 274.183 +          if pos == oldpos then break end
 274.184 +          header = string.sub(part, oldpos, pos - 1)
 274.185 +          pos = pos + 2
 274.186 +        end
 274.187 +        if string.find(
 274.188 +          string.lower(header),
 274.189 +          "^content%-disposition:[ \t]*form%-data[ \t]*;"
 274.190 +        ) then
 274.191 +          -- TODO: handle all cases correctly
 274.192 +          name = string.match(header, ';[ \t]*name="([^"]*)"') or
 274.193 +            string.match(header, ';[ \t]*name=([^"; \t]+)')
 274.194 +          filename = string.match(header, ';[ \t]*filename="([^"]*)"') or
 274.195 +            string.match(header, ';[ \t]*filename=([^"; \t]+)')
 274.196 +        else
 274.197 +          local dummy, subpos = string.find(
 274.198 +            string.lower(header),
 274.199 +            "^content%-type:[ \t]*"
 274.200 +          )
 274.201 +          if subpos then
 274.202 +            contenttype = string.sub(header, subpos + 1, #header)
 274.203 +          end
 274.204 +        end
 274.205 +      end
 274.206 +      local content = string.sub(part, pos + 2, #part)
 274.207 +      if not name then
 274.208 +        error("Illegal POST data.")
 274.209 +      end
 274.210 +      proc_param(post_params, name, content)
 274.211 +      post_filenames[name] = filename
 274.212 +      post_types[name] = contenttype
 274.213 +    end
 274.214 +  end
 274.215 +end
 274.216 +
 274.217 +if post_data and #post_data > 262144 then
 274.218 +  post_data = nil
 274.219 +  collectgarbage("collect")
 274.220 +else
 274.221 +  post_data = nil
 274.222 +end

Impressum / About Us