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.
WebMCP has been developed on Linux and FreeBSD. Using it with Mac OS X is completely untested; Microsoft Windows is not supported. Beside the operating system, the only mandatory dependencies for WebMCP are the programming language Lua version 5.1, PostgreSQL version 8.2 or higher, a C compiler, and a Webserver like Lighttpd or Apache.
After downloading the tar.gz package, unpack it, enter the unpacked directory and type make. If you use Mac OS X or if you experience problems during compilation, you need to edit the Makefile.options file prior to compilation. The framework itself will be available in the framework/ directory, while a demo application is available in the demo-app/ directory. The framework.precompiled/ and demo-app.precompiled/ 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 cp -L to follow links) to any other place you like. Use the files doc/lighttpd.sample.conf or doc/apache.sample.conf to setup your webserver appropriatly. Don't forget to setup a database, and make the tmp/ directory of the application writable for the web server process. Good luck and have fun!
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:
In addition the following pseudo-types are existent, corresponding to Lua's base types:
Both atom.integer and atom.number refer to Lua's base type “number”.
New values of atom data types are created by either calling atom.type:load(string_representation) or by calling atom.type{...}, e.g. atom.date{year=1970, month=1, day=1}. You can dump any atom value as a string by calling atom.dump(value) and later reload it with atom.type:load(string).
The library “mondelefant” 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:
db = assert( mondelefant.connect{ engine='postgresql', dbname='webmcp_demo' } )
at_exit(function() 
  db:close()
end)
function mondelefant.class_prototype:get_db_conn() return db end
function db:sql_tracer(command)
  return function(error_info)
    local error_info = error_info or {}
    trace.sql{ command = command, error_position = error_info.position }
  end
end
    Overwriting the sql_tracer method of the database handle is optional, but helpful for debugging. The parameters for mondelefant.connect are directly passed to PostgreSQL's client library libpq. See PostgreSQL's documentation on PQconnect for information about supported parameters.
To define a model to be used within a WebMCP application, create a file named with the name of the model and .lua as extension in the model/ directory of your application. The most basic definition of a model (named “movie” in this example) is:
Movie = mondelefant.new_class() Movie.table = 'movie'
Note: Model classes are always written CamelCase, while the name of the file in model/ is written lower_case.
To select objects from the database, the mondelefant library provides a selector framework:
local s = Movie:new_selector()
s:add_where{ 'id = ?', param.get_id() }
s:single_object_mode()  -- return single object instead of list
local movie = s:exec()
    A short form of the above query would be:
local movie = Movie:new_selector():add_where{ 'id = ?', param.get_id() }:single_object_mode():exec()
    For more examples about how to use the model system, please take a look at the demo application.
As opposed to other web application frameworks, WebMCP does not use a Model-View-Controller (MVC) concept, but a Model-View-Action (MVA) concept.
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.
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.
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.
<db_class>.primary_key
class_prototype.primary_key = "id"
db_class = -- same class returned <db_class>:add_reference{ mode = mode, -- "11", "1m", "m1", or "mm" (one/many to one/many) to = to, -- referenced class (model), optionally as string or function returning the value (avoids autoload) this_key = this_key, -- name of key in this class (model) that_key = that_key, -- name of key in the other class (model) ("to" argument) ref = ref, -- name of reference in this class, referring to the other class back_ref = back_ref, -- name of reference in other class, referring to this class default_order = default_order, -- expression as passed to "assemble_command" used for sorting selector_generator = selector_generator, -- alternative function used as selector generator (use only, when you know what you are doing) connected_by_table = connected_by_table, -- connecting table used for many to many relations connected_by_this_key = connected_by_this_key, -- key in connecting table referring to "this_key" of this class (model) connected_by_that_key = connected_by_that_key -- key in connecting table referring to "that_key" in other class (model) ("to" argument) }
function class_prototype:add_reference(args) local selector_generator = args.selector_generator local mode = args.mode local to = args.to local this_key = args.this_key local that_key = args.that_key local connected_by_table = args.connected_by_table -- TODO: split to table and schema local connected_by_this_key = args.connected_by_this_key local connected_by_that_key = args.connected_by_that_key local ref = args.ref local back_ref = args.back_ref local default_order = args.default_order local model local function get_model() if not model then if type(to) == "string" then model = _G for path_element in string.gmatch(to, "[^.]+") do model = model[path_element] end elseif type(to) == "function" then model = to() else model = to end end if not model or model == _G then error("Could not get model for reference.") end return model end self.references[ref] = { mode = mode, this_key = this_key, that_key = connected_by_table and "mm_ref_" or that_key, ref = ref, back_ref = back_ref, selector_generator = selector_generator or function(list, options) -- TODO: support tuple keys local options = options or {} local model = get_model() -- TODO: too many records cause PostgreSQL command stack overflow local ids = { sep = ", " } for i, object in ipairs(list) do local id = object[this_key] if id ~= nil then ids[#ids+1] = {"?", id} end end if #ids == 0 then return model:new_selector():empty_list_mode() end local selector = model:new_selector() if connected_by_table then selector:join( connected_by_table, nil, { '$."$" = $."$"', {connected_by_table}, {connected_by_that_key}, {model:get_qualified_table()}, {that_key} } ) selector:add_field( { '$."$"', {connected_by_table}, {connected_by_this_key} }, 'mm_ref_' ) selector:add_where{ '$."$" IN ($)', {connected_by_table}, {connected_by_this_key}, ids } else selector:add_where{'$."$" IN ($)', {model:get_qualified_table()}, {that_key}, ids} end if options.order == nil and default_order then selector:add_order_by(default_order) elseif options.order then selector:add_order_by(options.order) end return selector end } if mode == "m1" or mode == "11" then self.foreign_keys[this_key] = ref end return self end
db_list =                 -- database result being an empty list
<db_class>:create_list()
    function class_prototype:create_list() local list = self:get_db_conn():create_list() list._class = self return list end
columns =                 -- list of columns
<db_class>:get_columns()
    function class_prototype:get_columns()
  if self._columns then
    return self._columns
  end
  local selector = self:get_db_conn():new_selector()
  selector:set_class(self)
  selector:from(self:get_qualified_table())
  selector:add_field("*")
  selector:add_where("FALSE")
  local db_result = selector:exec()
  local connection = db_result._connection
  local columns = {}
  for idx, info in ipairs(db_result._column_info) do
    local key   = info.field_name
    local value = {
      name = key,
      type = connection.type_mappings[info.type]
    }
    columns[key] = value
    table.insert(columns, value)
  end
  self._columns = columns
  return columns
end
    db_handle =               -- database connection handle used by this class
<db_class>:get_db_conn()
    function class_prototype:get_db_conn()
  error(
    "Method mondelefant class(_prototype):get_db_conn() " ..
    "has to be implemented."
  )
end
    reference_name = -- reference name <db_class>:get_foreign_key_reference_name( foreign_key -- foreign key )
-- implemented in mondelefant_native.c as -- static int mondelefant_class_get_reference(lua_State *L)
list =                             -- list of column names of primary key
<db_class>:get_primary_key_list()
    function class_prototype:get_primary_key_list()
  local primary_key = self.primary_key
  if type(primary_key) == "string" then
    return {primary_key}
  else
    return primary_key
  end
end
    string =                          -- string of form '"schemaname"."tablename"' or '"tablename"'
<db_class>:get_qualified_table()
    function class_prototype:get_qualified_table()
  if not self.table then error "Table unknown." end
  if self.schema then
    return '"' .. self.schema .. '"."' .. self.table .. '"'
  else
    return '"' .. self.table .. '"'
  end
end
--]]--
--[[--
string =                                  -- single quoted string of form "'schemaname.tablename'" or "'tablename'"
<db_class>:get_qualified_table_literal()
This method returns a string with an SQL literal representing the given table. It causes ambiguities when the table name contains a dot (".") character.
--]]--
function class_prototype:get_qualified_table_literal()
  if not self.table then error "Table unknown." end
  if self.schema then
    return self.schema .. '.' .. self.table
  else
    return self.table
  end
end
    reference_data = -- table with reference information <db_class>:get_reference( name -- reference name )
-- implemented in mondelefant_native.c as -- static int mondelefant_class_get_reference(lua_State *L)
db_object =       -- database object (instance of model)
<db_class>:new()
    function class_prototype:new() local object = self:get_db_conn():create_object() object._class = self object._new = true return object end
selector = -- new selector for selecting objects of this class <db_class>:new_selector( db_conn -- optional(!) database connection handle, defaults to result of :get_db_conn() )
function class_prototype:new_selector(db_conn) local selector = (db_conn or self:get_db_conn()):new_selector() selector:set_class(self) selector:from(self:get_qualified_table()) selector:add_field(self:get_qualified_table() .. ".*") return selector end
<db_error>:escalate()
-- implemented in mondelefant_native.c as -- static int mondelefant_errorobject_escalate(lua_State *L)
bool = -- true or false <db_error>:is_kind_of( error_code -- error code as used by this library )
-- implemented in mondelefant_native.c as -- static int mondelefant_errorobject_is_kind_of(lua_State *L)
sql_string =
<db_handle>:assemble_command{
  template,                    -- template string
  arg1,                        -- value to be inserted
  arg2,                        -- another value to be inserted
  key1 = named_arg3,           -- named value
  key2 = named_arg4,           -- another named value
  ...
}
    -- implemented in mondelefant_native.c as -- static int mondelefant_conn_assemble_command(lua_State *L)
<db_handle>:close()
-- implemented in mondelefant_native.c as -- static int mondelefant_conn_close(lua_State *L)
db_list =                  -- database result being an empty list
<db_handle>:create_list()
    -- implemented in mondelefant_native.c as -- static int mondelefant_conn_create_list(lua_State *L)
db_object =                  -- database result being an empty object (row)
<db_handle>:create_object()
    -- implemented in mondelefant_native.c as -- static int mondelefant_conn_create_object(lua_State *L)
status =                              -- status string
<db_handle>:get_transaction_status()
    -- implemented in mondelefant_native.c as -- static int mondelefant_conn_get_transaction_status(lua_State *L)
status =             -- true, if database connection has no malfunction
<db_handle>:is_ok()
    -- implemented in mondelefant_native.c as -- static int mondelefant_conn_is_ok(lua_State *L)
selector =                  -- new selector
<db_handle>:new_selector()
    function connection_prototype:new_selector()
  return init_selector(setmetatable({}, selector_metatable), self)
end
    quoted_encoded_data = -- encoded and quoted data (as Lua string) <db_handle>:quote_string( raw_data -- data (as Lua string) to encode and quote )
-- implemented in mondelefant_native.c as -- static int mondelefant_conn_quote_binary(lua_State *L)
quoted_encoded_string = -- encoded and quoted string <db_handle>:quote_string( unencoded_string -- string to encode and quote )
-- implemented in mondelefant_native.c as -- static int mondelefant_conn_quote_string(lua_State *L)
db_selector = <db_list>:get_reference_selector( ref_name, -- name of reference (e.g. "children") options, -- table options passed to the reference loader (e.g. { order = ... }) ref_alias, -- optional alias for the reference (e.g. "ordered_children") back_ref_alias -- back reference name (e.g. "parent") )
function class_prototype.list:get_reference_selector(
  ref_name, options, ref_alias, back_ref_alias
)
  local ref_info = self._class.references[ref_name]
  if not ref_info then
    error('Reference with name "' .. ref_name .. '" not found.')
  end
  local selector = ref_info.selector_generator(self, options or {})
  local mode = ref_info.mode
  if mode == "mm" or mode == "1m" then
    mode = "m1"
  elseif mode == "m1" then
    mode = "1m"
  end
  local ref_alias = ref_alias
  if ref_alias == false then
    ref_alias = nil
  elseif ref_alias == nil then
    ref_alias = ref_name
  end
  local back_ref_alias
  if back_ref_alias == false then
    back_ref_alias = nil
  elseif back_ref_alias == nil then
    back_ref_alias = ref_info.back_ref
  end
  selector:attach(
    mode,
    self,
    ref_info.that_key,                   ref_info.this_key,
    back_ref_alias or ref_info.back_ref, ref_alias or ref_name
  )
  return selector
end
    db_list_or_object = <db_list>:load( ref_name, -- name of reference (e.g. "children") options, -- table options passed to the reference loader (e.g. { order = ... }) ref_alias, -- optional alias for the reference (e.g. "ordered_children") back_ref_alias -- back reference name (e.g. "parent") )
function class_prototype.list.load(...) return class_prototype.list.get_reference_selector(...):exec() end
<db_object>:destroy()
function class_prototype.object:destroy()
  local db_error = self:try_destroy()
  if db_error then
    db_error:escalate()
  end
  return self
end
    db_object = <db_object>:get_reference_selector( ref_name, -- name of reference (e.g. "children") options, -- table options passed to the reference loader (e.g. { order = ... }) ref_alias, -- optional alias for the reference (e.g. "ordered_children") back_ref_alias -- back reference name (e.g. "parent") )
function class_prototype.object:get_reference_selector(...) local list = self._class:create_list() list[1] = self return list:get_reference_selector(...) end
db_list_or_object = <db_object>:load( ref_name, -- name of reference (e.g. "children") options, -- table options passed to the reference loader (e.g. { order = ... }) ref_alias, -- optional alias for the reference (e.g. "ordered_children") back_ref_alias -- back reference name (e.g. "parent") )
function class_prototype.object.load(...) return class_prototype.object.get_reference_selector(...):exec() end
<db_object>:save()
function class_prototype.object:save()
  local db_error = self:try_save()
  if db_error then
    db_error:escalate()
  end
  return self
end
    db_error =                 -- database error object, or nil in case of success
<db_object>:try_destroy()
    function class_prototype.object:try_destroy()
  if not self._class then
    error("Cannot destroy object: No class information available.")
  end
  local primary_key = self._class:get_primary_key_list()
  local primary_key_compare = {sep = " AND "}
  for idx, value in ipairs(primary_key) do
    primary_key_compare[idx] = {
      "$ = ?",
      {'"' .. value .. '"'},
      self[value]
    }
  end
  return self._connection:try_query{
    'DELETE FROM $ WHERE $',
    {self._class:get_qualified_table()},
    primary_key_compare
  }
end
    db_error =              -- database error object, or nil in case of success
<db_object>:try_save()
    function class_prototype.object:try_save()
  if not self._class then
    error("Cannot save object: No class information available.")
  end
  local primary_key = self._class:get_primary_key_list()
  local primary_key_sql = { sep = ", " }
  for idx, value in ipairs(primary_key) do
    primary_key_sql[idx] = '"' .. value .. '"'
  end
  if self._new then
    local fields = {sep = ", "}
    local values = {sep = ", "}
    for key, dummy in pairs(self._dirty or {}) do
      add(fields, {'"$"', {key}})
      add(values, {'?', self[key]})
    end
    if compat_returning then  -- compatibility for PostgreSQL 8.1
      local db_error, db_result1, db_result2 = self._connection:try_query(
        {
          'INSERT INTO $ ($) VALUES ($)',
          {self._class:get_qualified_table()},
          fields,
          values,
          primary_key_sql
        },
        "list",
        {
          'SELECT currval(?)',
          self._class.table .. '_id_seq'
        },
        "object"
      )
      if db_error then
        return db_error
      end
      self.id = db_result2.id
    else
      local db_error, db_result
      if #fields == 0 then
        db_error, db_result = self._connection:try_query(
          {
            'INSERT INTO $ DEFAULT VALUES RETURNING ($)',
            {self._class:get_qualified_table()},
            primary_key_sql
          },
          "object"
        )
      else
        db_error, db_result = self._connection:try_query(
          {
            'INSERT INTO $ ($) VALUES ($) RETURNING ($)',
            {self._class:get_qualified_table()},
            fields,
            values,
            primary_key_sql
          },
          "object"
        )
      end
      if db_error then
        return db_error
      end
      for idx, value in ipairs(primary_key) do
        self[value] = db_result[value]
      end
    end
    self._new = false
  else
    local command_sets = {sep = ", "}
    for key, dummy in pairs(self._dirty or {}) do
      add(command_sets, {'"$" = ?', {key}, self[key]})
    end
    if #command_sets >= 1 then
      local primary_key_compare = {sep = " AND "}
      for idx, value in ipairs(primary_key) do
        primary_key_compare[idx] = {
          "$ = ?",
          {'"' .. value .. '"'},
          self[value]
        }
      end
      local db_error = self._connection:try_query{
        'UPDATE $ SET $ WHERE $',
        {self._class:get_qualified_table()},
        command_sets,
        primary_key_compare
      }
      if db_error then
        return db_error
      end
    end
  end
  return nil
end
    db_selector = -- same selector returned <db_selector>:add_combine( expression -- expression as passed to "assemble_command" )
function selector_prototype:add_combine(expression) add(self._combine, expression) return self end
db_selector = -- same selector returned <db_selector>:add_distinct_on( expression -- expression as passed to "assemble_command" )
function selector_prototype:add_distinct_on(expression)
  if self._distinct then
    error("Can not combine DISTINCT with DISTINCT ON.")
  end
  add(self._distinct_on, expression)
  return self
end
    db_selector = -- same selector returned <db_selector>:add_field( expression, -- expression as passed to "assemble_command" alias, -- optional alias expression as passed to "assemble_command" option_list -- optional list of options (may contain strings "distinct" or "grouped") )
function selector_prototype:add_field(expression, alias, options)
  if alias then
    add(self._fields, {'$ AS "$"', {expression}, {alias}})
  else
    add(self._fields, expression)
  end
  if options then
    for i, option in ipairs(options) do
      if option == "distinct" then
        if alias then
          self:add_distinct_on('"' .. alias .. '"')
        else
          self:add_distinct_on(expression)
        end
      elseif option == "grouped" then
        if alias then
          self:add_group_by('"' .. alias .. '"')
        else
          self:add_group_by(expression)
        end
      else
        error("Unknown option '" .. option .. "' to add_field method.")
      end
    end
  end
  return self
end
    db_selector = -- same selector returned <db_selector>:add_from( expression, -- expression as passed to "assemble_command" alias, -- optional alias expression as passed to "assemble_command" condition -- optional condition expression as passed to "assemble_command" )
function selector_prototype:add_from(expression, alias, condition)
  local first = (#self._from == 0)
  if not first then
    if condition then
      add(self._from, "INNER JOIN")
    else
      add(self._from, "CROSS JOIN")
    end
  end
  if getmetatable(expression) == selector_metatable then
    if alias then
      add(self._from, {'($) AS "$"', {expression}, {alias}})
    else
      add(self._from, {'($) AS "subquery"', {expression}})
    end
  else
    if alias then
      add(self._from, {'$ AS "$"', {expression}, {alias}})
    else
      add(self._from, expression)
    end
  end
  if condition then
    if first then
      self:add_where(condition)
    else
      add(self._from, "ON")
      add(self._from, condition)
    end
  end
  return self
end
    db_selector = -- same selector returned <db_selector>:add_group_by( expression -- expression as passed to "assemble_command" )
function selector_prototype:add_group_by(expression) add(self._group_by, expression) return self end
db_selector = -- same selector returned <db_selector>:add_having( expression -- expression as passed to "assemble_command" )
function selector_prototype:add_having(expression) add(self._having, expression) return self end
db_selector = -- same selector returned <db_selector>:add_order_by( expression -- expression as passed to "assemble_command" )
function selector_prototype:add_order_by(expression) add(self._order_by, expression) return self end
db_selector = -- same selector returned <db_selector>:add_where( expression -- expression as passed to "assemble_command" )
function selector_prototype:add_where(expression) add(self._where, expression) return self end
db_selector = <db_selector>:add_with( expression = expression, selector = selector )
function selector_prototype:add_with(expression, selector)
  add(self._with, {"$ AS ($)", {expression}, {selector}})
  return self
end
    db_selector = -- same selector returned <db_selector>:attach( mode, -- attachment type: "11" one to one, "1m" one to many, "m1" many to one data2, -- other database result list or object, the results of this selector shall be attached with field1, -- field name(s) in result list or object of this selector used for attaching field2, -- field name(s) in "data2" used for attaching ref1, -- name of reference field in the results of this selector after attaching ref2 -- name of reference field in "data2" after attaching )
function selector_prototype:attach(mode, data2, field1, field2, ref1, ref2)
  self._attach = {
    mode = mode,
    data2 = data2,
    field1 = field1,
    field2 = field2,
    ref1 = ref1,
    ref2 = ref2
  }
  return self
end
    count =                -- number of rows returned
<db_selector>:count()
    function selector_prototype:count()
  if not self._count then
    local count_selector = self:get_db_conn():new_selector()
    count_selector:add_field('count(1)')
    count_selector:add_from(self)
    count_selector:single_object_mode()
    self._count = count_selector:exec().count
  end
  return self._count
end
    db_selector =                    -- same selector returned
<db_selector>:empty_list_mode()
    function selector_prototype:empty_list_mode() self._mode = "empty_list" return self end
db_selector = -- same selector returned <db_selector>:except( expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE )
function selector_prototype:except(expression)
  self:add_combine{"EXCEPT $", {expression}}
  return self
end
    db_selector = -- same selector returned <db_selector>:except_all( expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE )
function selector_prototype:except_all(expression)
  self:add_combine{"EXCEPT ALL $", {expression}}
  return self
end
    result =              -- database result list or object
<db_selector>:exec()
    function selector_prototype:exec()
  local db_error, result = self:try_exec()
  if db_error then
    db_error:escalate()
  else
    return result
  end
end
    db_selector =              -- same selector returned
<db_selector>:for_share()
    function selector_prototype:for_share() self._read_lock.all = true return self end
db_selector = -- same selector returned <db_selector>:for_share_of( expression -- expression as passed to "assemble_command" )
function selector_prototype:for_share_of(expression) add(self._read_lock, expression) return self end
db_selector =               -- same selector returned
<db_selector>:for_update()
    function selector_prototype:for_update() self._write_lock.all = true return self end
db_selector = -- same selector returned <db_selector>:for_update_of( expression -- expression as passed to "assemble_command" )
function selector_prototype:for_update_of(expression) add(self._write_lock, expression) return self end
db_selector = -- same selector returned <db_selector>:from( expression, -- expression as passed to "assemble_command" alias, -- optional alias expression as passed to "assemble_command" condition -- optional condition expression as passed to "assemble_command" )
function selector_prototype:from(expression, alias, condition)
  if #self._from > 0 then
    error("From-clause already existing (hint: try join).")
  end
  return self:join(expression, alias, condition)
end
    db_handle =                  -- handle of database connection
<db_selector>:get_db_conn()
    function selector_prototype:get_db_conn() return self._db_conn end
db_selector = -- same selector returned <db_selector>:intersect( expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE )
function selector_prototype:intersect(expression)
  self:add_combine{"INTERSECT $", {expression}}
  return self
end
    db_selector = -- same selector returned <db_selector>:intersect_all( expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE )
function selector_prototype:intersect_all(expression)
  self:add_combine{"INTERSECT ALL $", {expression}}
  return self
end
    db_selector = -- same selector returned <db_selector>:join( expression, -- expression as passed to "assemble_command" alias, -- optional alias expression as passed to "assemble_command" condition -- optional condition expression as passed to "assemble_command" )
function selector_prototype:join(...)  -- NOTE: alias for add_from
  return self:add_from(...)
end
    db_selector = -- same selector returned <db_selector>:left_join( expression, -- expression as passed to "assemble_command" alias, -- optional alias expression as passed to "assemble_command" condition -- optional condition expression as passed to "assemble_command" )
function selector_prototype:left_join(expression, alias, condition)
  local first = (#self._from == 0)
  if not first then
    add(self._from, "LEFT OUTER JOIN")
  end
  if alias then
    add(self._from, {'$ AS "$"', {expression}, {alias}})
  else
    add(self._from, expression)
  end
  if condition then
    if first then
      self:add_where(condition)
    else
      add(self._from, "ON")
      add(self._from, condition)
    end
  end
  return self
end
    db_selector = -- same selector returned <db_selector>:limit( count -- integer used as LIMIT )
function selector_prototype:limit(count)
  if type(count) ~= "number" or count % 1 ~= 0 then
    error("LIMIT must be an integer.")
  end
  self._limit = count
  return self
end
    db_selector = -- same selector returned <db_selector>:offset( count -- integer used as OFFSET )
function selector_prototype:offset(count)
  if type(count) ~= "number" or count % 1 ~= 0 then
    error("OFFSET must be an integer.")
  end
  self._offset = count
  return self
end
    db_selector =                         -- same selector returned
<db_selector>:optional_object_mode()
    function selector_prototype:optional_object_mode() self._mode = "opt_object" return self end
db_selector =                 -- same selector returned
<db_selector>:reset_fields()
    function selector_prototype:reset_fields()
  for idx in ipairs(self._fields) do
    self._fields[idx] = nil
  end
  return self
end
    db_selector = -- same selector returned <db_selector>:set_class( class -- database class (model) )
function selector_prototype:set_class(class) self._class = class return self end
db_selector =                -- same selector returned
<db_selector>:set_distinct()
    function selector_prototype:set_distinct()
  if #self._distinct_on > 0 then
    error("Can not combine DISTINCT with DISTINCT ON.")
  end
  self._distinct = true
  return self
end
    db_selector =                       -- same selector returned
<db_selector>:single_object_mode()
    function selector_prototype:single_object_mode() self._mode = "object" return self end
db_error, -- database error object, or nil in case of success result = -- database result list or object <db_selector>:try_exec()
function selector_prototype:try_exec()
  if self._mode == "empty_list" then
    if self._class then
      return nil, self._class:create_list()
    else
       return nil, self._db_conn:create_list()
    end
  end
  local db_error, db_result = self._db_conn:try_query(self, self._mode)
  if db_error then
    return db_error
  elseif db_result then
    if self._class then set_class(db_result, self._class) end
    if self._attach then
      attach(
        self._attach.mode,
        db_result,
        self._attach.data2,
        self._attach.field1,
        self._attach.field2,
        self._attach.ref1,
        self._attach.ref2
      )
    end
    return nil, db_result
  else
    return nil
  end
end
    db_selector = -- same selector returned <db_selector>:union( expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE )
function selector_prototype:union(expression)
  self:add_combine{"UNION $", {expression}}
  return self
end
    db_selector = -- same selector returned <db_selector>:union_all( expression -- expression or selector without ORDER BY, LIMIT, FOR UPDATE or FOR SHARE )
function selector_prototype:union_all(expression)
  self:add_combine{"UNION ALL $", {expression}}
  return self
end
    app  -- table to store an application state
    app = {}
    at_exit(
  func  -- function to be called before the process is ending
)
    do
  local exit_handlers = {}
  function at_exit(func)
    table.insert(exit_handlers, func)
  end
  function exit(code)
    for i = #exit_handlers, 1, -1 do
      exit_handlers[i]()
    end
    os.exit(code)
  end
end
    bool = -- true, false, or nil atom.boolean:load( string -- string to be interpreted as boolean )
function boolean:load(str)
  if str == nil or str == "" then
    return nil
  elseif type(str) ~= "string" then
    error("String expected")
  elseif string.find(str, "^[TtYy1]") then
    return true
  elseif string.find(str, "^[FfNn0]") then
    return false
  else
    return nil  -- we don't have an undefined bool
  end
end
    atom.date.invalid
date.invalid = date:_create{
  jd = not_a_number,
  year = not_a_number, month = not_a_number, day = not_a_number,
  invalid = true
}
    year, -- year month, -- month from 1 to 12 day = -- day from 1 to 31 atom.date.jd_to_ymd( jd, -- days from January 1st 1970 )
function date.jd_to_ymd(jd)
  assert(is_integer(jd), "Invalid julian date specified.")
  local calc_jd = jd + offset
  assert(is_integer(calc_jd), "Julian date is out of range.")
  local n400 = math.floor(calc_jd / c400)
  local r400 = calc_jd % c400
  local n100 = math.floor(r400 / c100)
  local r100 = r400 % c100
  if n100 == 4 then n100, r100 = 3, c100 end
  local n4 = math.floor(r100 / c4)
  local r4 = r100 % c4
  local n1 = math.floor(r4 / c1)
  local r1 = r4 % c1
  if n1 == 4 then n1, r1 = 3, c1 end
  local year = 1 + 400 * n400 + 100 * n100 + 4 * n4 + n1
  local month = 1 + math.floor(r1 / 31)
  local month_offset = get_month_offset(year, month)
  if month < 12 then
    local next_month_offset = get_month_offset(year, month + 1)
    if r1 >= next_month_offset then
      month = month + 1
      month_offset = next_month_offset
    end
  end
  local day = 1 + r1 - month_offset
  return year, month, day
end
    jd = -- days from January 1st 1970 atom.date.ymd_to_jd( year, -- year month, -- month from 1 to 12 day -- day from 1 to 31 )
local offset = 0
function date.ymd_to_jd(year, month, day)
  assert(is_integer(year), "Invalid year specified.")
  assert(is_integer(month), "Invalid month specified.")
  assert(is_integer(day), "Invalid day specified.")
  local calc_year = year - 1
  local n400 = math.floor(calc_year / 400)
  local r400 = calc_year % 400
  local n100 = math.floor(r400 / 100)
  local r100 = r400 % 100
  local n4 = math.floor(r100 / 4)
  local n1 = r100 % 4
  local jd = (
    c400 * n400 + c100 * n100 + c4 * n4 + c1 * n1 +
    get_month_offset(year, month) + (day - 1)
  )
  return jd - offset
end
offset = date.ymd_to_jd(1970, 1, 1)
    atom.date:get_current()
function date:get_current()
  local now = os.date("*t")
  return date{
    year = now.year, month = now.month, day = now.day
  }
end
    date = -- date represented by the string atom.date:load( string -- string representing a date )
function date:load(str)
  if str == nil or str == "" then
    return nil
  elseif type(str) ~= "string" then
    error("String expected")
  else
    local year, month, day = string.match(
      str, "^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"
    )
    if year then
      return date{
        year = tonumber(year),
        month = tonumber(month),
        day = tonumber(day)
      }
    else
      return date.invalid
    end
  end
end
    d = -- date based on the given data atom.date:new{ jd = jd, -- days since January 1st 1970 year = year, -- year month = month, -- month from 1 to 12 day = day, -- day from 1 to 31 iso_weekyear = iso_weekyear, -- year according to ISO 8601 iso_week = iso_week, -- week number according to ISO 8601 iso_weekday = iso_weekday, -- day of week from 1 for monday to 7 for sunday us_weekyear = us_weekyear, -- year us_week = us_week, -- week number according to US style counting us_weekday = us_weekday -- day of week from 1 for sunday to 7 for saturday }
function date:new(args)
  local args = args
  if type(args) == "number" then args = { jd = args } end
  if type(args) == "table" then
    local year, month, day = args.year, args.month, args.day
    local jd = args.jd
    local iso_weekyear = args.iso_weekyear
    local iso_week     = args.iso_week
    local iso_weekday  = args.iso_weekday
    local us_week      = args.us_week
    local us_weekday   = args.us_weekday
    if
      type(year)  == "number" and
      type(month) == "number" and
      type(day)   == "number"
    then
      if
        is_integer(year)  and year >= 1  and year <= 9999 and
        is_integer(month) and month >= 1 and month <= 12  and
        is_integer(day)   and day >= 1   and day <= 31
      then
        return date:_create{
          jd = date.ymd_to_jd(year, month, day),
          year = year, month = month, day = day
        }
      else
        return date.invalid
      end
    elseif type(jd) == "number" then
      if is_integer(jd) and jd >= -719162 and jd <= 2932896 then
        local year, month, day = date.jd_to_ymd(jd)
        return date:_create{
          jd = jd, year = year, month = month, day = day
        }
      else
        return date.invalid
      end
    elseif
      type(year)        == "number" and not iso_weekyear and
      type(iso_week)    == "number" and
      type(iso_weekday) == "number"
    then
      if
        is_integer(year) and
        is_integer(iso_week)     and iso_week >= 0 and iso_week <= 53 and
        is_integer(iso_weekday)  and iso_weekday >= 1 and iso_weekday <= 7
      then
        local jan4 = date{ year = year, month = 1, day = 4 }
        local reference = jan4 - jan4.iso_weekday - 7  -- Sun. of week -1
        return date(reference + 7 * iso_week + iso_weekday)
      else
        return date.invalid
      end
    elseif
      type(iso_weekyear) == "number" and not year and
      type(iso_week)     == "number" and
      type(iso_weekday)  == "number"
    then
      if
        is_integer(iso_weekyear) and
        is_integer(iso_week)     and iso_week >= 0 and iso_week <= 53 and
        is_integer(iso_weekday)  and iso_weekday >= 1 and iso_weekday <= 7
      then
        local guessed = date{
          year        = iso_weekyear,
          iso_week    = iso_week,
          iso_weekday = iso_weekday
        }
        if guessed.invalid or guessed.iso_weekyear == iso_weekyear then
          return guessed
        else
          local year
          if iso_week <= 1 then
            year = iso_weekyear - 1
          elseif iso_week >= 52 then
            year = iso_weekyear + 1
          else
            error("Internal error in ISO week computation occured.")
          end
          return date{
            year = year, iso_week = iso_week, iso_weekday = iso_weekday
          }
        end
      else
        return date.invalid
      end
    elseif
      type(year) == "number" and
      type(us_week)     == "number" and
      type(us_weekday)  == "number"
    then
      if
        is_integer(year) and
        is_integer(us_week)     and us_week >= 0    and us_week <= 54   and
        is_integer(us_weekday)  and us_weekday >= 1 and us_weekday <= 7
      then
        local jan1 = date{ year = year, month = 1, day = 1 }
        local reference = jan1 - jan1.us_weekday - 7  -- Sat. of week -1
        return date(reference + 7 * us_week + us_weekday)
      else
        return date.invalid
      end
    end
  end
  error("Illegal arguments passed to date constructor.")
end
    string = -- string representation to be passed to a load function atom.dump( value -- value to be dumped )
function dump(obj)
  if obj == nil then
    return ""
  else
    return tostring(obj)
  end
end
    atom.fraction.invalid
fraction.invalid = fraction:_create{
  numerator = not_a_number, denominator = not_a_number, invalid = true
}
    frac = -- fraction represented by the given string atom.fraction:load( string -- string representation of a fraction )
function fraction:load(str)
  if str == nil or str == "" then
    return nil
  elseif type(str) ~= "string" then
    error("String expected")
  else
    local sign, int = string.match(str, "^(%-?)([0-9]+)$")
    if sign == "" then return fraction:new(tonumber(int))
    elseif sign == "-" then return fraction:new(- tonumber(int))
    end
    local sign, n, d = string.match(str, "^(%-?)([0-9]+)/([0-9]+)$")
    if sign == "" then return fraction:new(tonumber(n), tonumber(d))
    elseif sign == "-" then return fraction:new(- tonumber(n), tonumber(d))
    end
    return fraction.invalid
  end
end
    frac = -- fraction atom.fraction:new( numerator, -- numerator denominator -- denominator )
function fraction:new(numerator, denominator)
  if not (
    (numerator == nil   or type(numerator)   == "number") and
    (denominator == nil or type(denominator) == "number")
  ) then
    error("Invalid arguments passed to fraction constructor.")
  elseif
    (not is_integer(numerator)) or
    (denominator and (not is_integer(denominator)))
  then
    return fraction.invalid
  elseif denominator then
    if denominator == 0 then
      return fraction.invalid
    elseif numerator == 0 then
      return fraction:_create{ numerator = 0, denominator = 1, float = 0 }
    else
      local d = gcd(math.abs(numerator), math.abs(denominator))
      if denominator < 0 then d = -d end
      local numerator2, denominator2 = numerator / d, denominator / d
      return fraction:_create{
        numerator   = numerator2,
        denominator = denominator2,
        float       = numerator2 / denominator2
      }
    end
  else
    return fraction:_create{
      numerator = numerator, denominator = 1, float = numerator
    }
  end
end
    i = -- the greatest common divisor (GCD) of all given natural numbers atom.gcd( a, -- a natural number b, -- another natural number ... -- optionally more natural numbers )
function gcd(a, b, ...)
  if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
  if b == nil then
    return a
  else
    if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
    if ... == nil then
      local k = 0
      local t
      while a % 2 == 0 and b % 2 == 0 do
        a = a / 2; b = b / 2; k = k + 1
      end
      if a % 2 == 0 then t = a else t = -b end
      while t ~= 0 do
        while t % 2 == 0 do t = t / 2 end
        if t > 0 then a = t else b = -t end
        t = a - b
      end
      return a * 2 ^ k
    else
      return gcd(gcd(a, b), ...)
    end
  end
end
    bool = -- true, if 'value' is of type 't' atom.has_type( value, -- any value t -- atom time, e.g. atom.date, or lua type, e.g. "string" )
function has_type(value, t)
  if t == nil then error("No type passed to has_type(...) function.") end
  local lua_type = type(value)
  return
    lua_type == t or
    getmetatable(value) == t or
    (lua_type == "boolean" and t == _M.boolean) or
    (lua_type == "string" and t == _M.string) or (
      lua_type == "number" and
      (t == _M.number or (
        t == _M.integer and (
          not (value <= 0 or value >= 0) or (
            value % 1 == 0 and
            (value + 1) - value == 1 and
            value - (value - 1) == 1
          )
        )
      ))
    )
end
    atom.integer.invalid
integer.invalid = not_a_number --// ------------ -- number -- ------------ number = create_new_type("number") --[[-- int = -- a number or atom.number.invalid (atom.not_a_number) atom.number:load( string -- a string representing a number ) 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. --]]-- function number:load(str) if str == nil or str == "" then return nil elseif type(str) ~= "string" then error("String expected") else return tonumber(str) or not_a_number end end
int = -- an integer or atom.integer.invalid (atom.not_a_number) atom.integer:load( string -- a string representing an integer )
function integer:load(str)
  if str == nil or str == "" then
    return nil
  elseif type(str) ~= "string" then
    error("String expected")
  else
    local num = tonumber(str)
    if is_integer(num) then return num else return not_a_number end
  end
end
    bool = -- true, if value is an integer within resolution atom.is_integer( value -- value to be tested )
function is_integer(i)
  return
    type(i) == "number" and i % 1 == 0 and
    (i + 1) - i == 1 and i - (i - 1) == 1
end
    bool = -- true, if 'value' is of type 't' atom.is_valid( value, -- any value t -- atom time, e.g. atom.date, or lua type, e.g. "string" )
function is_valid(value, t)
  local lua_type = type(value)
  if lua_type == "table" then
    local mt = getmetatable(value)
    if t then
      return t == mt and not value.invalid
    else
      return (getmetatable(mt) == type_mt) and not value.invalid
    end
  elseif lua_type == "boolean" then
    return not t or t == "boolean" or t == _M.boolean
  elseif lua_type == "string" then
    return not t or t == "string" or t == _M.string
  elseif lua_type == "number" then
    if t == _M.integer then
      return
        value % 1 == 0 and
        (value + 1) - value == 1 and
        value - (value - 1) == 1
    else
      return
        (not t or t == "number" or t == _M.number) and
        (value <= 0 or value >= 0)
    end
  else
    return false
  end
end
    i = --the least common multiple (LCD) of all given natural numbers atom.lcm( a, -- a natural number b, -- another natural number ... -- optionally more natural numbers )
function lcm(a, b, ...)
  if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
  if b == nil then
    return a
  else
    if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
    if ... == nil then
      return a * b / gcd(a, b)
    else
      return lcm(lcm(a, b), ...)
    end
  end
end
    atom.not_a_number
not_a_number = 0 / 0
atom.number.invalid
number.invalid = not_a_number
string = -- the same string atom.string:load( string -- a string )
function _M.string:load(str)
  if str == nil then
    return nil
  elseif type(str) ~= "string" then
    error("String expected")
  else
    return str
  end
end
    t =                      -- current time of day
atom.time:get_current()
    function time:get_current()
  local now = os.date("*t")
  return time{ hour = now.hour, minute = now.min, second = now.sec }
end
    t = -- time represented by the string atom.time:load( string -- string representing a time of day )
function time:load(str)
  if str == nil or str == "" then
    return nil
  elseif type(str) ~= "string" then
    error("String expected")
  else
    local hour, minute, second = string.match(
      str,
      "^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$"
    )
    if hour then
      return time{
        hour   = tonumber(hour),
        minute = tonumber(minute),
        second = tonumber(second)
      }
    else
      return time.invalid
    end
  end
end
    t = -- time based on given data atom.time:new{ dsec = dsec, -- seconds since 00:00:00 hour = hour, -- hour from 0 to 23 minute = minute, -- minute from 0 to 59 second = second -- second from 0 to 59 }
function time:new(args)
  local args = args
  if type(args) == "number" then args = { dsec = args } end
  if type(args) == "table" then
    if not args.second then
      args.second = 0
      if not args.minute then
        args.minute = 0
      end
    end
    if
      type(args.hour)   == "number" and
      type(args.minute) == "number" and
      type(args.second) == "number"
    then
      if
        is_integer(args.hour) and
        args.hour >= 0 and args.hour <= 23 and
        is_integer(args.minute) and
        args.minute >= 0 and args.minute <= 59 and
        is_integer(args.second) and
        args.second >= 0 and args.second <= 59
      then
        return time:_create{
          dsec = time.hms_to_dsec(args.hour, args.minute, args.second),
          hour   = args.hour,
          minute = args.minute,
          second = args.second
        }
      else
        return time.invalid
      end
    elseif type(args.dsec) == "number" then
      if
        is_integer(args.dsec) and
        args.dsec >= 0 and args.dsec <= 86399
      then
        local hour, minute, second =
          time.dsec_to_hms(args.dsec)
        return time:_create{
          dsec = args.dsec,
          hour = hour, minute = minute, second = second
        }
      else
        return time.invalid
      end
    end
  end
  error("Invalid arguments passed to time constructor.")
end
    year, -- year month, -- month from 1 to 12 day, -- day from 1 to 31 hour, -- hour from 0 to 23 minute, -- minute from 0 to 59 second = -- second from 0 to 59 atom.timestamp.tsec_to_ymdhms( tsec -- seconds since January 1st 1970 00:00 )
function timestamp.tsec_to_ymdhms(tsec) local jd = math.floor(tsec / 86400) local dsec = tsec % 86400 local year, month, day = date.jd_to_ymd(jd) local hour = math.floor(dsec / 3600) local minute = math.floor((dsec % 3600) / 60) local second = dsec % 60 return year, month, day, hour, minute, second end
tsec = -- seconds since January 1st 1970 00:00 atom.timestamp.ymdhms_to_tsec( year, -- year month, -- month from 1 to 12 day, -- day from 1 to 31 hour, -- hour from 0 to 23 minute, -- minute from 0 to 59 second -- second from 0 to 59 )
function timestamp.ymdhms_to_tsec(year, month, day, hour, minute, second)
  return
    86400 * date.ymd_to_jd(year, month, day) +
    3600 * hour + 60 * minute + second
end
    ts =                          -- current date/time as timestamp
atom.timestamp:get_current()
    function timestamp:get_current()
  local now = os.date("*t")
  return timestamp{
    year = now.year, month = now.month, day = now.day,
    hour = now.hour, minute = now.min, second = now.sec
  }
end
    ts = -- timestamp represented by the string atom.timestamp:load( string -- string representing a timestamp )
function timestamp:load(str)
  if str == nil or str == "" then
    return nil
  elseif type(str) ~= "string" then
    error("String expected")
  else
    local year, month, day, hour, minute, second = string.match(
      str,
      "^([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])$"
    )
    if year then
      return timestamp{
        year   = tonumber(year),
        month  = tonumber(month),
        day    = tonumber(day),
        hour   = tonumber(hour),
        minute = tonumber(minute),
        second = tonumber(second)
      }
    else
      return timestamp.invalid
    end
  end
end
function timestamp:__tostring()
  if self.invalid then
    return "invalid_timestamp"
  else
    return string.format(
      "%04i-%02i-%02i %02i:%02i:%02i",
      self.year, self.month, self.day, self.hour, self.minute, self.second
    )
  end
end
function timestamp.getters:date()
  return date{ year = self.year, month = self.month, day = self.day }
end
function timestamp.getters:time()
  return time{
    hour = self.hour,
    minute = self.minute,
    second = self.second
  }
end
function timestamp.__eq(value1, value2)
  if value1.invalid or value2.invalid then
    return false
  else
    return value1.tsec == value2.tsec
  end
end
function timestamp.__lt(value1, value2)
  if value1.invalid or value2.invalid then
    return false
  else
    return value1.tsec < value2.tsec
  end
end
function timestamp.__le(value1, value2)
  if value1.invalid or value2.invalid then
    return false
  else
    return value1.tsec <= value2.tsec
  end
end
function timestamp.__add(value1, value2)
  if getmetatable(value1) == timestamp then
    if getmetatable(value2) == timestamp then
      error("Can not add two timestamps.")
    elseif type(value2) == "number" then
      return timestamp(value1.tsec + value2)
    else
      error("Right operand of '+' operator has wrong type.")
    end
  elseif type(value1) == "number" then
    if getmetatable(value2) == timestamp then
      return timestamp(value1 + value2.tsec)
    else
      error("Assertion failed")
    end
  else
    error("Left operand of '+' operator has wrong type.")
  end
end
function timestamp.__sub(value1, value2)
  if not getmetatable(value1) == timestamp then
    error("Left operand of '-' operator has wrong type.")
  end
  if getmetatable(value2) == timestamp then
    return value1.tsec - value2.tsec  -- TODO: transform to interval
  elseif type(value2) == "number" then
    return timestamp(value1.tsec - value2)
  else
    error("Right operand of '-' operator has wrong type.")
  end
end
----------
-- time --
----------
time = create_new_type("time")
function time.hms_to_dsec(hour, minute, second)
  return 3600 * hour + 60 * minute + second
end
function time.dsec_to_hms(dsec)
  local hour   = math.floor(dsec / 3600)
  local minute = math.floor((dsec % 3600) / 60)
  local second = dsec % 60
  return hour, minute, second
end
--[[--
atom.time.invalid
Value representing an invalid time of day.
--]]--
time.invalid = time:_create{
  dsec = not_a_number,
  hour = not_a_number, minute = not_a_number, second = not_a_number,
  invalid = true
}
    ts = -- timestamp based on given data atom.timestamp:new{ tsec = tsec, -- seconds since January 1st 1970 00:00 year = year, -- year month = month, -- month from 1 to 12 day = day, -- day from 1 to 31 hour = hour, -- hour from 0 to 23 minute = minute, -- minute from 0 to 59 second = second -- second from 0 to 59 }
function timestamp:new(args)
  local args = args
  if type(args) == "number" then args = { tsec = args } end
  if type(args) == "table" then
    if not args.second then
      args.second = 0
      if not args.minute then
        args.minute = 0
        if not args.hour then
          args.hour = 0
        end
      end
    end
    if
      type(args.year)   == "number" and
      type(args.month)  == "number" and
      type(args.day)    == "number" and
      type(args.hour)   == "number" and
      type(args.minute) == "number" and
      type(args.second) == "number"
    then
      if
        is_integer(args.year) and
        args.year >= 1 and args.year <= 9999 and
        is_integer(args.month) and
        args.month >= 1 and args.month <= 12 and
        is_integer(args.day) and
        args.day >= 1 and args.day <= 31 and
        is_integer(args.hour) and
        args.hour >= 0 and args.hour <= 23 and
        is_integer(args.minute) and
        args.minute >= 0 and args.minute <= 59 and
        is_integer(args.second) and
        args.second >= 0 and args.second <= 59
      then
        return timestamp:_create{
          tsec = timestamp.ymdhms_to_tsec(
            args.year, args.month, args.day,
            args.hour, args.minute, args.second
          ),
          year   = args.year,
          month  = args.month,
          day    = args.day,
          hour   = args.hour,
          minute = args.minute,
          second = args.second
        }
      else
        return timestamp.invalid
      end
    elseif type(args.tsec) == "number" then
      if
        is_integer(args.tsec) and
        args.tsec >= -62135596800 and args.tsec <= 253402300799
      then
        local year, month, day, hour, minute, second =
          timestamp.tsec_to_ymdhms(args.tsec)
        return timestamp:_create{
          tsec = args.tsec,
          year = year, month = month, day = day,
          hour = hour, minute = minute, second = second
        }
      else
        return timestamp.invalid
      end
    end
  end
  error("Invalid arguments passed to timestamp constructor.")
end
    discovery_data, -- table containing "claimed_identifier", "op_endpoint" and "op_local_identifier" errmsg, -- error message in case of failure errcode = -- error code in case of failure (TODO: not implemented yet) auth.openid.discover{ user_supplied_identifier = user_supplied_identifier, -- string given by user https_as_default = https_as_default, -- default to https curl_options = curl_options -- options passed to "curl" binary, when performing discovery }
-- helper function local function decode_entities(str) local str = str str = string.gsub(value, "<", '<') str = string.gsub(value, ">", '>') str = string.gsub(value, """, '"') str = string.gsub(value, "&", '&') return str end -- helper function local function get_tag_value( str, -- HTML document or document snippet match_tag, -- tag name match_key, -- attribute key to match match_value, -- attribute value to match result_key -- attribute key of value to return ) -- NOTE: The following parameters are case insensitive local match_tag = string.lower(match_tag) local match_key = string.lower(match_key) local match_value = string.lower(match_value) local result_key = string.lower(result_key) for tag, attributes in string.gmatch(str, "<([0-9A-Za-z_-]+) ([^>]*)>") do local tag = string.lower(tag) if tag == match_tag then local matching = false for key, value in string.gmatch(attributes, '([0-9A-Za-z_-]+)="([^"<>]*)"') do local key = string.lower(key) local value = decode_entities(value) if key == match_key then -- NOTE: match_key must only match one key of space seperated list for value in string.gmatch(value, "[^ ]+") do if string.lower(value) == match_value then matching = true break end end end if key == result_key then result_value = value end end if matching then return result_value end end end return nil end -- helper function local function tag_contents(str, match_tag) local pos = 0 local tagpos, closing, tag local function next_tag() local prefix tagpos, prefix, tag, pos = string.match( str, "()<(/?)([0-9A-Za-z:_-]+)[^>]*>()", pos ) closing = (prefix == "/") end return function() repeat next_tag() if not tagpos then return nil end local stripped_tag if string.find(tag, ":") then stripped_tag = string.match(tag, ":([^:]*)$") else stripped_tag = tag end until stripped_tag == match_tag and not closing local content_start = pos local used_tag = tag local counter = 0 while true do repeat next_tag() if not tagpos then return nil end until tag == used_tag if closing then if counter > 0 then counter = counter - 1 else return string.sub(str, content_start, tagpos-1) end else counter = counter + 1 end end local content = string.sub(rest, 1, startpos-1) str = string.sub(rest, endpos+1) return content end end local function strip(str) local str = str string.gsub(str, "^[ \t\r\n]+", "") string.gsub(str, "[ \t\r\n]+$", "") return str end function auth.openid.discover(args) local url = string.match(args.user_supplied_identifier, "[^#]*") -- NOTE: XRIs are not supported if string.find(url, "^[Xx][Rr][Ii]://") or string.find(url, "^[=@+$!(]") then return nil, "XRI identifiers are not supported." end -- Prepend http:// or https://, if neccessary: if not string.find(url, "://") then if args.default_to_https then url = "https://" .. url else url = "http://" .. url end end -- Either an xrds_document or an html_document will be fetched local xrds_document, html_document -- Repeat max 10 times to avoid endless redirection loops local redirects = 0 while true do local status, headers, body = auth.openid._curl(url, args.curl_options) if not status then return nil, "Error while locating XRDS or HTML file for discovery." end -- Check, if we are redirected: local location = string.match( headers, "\r?\n[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[ \t]*([^\r\n]+)" ) if location then -- If we are redirected too often, then return an error. if redirects >= 10 then return nil, "Too many redirects." end -- Otherwise follow the redirection by changing the variable "url" -- and by incrementing the redirect counter. url = location redirects = redirects + 1 else -- Check, if there is an X-XRDS-Location header -- pointing to an XRDS document: local xrds_location = string.match( headers, "\r?\n[Xx]%-[Xx][Rr][Dd][Ss]%-[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[ \t]*([^\r\n]+)" ) -- If there is no X-XRDS-Location header, there might be an -- http-equiv meta tag serving the same purpose: if not xrds_location and status == 200 then xrds_location = get_tag_value(body, "meta", "http-equiv", "X-XRDS-Location", "content") end if xrds_location then -- If we know the XRDS-Location, we can fetch the XRDS document -- from that location: local status, headers, body = auth.openid._curl(xrds_location, args.curl_options) if not status then return nil, "XRDS document could not be loaded." end if status ~= 200 then return nil, "XRDS document not found where expected." end xrds_document = body break elseif -- If the Content-Type header is set accordingly, then we already -- should have received an XRDS document: string.find( headers, "\r?\n[Cc][Oo][Nn][Tt][Ee][Nn][Tt]%-[Tt][Yy][Pp][Ee]:[ \t]*application/xrds%+xml\r?\n" ) then if status ~= 200 then return nil, "XRDS document announced but not found." end xrds_document = body break else -- Otherwise we should have received an HTML document: if status ~= 200 then return nil, "No XRDS or HTML document found for discovery." end html_document = body break; end end end local claimed_identifier -- OpenID identifier the user claims to own local op_endpoint -- OpenID provider endpoint URL local op_local_identifier -- optional user identifier, local to the OpenID provider if xrds_document then -- If we got an XRDS document, we look for a matching <Service> entry: for content in tag_contents(xrds_document, "Service") do local service_uri, service_localid for content in tag_contents(content, "URI") do if not string.find(content, "[<>]") then service_uri = strip(content) break end end for content in tag_contents(content, "LocalID") do if not string.find(content, "[<>]") then service_localid = strip(content) break end end for content in tag_contents(content, "Type") do if not string.find(content, "[<>]") then local content = strip(content) if content == "http://specs.openid.net/auth/2.0/server" then -- The user entered a provider identifier, thus claimed_identifier -- and op_local_identifier will be set to nil. op_endpoint = service_uri break elseif content == "http://specs.openid.net/auth/2.0/signon" then -- The user entered his/her own identifier. claimed_identifier = url op_endpoint = service_uri op_local_identifier = service_localid break end end end end elseif html_document then -- If we got an HTML document, we look for matching <link .../> tags: claimed_identifier = url op_endpoint = get_tag_value( html_document, "link", "rel", "openid2.provider", "href" ) op_local_identifier = get_tag_value( html_document, "link", "rel", "openid2.local_id", "href" ) else error("Assertion failed") -- should not happen end if not op_endpoint then return nil, "No OpenID endpoint found." end if claimed_identifier then claimed_identifier = auth.openid._normalize_url(claimed_identifier) if not claimed_identifier then return nil, "Claimed identifier could not be normalized." end end return { claimed_identifier = claimed_identifier, op_endpoint = op_endpoint, op_local_identifier = op_local_identifier } end
success, -- boolean indicating success or failure errmsg, -- error message in case of failure errcode = -- error code in case of failure (TODO: not implemented yet) auth.openid.initiate{ user_supplied_identifier = user_supplied_identifier, -- string given by user https_as_default = https_as_default, -- default to https curl_options = curl_options, -- additional options passed to "curl" binary, when performing discovery return_to_module = return_to_module, -- module of the verifying view, the user shall return to after authentication return_to_view = return_to_view, -- verifying view, the user shall return to after authentication realm = realm -- URL the user should authenticate for, defaults to application base }
function auth.openid.initiate(args)
  local dd, errmsg, errcode = auth.openid.discover(args)
  if not dd then
    return nil, errmsg, errcode
  end
  -- TODO: Use request.redirect once it supports external URLs
  cgi.set_status("303 See Other")
  cgi.add_header(
    "Location: " ..
    encode.url{
      external = dd.op_endpoint,
      params = {
        ["openid.ns"]         = "http://specs.openid.net/auth/2.0",
        ["openid.mode"]       = "checkid_setup",
        ["openid.claimed_id"] = dd.claimed_identifier or
                                "http://specs.openid.net/auth/2.0/identifier_select",
        ["openid.identity"]   = dd.op_local_identifier or dd.claimed_identifier or
                                "http://specs.openid.net/auth/2.0/identifier_select",
        ["openid.return_to"]  = encode.url{
                                  base   = request.get_absolute_baseurl(),
                                  module = args.return_to_module,
                                  view   = args.return_to_view
                                },
        ["openid.realm"]      = args.realm or request.get_absolute_baseurl()
      }
    }
  )
  cgi.send_data()
  exit()
end
    claimed_identifier, -- identifier owned by the user errmsg, -- error message in case of failure errcode = -- error code in case of failure (TODO: not implemented yet) auth.openid.verify( force_https = force_https, -- only allow https curl_options = curl_options -- options passed to "curl" binary, when performing discovery )
function auth.openid.verify(args)
  local args = args or {}
  if cgi.params["openid.ns"] ~= "http://specs.openid.net/auth/2.0" then
    return nil, "No indirect OpenID 2.0 message received."
  end
  local mode = cgi.params["openid.mode"]
  if mode == "id_res" then
    local return_to_url = cgi.params["openid.return_to"]
    if not return_to_url then
      return nil, "No return_to URL received in answer."
    end
    if return_to_url ~= encode.url{
      base   = request.get_absolute_baseurl(),
      module = request.get_module(),
      view   = request.get_view()
    } then
      return nil, "return_to URL not matching."
    end
    local discovery_args = table.new(args)
    local claimed_identifier = cgi.params["openid.claimed_id"]
    if not claimed_identifier then
      return nil, "No claimed identifier received."
    end
    local cropped_identifier = string.match(claimed_identifier, "[^#]*")
    local normalized_identifier = auth.openid._normalize_url(
      cropped_identifier
    )
    if not normalized_identifier then
      return nil, "Claimed identifier could not be normalized."
    end
    if normalized_identifier ~= cropped_identifier then
      return nil, "Claimed identifier was not normalized."
    end
    discovery_args.user_supplied_identifier = cropped_identifier
    local dd, errmsg, errcode = auth.openid.discover(discovery_args)
    if not dd then
      return nil, errmsg, errcode
    end
    if not dd.claimed_identifier then
      return nil, "Identifier is an OpenID Provider."
    end
    if dd.claimed_identifier ~= cropped_identifier then
      return nil, "Claimed identifier does not match."
    end
    local nonce = cgi.params["openid.response_nonce"]
    if not nonce then
      return nil, "Did not receive a response nonce."
    end
    local year, month, day, hour, minute, second = string.match(
      nonce,
      "^([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9])T([0-9][0-9]):([0-9][0-9]):([0-9][0-9])Z"
    )
    if not year then
      return nil, "Response nonce did not contain a parsable date/time."
    end
    local ts = atom.timestamp{
      year   = tonumber(year),
      month  = tonumber(month),
      day    = tonumber(day),
      hour   = tonumber(hour),
      minute = tonumber(minute),
      second = tonumber(second)
    }
    -- NOTE: 50 hours margin allows us to ignore time zone issues here:
    if math.abs(ts - atom.timestamp:get_current()) > 3600 * 50 then
      return nil, "Response nonce contains wrong time or local time is wrong."
    end
    local params = {}
    for key, value in pairs(cgi.params) do
      local trimmed_key = string.match(key, "^openid%.(.+)")
      if trimmed_key then
        params[key] = value
      end
    end
    params["openid.mode"] = "check_authentication"
    local options = table.new(args.curl_options)
    for key, value in pairs(params) do
      options[#options+1] = "--data-urlencode"
      options[#options+1] = key .. "=" .. value
    end
    local status, headers, body = auth.openid._curl(dd.op_endpoint, options)
    if status ~= 200 then
      return nil, "Authorization could not be verified."
    end
    local result = {}
    for key, value in string.gmatch(body, "([^\n:]+):([^\n]*)") do
      result[key] = value
    end
    if result.ns ~= "http://specs.openid.net/auth/2.0" then
      return nil, "No OpenID 2.0 message replied."
    end
    if result.is_valid == "true" then
      return claimed_identifier
    else
      return nil, "Signature invalid."
    end
  elseif mode == "cancel" then
    return nil, "Authorization failed according to OpenID provider."
  else
    return nil, "Unexpected OpenID mode."
  end
end
    auth.openid.xrds_document{
  return_to_module = return_to_module,
  return_to_view   = return_to_view
}
    function auth.openid.xrds_document(args)
  slot.set_layout(nil, "application/xrds+xml")
  slot.put_into("data",
    '<?xml version="1.0" encoding="UTF-8"?>\n',
    '<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">\n',
    '  <XRD>\n',
    '    <Service>\n',
    '      <Type>http://specs.openid.net/auth/2.0/return_to</Type>\n',
    '      <URI>',
    encode.url{
      base   = request.get_absolute_baseurl(),
      module = args.return_to_module,
      view   = args.return_to_view
    },
    '</URI>\n',
    '    </Service>\n',
    '  </XRD>\n',
    '</xrds:XRDS>\n'
  )
end
    auth.openid.xrds_header{
  ...                     -- arguments as used for encode.url{...}, pointing to an XRDS document as explained below
}
    function auth.openid.xrds_header(args)
  cgi.add_header("X-XRDS-Location: " .. encode.url(args))
end
    current_charset =  -- currently selected character set to be used
charset.get()
    function charset.get() return charset._current end
charset_data =  -- table containing information about the current charset
charset.get_data()
    function charset.get_data()
  return charset.data[
    string.gsub(string.lower(charset._current), "%-", "_")
  ]
end
    charset.set(
  charset_ident  -- identifier of a charset, i.e. "UTF-8"
)
    function charset.set(charset_ident) charset._current = charset_ident end
config  -- table to store application configuration
    config = {}
    db_error, -- error object, or nil in case of success result1, -- result of first command result2, -- result of second command ... = <db_handle>:try_query( command1, -- first command (to be processed by "assemble_command" method) mode1, -- mode for first command: "list", "object" or "opt_object" command2, -- second command (to be processed by "assemble_command" method) mode2, -- mode for second command: "list", "object" or "opt_object" .. )
-- implemented in mondelefant_native.c as -- static int mondelefant_conn_try_query(lua_State *L)
path = -- string containing a path to an action encode.action_file_path{ module = module, -- module name action = action -- action name }
function encode.action_file_path(args)
  return (encode.file_path(
    request.get_app_basepath(),
    'app',
    request.get_app_name(),
    args.module,
    '_action',
    args.action .. '.lua'
  ))
end
    path = -- string containing a (file) path encode.concat_file_path( element1, -- first part of the path element2, -- second part of the path ... -- more parts of the path )
function encode.concat_file_path(...)
  return (
    string.gsub(
      table.concat({...}, "/"), "/+", "/"
    )
  )
end
    path = -- string containing a (file) path encode.encode_file_path( base_path, element1, -- next part of the path element2, -- next part of the path ... )
function encode.file_path(base, ...)  -- base argument is not encoded
  local raw_elements = {...}
  local encoded_elements = {}
  for i = 1, #raw_elements do
    encoded_elements[i] = encode.file_path_element(raw_elements[i])
  end
  return encode.concat_file_path(base, table.unpack(encoded_elements))
end
    encoded_path_element = -- string which can't contain evil stuff like "/" encode.file_path_element( path_element -- string to be encoded )
function encode.file_path_element(path_element)
  return (
    string.gsub(
      string.gsub(
        path_element, "[^0-9A-Za-z_%.-]",
        function(char)
          return string.format("%%%02x", string.byte(char))
        end
      ), "^%.", string.format("%%%%%02x", string.byte("."))
    )
  )
end
    string = -- string to be used as __format information encode.format_info( format, -- name of format function params -- arguments for format function )
function encode.format_info(format, params) return format .. encode.format_options(params) end
string = -- part of string to be used as __format information encode.format_options( params -- arguments for format function )
function encode.format_options(params)
  local params = params or {}
  local result_parts = {}
  for key, value in pairs(params) do
    if type(key) == "string" then
      if string.find(key, "^[A-Za-z][A-Za-z0-9_]*$") then
        table.insert(result_parts, "-")
        table.insert(result_parts, key)
        table.insert(result_parts, "-")
        local t = type(value)
        if t == "string" then
          value = string.gsub(value, "\\", "\\\\")
          value = string.gsub(value, "'", "\\'")
          table.insert(result_parts, "'")
          table.insert(result_parts, value)
          table.insert(result_parts, "'")
        elseif t == "number" then
          table.insert(result_parts, tostring(value))
        elseif t == "boolean" then
          table.insert(result_parts, value and "true" or "false")
        else
          error("Format parameter table contained value of unsupported type " .. t .. ".")
        end
      else
        error('Format parameter table contained invalid key "' .. key .. '".')
      end
    end
  end
  return table.concat(result_parts)
end
    result = -- encoded string encode.html( str -- original string )
function encode.html(text)
  -- TODO: perhaps filter evil control characters?
  return (
    string.gsub(
      text, '[<>&"]',
      function(char)
        if char == '<' then
          return "<"
        elseif char == '>' then
          return ">"
        elseif char == '&' then
          return "&"
        elseif char == '"' then
          return """
        end
      end
    )
  )
end
    text_with_br_tags = -- text with <br/> tags encode.html_newlines( text_with_lf_control_characters -- text with LF control characters )
function encode.html_newlines(text) return (string.gsub(text, '\n', '<br/>')) end
json_string = -- JavaScript code representing the given datum (with quotes, if needed) encode.json( obj -- true, false, nil or a number or string )
-- TODO: check if numeric representations are JSON compatible
function encode.json(obj)
  if obj == nil then
    return "null";
  elseif atom.has_type(obj, atom.boolean) then
    return tostring(obj)
  elseif atom.has_type(obj, atom.number) then
    return tostring(obj)
  elseif type(obj) == "table" then
    local parts = {}
    local first = true
    if #obj > 0 then
      parts[#parts+1] = "["
      for idx, value in ipairs(obj) do
        if first then
          first = false
        else
          parts[#parts+1] = ","
        end
        parts[#parts+1] = tostring(value)
      end
      parts[#parts+1] = "]"
    else
      parts[#parts+1] = "{"
      for key, value in pairs(obj) do
        if first then
          first = false
        else
          parts[#parts+1] = ","
        end
        parts[#parts+1] = encode.json(key)
        parts[#parts+1] = ":"
        parts[#parts+1] = encode.json(value)
      end
      parts[#parts+1] = "}"
    end
    return table.concat(parts)
  else
    local str = atom.dump(obj)
    str = string.gsub(str, ".",
      function (char)
        if char == '\r' then return '\\r'  end
        if char == '\n' then return '\\n'  end
        if char == '\\' then return '\\\\' end
        if char == '"'  then return '\\"'  end
        local byte = string.byte(char)
        if byte < 32 then return string.format("\\u%04x", byte) end
      end
    )
    str = string.gsub(str, "</", "<\\/")
    str = string.gsub(str, "<!%[CDATA%[", "\\u003c![CDATA[")
    str = string.gsub(str, "]]>", "]]\\u003e")
    return '"' .. str .. '"'
  end
end
    url_encoded_string = -- URL-encoded string encode.url_part( obj -- any native datatype or atom )
function encode.url_part(obj)
  return (
    string.gsub(
      atom.dump(obj),
      "[^0-9A-Za-z_%.~-]",
      function (char)
        return string.format("%%%02x", string.byte(char))
      end
    )
  )
end
    url_string = -- a string containing an URL encode.url{ external = external, -- external URL (instead of specifying base, module, etc. below) base = base, -- optional string containing a base URL of a WebMCP application static = static, -- an URL relative to the static file directory module = module, -- a module name of the WebMCP application view = view, -- a view name of the WebMCP application action = action, -- an action name of the WebMCP application id = id, -- optional id to be passed to the view or action to select a particular data record params = params, -- optional parameters to be passed to the view or action anchor = anchor -- anchor in URL }
function encode.url(args)
  local external  = args.external
  local base      = args.base or request.get_relative_baseurl()
  local static    = args.static
  local module    = args.module
  local view      = args.view
  local action    = args.action
  local id        = args.id
  local params    = args.params or {}
  local anchor    = args.anchor
  local result    = {}
  local id_as_param = false
  local function add(...)
    for i = 1, math.huge do
      local v = select(i, ...)
      if v == nil then break end
      result[#result+1] = v
    end
  end
  if external then
    add(external)
  else
    add(base)
    if not string.find(base, "/$") then
      add("/")
    end
    if static then
      add("static/")
      add(static)
    elseif module or view or action or id then
      assert(module, "Module not specified.")
      add(encode.url_part(module), "/")
      if view and not action then
        local view_base, view_suffix = string.match(
          view,
          "^([^.]*)(.*)$"
        )
        add(encode.url_part(view_base))
        if args.id then
          add("/", encode.url_part(id))
        end
        if view_suffix == "" then
          add(".html")
        else
          add(view_suffix)  -- view_suffix includes dot as first character
        end
      elseif action and not view then
        add(encode.url_part(action))
        id_as_param = true
      elseif view and action then
        error("Both a view and an action was specified.")
      end
    end
    do
      local new_params = request.get_perm_params()
      for key, value in pairs(params) do
        new_params[key] = value
      end
      params = new_params
    end
  end
  if next(params) ~= nil or (id and id_as_param) then
    add("?")
    if id and id_as_param then
      add("_webmcp_id=", encode.url_part(id), "&")
    end
    for key, value in pairs(params) do
      -- TODO: better way to detect arrays?
      if string.match(key, "%[%]$") then
        for idx, entry in ipairs(value) do
          add(encode.url_part(key), "=", encode.url_part(entry), "&")
        end
      else
        add(encode.url_part(key), "=", encode.url_part(value), "&")
      end
    end
    result[#result] = nil  -- remove last '&' or '?'
  end
  local string_result = table.concat(result)
  if anchor ~= nil then
    string_result = string.match(string_result, "^[^#]*")
    if anchor then
      string_result = string_result .. "#" .. encode.url_part(anchor)
    end
  end
  return string_result
end
    path = -- string containing a path to a view encode.view_file_path{ module = module, -- module name view = view -- view name }
function encode.view_file_path(args)
  return (encode.file_path(
    request.get_app_basepath(),
    'app', request.get_app_name(), args.module, args.view .. '.lua'
  ))
end
    action_status = -- status code returned by the action (a string) execute.action{ module = module, -- module name of the action to be executed action = action, -- name of the action to be executed id = id, -- id to be returned by param.get_id(...) during execution params = params -- parameters to be returned by param.get(...) during execution }
function execute.action(args)
  local module = args.module
  local action = args.action
  trace.enter_action{ module = module, action = action }
  local action_status = execute.file_path{
    file_path = encode.file_path(
      request.get_app_basepath(),
      'app', request.get_app_name(), module, '_action', action .. '.lua'
    ),
    id     = args.id,
    params = args.params
  }
  trace.execution_return{ status = action_status }
  return action_status
end
    return_value = -- return value of executed chunk execute.chunk{ file_path = file_path, -- path to a lua source or byte-code file app = app, -- app name to use or the current will be used module = module, -- module where chunk is located chunk = chunk -- name of chunk (filename without .lua extension) id = id, -- id to be returned by param.get_id(...) during execution params = params -- parameters to be returned by param.get(...) during execution }
function execute.chunk(args)
  local file_path = args.file_path
  local app       = args.app
  local module    = args.module
  local chunk     = args.chunk
  local id        = args.id
  local params    = args.params
  app = app or request.get_app_name()
  file_path = file_path or encode.file_path(
    request.get_app_basepath(),
    'app', app, module, chunk .. '.lua'
  )
  local func, load_errmsg = loadfile(file_path)
  if not func then
    error('Could not load file "' .. file_path .. '": ' .. load_errmsg)
  end
  if id or params then
    param.exchange(id, params)
  end
  local result = {func()}
  if id or params then
    param.restore()
  end
  return table.unpack(result)
end
    execute.config(
  name           -- name of the configuration to be loaded
)
    function execute.config(name)
  trace.enter_config{ name = name }
  execute.file_path{
    file_path = encode.file_path(
      request.get_app_basepath(), 'config', name .. '.lua'
    )
  }
  trace.execution_return()
end
    status_code = -- status code returned by the executed lua file (a string) execute.file_path{ file_path = file_path, -- path to a lua source or byte-code file id = id, -- id to be returned by param.get_id(...) during execution params = params -- parameters to be returned by param.get(...) during execution }
function execute.file_path(args)
  local file_path = args.file_path
  local id        = args.id
  local params    = args.params
  local func, load_errmsg = loadfile(file_path)
  if not func then
    error('Could not load file "' .. file_path .. '": ' .. load_errmsg)
  end
  if id or params then
    param.exchange(id, params)
  end
  local result = func()
  if result == nil or result == true then
    result = 'ok'
  elseif result == false then
    result = 'error'
  elseif type(result) ~= "string" then
    error("Unexpected type of result: " .. type(result))
  end
  if id or params then
    param.restore()
  end
  return result
end
    action_status = -- status code returned by the action (a string) execute.filtered_action{ module = module, -- module name of the action to be executed action = action, -- name of the action to be executed id = id, -- id to be returned by param.get_id(...) during execution params = params -- parameters to be returned by param.get(...) during execution }
function execute.filtered_action(args)
  local filters = {}
  local function add_by_path(...)
    execute._add_filters_by_path(filters, ...)
  end
  add_by_path("_filter")
  add_by_path("_filter_action")
  add_by_path(request.get_app_name(), "_filter")
  add_by_path(request.get_app_name(), "_filter_action")
  add_by_path(request.get_app_name(), args.module, "_filter")
  add_by_path(request.get_app_name(), args.module, "_filter_action")
  table.sort(filters)
  for idx, filter_name in ipairs(filters) do
    filters[idx] = filters[filter_name]
    filters[filter_name] = nil
  end
  local result
  execute.multi_wrapped(
    filters,
    function()
      result = execute.action(args)
    end
  )
  return result
end
    execute.filtered_view{
  module = module,  -- module name of the view to be executed
  view   = view     -- name of the view to be executed
}
    function execute.filtered_view(args)
  local filters = {}
  local function add_by_path(...)
    execute._add_filters_by_path(filters, ...)
  end
  add_by_path("_filter")
  add_by_path("_filter_view")
  add_by_path(request.get_app_name(), "_filter")
  add_by_path(request.get_app_name(), "_filter_view")
  add_by_path(request.get_app_name(), args.module, "_filter")
  add_by_path(request.get_app_name(), args.module, "_filter_view")
  table.sort(filters)
  for idx, filter_name in ipairs(filters) do
    filters[idx] = filters[filter_name]
    filters[filter_name] = nil
  end
  execute.multi_wrapped(
    filters,
    function()
      execute.view(args)
    end
  )
end
    execute.inner()
function execute.inner()
  local stack = execute._wrap_stack
  local pos = #stack
  if pos == 0 then
    error("Unexpected call of execute.inner().")
  end
  local inner_func = stack[pos]
  if not inner_func then
    error("Repeated call of execute.inner().")
  end
  stack[pos] = false
  inner_func()
end
    return_value = -- return value of executed chunk execute.load_chunk{ file_path = file_path, -- path to a lua source or byte-code file app = app, -- app name to use or the current will be used module = module, -- module where chunk is located chunk = chunk -- filename of lua file to load (including filename extension) id = id, -- id to be returned by param.get_id(...) during execution params = params -- parameters to be returned by param.get(...) during execution }
function execute.load_chunk(args)
  local chunk_name
  if args.chunk then
    chunk_name = string.match(args.chunk, "^(.*)%.lua$")
    if not chunk_name then
      error('"chunk_name" does not end with \'.lua\'')
    end
  end
  return execute.chunk{
    file_path = args.file_path,
    app       = args.app,
    module    = args.module,
    chunk     = chunk_name,
    id        = args.id,
    params    = args.params
  }
end
    execute.multi_wrapped( wrapper_funcs, -- multiple wrapper functions (i.e. filters) inner_func -- inner function (i.e. an action or view) )
function execute.multi_wrapped(wrapper_funcs, inner_func)
  local function wrapped_execution(pos)
    local wrapper_func = wrapper_funcs[pos]
    if wrapper_func then
      return execute.wrapped(
        wrapper_func,
        function()
          wrapped_execution(pos+1)
        end
      )
    else
      return inner_func()
    end
  end
  return wrapped_execution(1)
end
    execute.view{
  module = module,  -- module name of the view to be executed
  view   = view,    -- name of the view to be executed
  id     = id,      -- id to be returned by param.get_id(...) during execution
  params = params   -- parameters to be returned by param.get(...) during execution
}
    function execute.view(args)
  local module = args.module
  local view = args.view
  trace.enter_view{ module = module, view = view }
  execute.file_path{
    file_path = encode.file_path(
      request.get_app_basepath(),
      'app', request.get_app_name(), module, view .. '.lua'
    ),
    id     = args.id,
    params = args.params
  }
  trace.execution_return()
end
    execute.wrapped( wrapper_func, -- function with an execute.inner() call inside inner_func -- function which is executed when execute.inner() is called )
function execute.wrapped(wrapper_func, inner_func)
  if
    type(wrapper_func) ~= "function" or
    type(inner_func) ~= "function"
  then
    error("Two functions need to be passed to execute.wrapped(...).")
  end
  local stack = execute._wrap_stack
  local pos = #stack + 1
  stack[pos] = inner_func
  wrapper_func()
  -- if stack[pos] then
  --   error("Wrapper function did not call execute.inner().")
  -- end
  stack[pos] = nil
end
    passhash = -- encrypted password extos.crypt( key = key, -- password to be one-way encrypted salt = salt -- salt to be used for encryption, optionally starting with "$N$", where N is a digit )
-- implemented in extos.c as -- static int extos_crypt(lua_State *L)
seconds = extos.hires_time()
-- implemented in extos.c as -- static int extos_hires_time(lua_State *L)
directory_entries = -- table of directory entries extos.listdir( path = path -- path name )
-- implemented in extos.c as -- static int extos_listdir(lua_State *L)
seconds = extos.monotonic_hires_time()
-- implemented in extos.c as -- static int extos_monotonic_hires_time(lua_State *L)
data_out, -- string containing stdout data, or nil in case of error data_err, -- string containing error or stderr data status = -- exit code, or negative code in case of abnormal termination extos.pfilter( data_in = data_in, -- string containing stdin data filename = filename, -- executable arg1 = arg1, -- first (non-zero) argument to executable arg2 = arg2, -- second argument to executable ... )
-- implemented in extos.c as -- static int extos_pfilter(lua_State *L)
text = -- human text representation of the boolean format.boolean( value, -- true, false or nil { true_as = true_text, -- text representing true false_as = false_text, -- text representing false nil_as = nil_text -- text representing nil } )
function format.boolean(value, options)
  local options = options or {}
  local true_text  = options.true_as or "Yes"  -- TODO: localization?
  local false_text = options.false_as or "No"  -- TODO: localization?
  local nil_text   = options.nil_as or ""
  if value == nil then
    return nil_text
  elseif value == false then
    return false_text
  elseif value == true then
    return true_text
  else
    error("Value passed to format.boolean(...) is neither a boolean nor nil.")
  end
end
    text =
format.currency(
  value,
  {
    nil_as                 = nil_text,                -- text to be returned for a nil value
    digits                 = digits,                  -- number of digits before the decimal point
    currency_precision     = currency_precision,      -- number of digits after decimal point
    currency_prefix        = currency_prefix,         -- prefix string, i.e. "$ "
    currency_decimal_point = currency_decimal_point,  -- string to be used as decimal point
    currency_suffix        = currency_suffix,         -- suffix string, i.e. " EUR"
    hide_unit              = hide_unit,               -- hide the currency unit, if true
    decimal_point          = decimal_point            -- used instead of 'currency_decimal_point', if 'hide_unit' is true
  }
)
    function format.currency(value, options)
  local options = table.new(options)
  local prefix
  local suffix
  if options.hide_unit then
    prefix = ""
    suffix = ""
    options.decimal_point =
      options.decimal_point or locale.get("decimal_point")
    options.precision =
      options.currency_precision or locale.get("currency_precision") or 2
  elseif
    options.currency_prefix or options.currency_suffix or
    options.currency_precision or options.currency_decimal_point
  then
    prefix                = options.currency_prefix or ''
    suffix                = options.currency_suffix or ''
    options.decimal_point = options.currency_decimal_point
    options.precision     = options.currency_precision or 2
  else
    prefix                = locale.get("currency_prefix") or ''
    suffix                = locale.get("currency_suffix") or ''
    options.decimal_point = locale.get("currency_decimal_point")
    options.precision     = locale.get("currency_precision") or 2
  end
  if value == nil then
    return options.nil_as or ''
  end
  return prefix .. format.decimal(value, options) .. suffix
end
    text = -- text with the value formatted as a date, according to the locale settings format.date( value, -- a date, a timestamp or nil { nil_as = nil_text -- text to be returned for a nil value } )
function format.date(value, options)
  local options = options or {}
  if value == nil then
    return options.nil_as or ""
  end
  if not (
    atom.has_type(value, atom.date) or
    atom.has_type(value, atom.timestamp)
  ) then
    error("Value passed to format.date(...) is neither a date, a timestamp, nor nil.")
  end
  if value.invalid then
    return "invalid"
  end
  local result = locale.get("date_format") or "YYYY-MM-DD"
  result = string.gsub(result, "YYYY", function()
    return format.decimal(value.year, { digits = 4 })
  end)
  result = string.gsub(result, "YY", function()
    return format.decimal(value.year % 100, { digits = 2 })
  end)
  result = string.gsub(result, "Y", function()
    return format.decimal(value.year)
  end)
  result = string.gsub(result, "MM", function()
    return format.decimal(value.month, { digits = 2 })
  end)
  result = string.gsub(result, "M", function()
    return format.decimal(value.month)
  end)
  result = string.gsub(result, "DD", function()
    return format.decimal(value.day, { digits = 2 })
  end)
  result = string.gsub(result, "D", function()
    return format.decimal(value.day)
  end)
  return result
end
    text = -- text with the value formatted as decimal number format.decimal( value, -- a number, a fraction or nil { nil_as = nil_text, -- text to be returned for a nil value digits = digits, -- digits before decimal point precision = precision, -- digits after decimal point decimal_shift = decimal_shift -- divide the value by 10^decimal_shift (setting true uses precision) } )
function format.decimal(value, options) -- TODO: more features local options = options or {} local special_chars = charset.get_data().special_chars local f if value == nil then return options.nil_as or "" elseif atom.has_type(value, atom.number) then f = value elseif atom.has_type(value, atom.fraction) then f = value.float else error("Value passed to format.decimal(...) is neither a number nor a fraction nor nil.") end local digits = options.digits local precision = options.precision or 0 local decimal_shift = options.decimal_shift or 0 if decimal_shift == true then decimal_shift = precision end f = f / 10 ^ decimal_shift local negative local absolute if f < 0 then absolute = -f negative = true else absolute = f negative = false end absolute = absolute + 0.5 / 10 ^ precision local int = math.floor(absolute) if not atom.is_integer(int) then if f > 0 then return "+" .. special_chars.inf_sign elseif f < 0 then return minus_sign .. special_chars.inf_sign else return "NaN" end end local int_str = tostring(int) if digits then while #int_str < digits do int_str = "0" .. int_str end end if precision > 0 then local decimal_point = options.decimal_point or locale.get('decimal_point') or '.' local frac_str = tostring(math.floor((absolute - int) * 10 ^ precision)) while #frac_str < precision do frac_str = "0" .. frac_str end assert(#frac_str == precision, "Assertion failed in format.float(...).") -- should not happen if negative then return special_chars.minus_sign .. int_str .. decimal_point .. frac_str elseif options.show_plus then return "+" .. int_str .. decimal_point .. frac_str else return int_str .. decimal_point .. frac_str end else if negative then return special_chars.minus_sign .. int elseif options.show_plus then return "+" .. int_str else return int_str end end end
text = -- text with the value formatted as a percentage format.percentage( value, -- a number, a fraction or nil { nil_as = nil_text -- text to be returned for a nil value digits = digits, -- digits before decimal point (of the percentage value) precision = precision, -- digits after decimal point (of the percentage value) decimal_shift = decimal_shift -- divide the value by 10^decimal_shift (setting true uses precision + 2) } )
function format.percentage(value, options)
  local options = table.new(options)
  local f
  if value == nil then
    return options.nil_as or ""
  elseif atom.has_type(value, atom.number) then
    f = value
  elseif atom.has_type(value, atom.fraction) then
    f = value.float
  else
    error("Value passed to format.percentage(...) is neither a number nor a fraction nor nil.")
  end
  options.precision = options.precision or 0
  if options.decimal_shift == true then
    options.decimal_shift = options.precision + 2
  end
  local suffix = options.hide_unit and "" or " %"
  return format.decimal(f * 100, options) .. suffix
end
    text = -- a string format.string( value, -- any value where tostring(value) gives a reasonable result { nil_as = nil_text, -- text to be returned for a nil value truncate_mode = "codepoints", -- performe truncating by counting UTF-8 codepoints ("codepoints") or Unicode grapheme clusters ("graphmeclusters") -- (currently only "codepoints" are supported and this option may be omitted) truncate_at = truncate_at, -- truncate string after the given number of UTF-8 codepoints (or Unicode grapheme clusters) truncate_suffix = truncate_suffix, -- string to append, if string was truncated (use boolean true for Unicode ellipsis) truncate_count_suffix = truncate_count_suffix -- if true, then the total length (including suffix) may not exceed the given length } )
local function codepoint_count(str)
  return #string.gsub(str, '[\128-\255][\128-\191]?[\128-\191]?[\128-\191]?', 'x')
end
local function codepoint_truncate(str, length)
  local byte_pos = 1
  local count = 0
  while count < length do
    b1, b2, b3, b4 = string.byte(str, byte_pos, byte_pos+3)
    if not b2 then
      break
    end
    b3 = b3 or 0
    b4 = b4 or 0
    if b1 >= 128 and b2 >= 128 and b2 <= 191 then
      if b3 >= 128 and b3 <= 191 then
        if b4 >= 128 and b4 <= 191 then
          byte_pos = byte_pos + 4
          count = count + 1
        elseif count + 1 < length and b4 < 128 then
          byte_pos = byte_pos + 4
          count = count + 2
        else
          byte_pos = byte_pos + 3
          count = count + 1
        end
      elseif count + 1 < length and b3 < 128 then
        if count + 2 < length and b4 < 128 then
          byte_pos = byte_pos + 4
          count = count + 3
        else
          byte_pos = byte_pos + 3
          count = count + 2
        end
      else
        byte_pos = byte_pos + 2
        count = count + 1
      end
    elseif count + 1 < length and b2 < 128 then
      if count + 2 < length and b3 < 128 then
        if count + 3 < length and b4 < 128 then
          byte_pos = byte_pos + 4
          count = count + 4
        else
          byte_pos = byte_pos + 3
          count = count + 3
        end
      else
        byte_pos = byte_pos + 2
        count = count + 2
      end
    else
      byte_pos = byte_pos + 1
      count = count + 1
    end
  end
  return string.sub(str, 1, byte_pos-1)
end
function format.string(str, options)
  local options = options or {}
  if str == nil then
    return options.nil_as or ""
  elseif options.truncate_at then
    str = tostring(str)
    -- TODO: Unicode grapheme cluster boundary detetion is not implemented
    -- (Unicode codepoints are used instead)
    local truncate_suffix = options.truncate_suffix
    if truncate_suffix == true then
      truncate_suffix = '\226\128\166'
    elseif not truncate_suffix then
      truncate_suffix = ''
    end
    if options.truncate_count_suffix and truncate_suffix then
      local suffix_length = codepoint_count(truncate_suffix)
      if codepoint_count(str) > options.truncate_at then
        return (
          codepoint_truncate(str, options.truncate_at - suffix_length) ..
          truncate_suffix
        )
      else
        return str
      end
    else
      if codepoint_count(str) > options.truncate_at then
        return codepoint_truncate(str, options.truncate_at) .. truncate_suffix
      else
        return str
      end
    end
  else
    return tostring(str)
  end
end
    text = -- text with the value formatted as a time, according to the locale settings format.time( value, -- a time, a timestamp or nil { nil_as = nil_text, -- text to be returned for a nil value hide_seconds = hide_seconds -- set to TRUE to hide seconds } )
function format.time(value, options)
  local options = options or {}
  if value == nil then
    return options.nil_as or ""
  end
  if not (
    atom.has_type(value, atom.time) or
    atom.has_type(value, atom.timestamp)
  ) then
    error("Value passed to format.time(...) is neither a time, a timestamp, nor nil.")
  end
  if value.invalid then
    return "invalid"
  end
  local result = locale.get("time_format") or "HH:MM{:SS}"
  if options.hide_seconds then
    result = string.gsub(result, "{[^{|}]*}", "")
  else
    result = string.gsub(result, "{([^{|}]*)}", "%1")
  end
  local am_pm
  local hour = value.hour
  result = string.gsub(result, "{([^{|}]*)|([^{|}]*)}", function(am, pm)
    if hour >= 12 then
      am_pm = pm
    else
      am_pm = am
    end
    return "{|}"
  end)
  if am_pm and hour > 12 then
    hour = hour - 12
  end
  if am_pm and hour == 0 then
    hour = 12
  end
  result = string.gsub(result, "HH", function()
    return format.decimal(hour, { digits = 2 })
  end)
  result = string.gsub(result, "MM", function()
    return format.decimal(value.minute, { digits = 2 })
  end)
  result = string.gsub(result, "SS", function()
    return format.decimal(value.second, { digits = 2 })
  end)
  if am_pm then
    result = string.gsub(result, "{|}", am_pm)
  end
  return result
end
    text = -- text with the given timestamp value formatted according to the locale settings format.timestamp( value, -- a timestamp or nil { nil_as = nil_text, -- text to be returned for a nil value hide_seconds = hide_seconds -- set to TRUE to hide seconds } )
function format.timestamp(value, options)
  if value == nil then
    return options.nil_as or ""
  end
  return format.date(value, options) .. " " .. format.time(value, options)
end
    locale.do_with( locale_options, -- table with locale information (as if passed to locale.set(...)) function() ... -- code to be executed with the given locale settings end )
function locale.do_with(locale_options, block)
  local old_data = {}
  for key, value in pairs(locale._current_data) do
    old_data[key] = value
  end
  locale.set(locale_options)
  block()
  old_data.reset = true
  locale.set(old_data)
end
    locale_setting = -- setting for the given localization category (could be of any type, depending on the category) locale.get( category -- string selecting a localization category, e.g. "lang" or "time", etc... )
function locale.get(category) return locale._current_data[category] end
locale.set(
  locale_options  -- table with locale categories as keys and their settings as values
)
    function locale.set(locale_options)
  if locale_options.reset then
    locale._current_data = {}
  end
  for key, value in pairs(locale_options) do
    if key ~= "reset" then
      locale._current_data[key] = value
    end
  end
end
    mondelefant.attach( mode, -- attachment type: "11" one to one, "1m" one to many, "m1" many to one data1, -- first database result list or object data2, -- second database result list or object key1, -- field name(s) in first result list or object used for attaching key2, -- field name(s) in second result list or object used for attaching ref1, -- name of reference field to be set in first database result list or object ref2 -- name of reference field to be set in second database result list or object )
function attach(mode, data1, data2, key1, key2, ref1, ref2)
  local many1, many2
  if mode == "11" then
    many1 = false
    many2 = false
  elseif mode == "1m" then
    many1 = false
    many2 = true
  elseif mode == "m1" then
    many1 = true
    many2 = false
  elseif mode == "mm" then
    many1 = true
    many2 = true
  else
    error("Unknown mode specified for 'mondelefant.attach'.")
  end
  local list1, list2
  if data1._type == "object" then
    list1 = { data1 }
  elseif data1._type == "list" then
    list1 = data1
  else
    error("First result data given to 'mondelefant.attach' is invalid.")
  end
  if data2._type == "object" then
    list2 = { data2 }
  elseif data2._type == "list" then
    list2 = data2
  else
    error("Second result data given to 'mondelefant.attach' is invalid.")
  end
  local hash1 = {}
  local hash2 = {}
  if ref2 then
    for i, row in ipairs(list1) do
      local key = attach_key(row, key1)
      local list = hash1[key]
      if not list then list = {}; hash1[key] = list end
      list[#list + 1] = row
    end
  end
  if ref1 then
    for i, row in ipairs(list2) do
      local key = attach_key(row, key2)
      local list = hash2[key]
      if not list then list = {}; hash2[key] = list end
      list[#list + 1] = row
    end
    for i, row in ipairs(list1) do
      local key = attach_key(row, key1)
      local matching_rows = hash2[key]
      if many2 then
        local list = data2._connection:create_list(matching_rows)
        list._class = data2._class
        row._ref[ref1] = list
      elseif matching_rows and #matching_rows == 1 then
        row._ref[ref1] = matching_rows[1]
      else
        row._ref[ref1] = false
      end
    end
  end
  if ref2 then
    for i, row in ipairs(list2) do
      local key = attach_key(row, key2)
      local matching_rows = hash1[key]
      if many1 then
        local list = data1._connection:create_list(matching_rows)
        list._class = data1._class
        row._ref[ref2] = list
      elseif matching_rows and #matching_rows == 1 then
        row._ref[ref2] = matching_rows[1]
      else
        row._ref[ref2] = false
      end
    end
  end
end
    db_handle, -- database handle, or nil in case of error errmsg, -- error message errcode = -- error code mondelefant.connect{ engine = "postgresql", -- no other engine is supported conninfo = conninfo, -- string passed directly to PostgreSQL's libpq host = host, -- hostname or directory with leading slash where Unix-domain socket resides hostaddr = hostaddr, -- IPv4, or IPv6 address if supported port = port, -- TCP port or socket file name extension dbname = dbname, -- name of database to connect with user = user, -- login name password = password, -- password connect_timeout = connect_timeout, -- seconds to wait for connection to be established. Zero or nil means infinite ... }
-- implemented in mondelefant_native.c as -- static int mondelefant_connect(lua_State *L)
db_class =               -- new database class (model)
mondelefant.new_class()
    -- implemented in mondelefant_native.c as -- static int mondelefant_new_class(lua_State *L)
db_list_or_object = -- first argument is returned mondelefant.set_class( db_list_or_object, -- database result list or object db_class -- database class (model) )
-- implemented in mondelefant_native.c as -- static int mondelefant_set_class(lua_State *L)
success = -- true, if mail has been sent successfully, otherwise false net.send_mail{ envelope_from = envelope_from, -- envelope from address, not part of mail headers from = from, -- From header address or table with 'name' and 'address' fields sender = sender, -- Sender header address or table with 'name' and 'address' fields reply_to = reply_to, -- Reply-To header address or table with 'name' and 'address' fields to = to, -- To header address or table with 'name' and 'address' fields cc = cc, -- Cc header address or table with 'name' and 'address' fields bcc = bcc, -- Bcc header address or table with 'name' and 'address' fields subject = subject, -- subject of e-mail multipart = multipart_type, -- "alternative", "mixed", "related", or nil content_type = content_type, -- only for multipart == nil, defaults to "text/plain" binary = binary, -- allow full 8-bit content content = content or { -- content as lua-string, or table in case of multipart { multipart = multipart_type, ..., content = content or { {...}, ... } }, { ... }, ... } }
function net.send_mail(args)
  local mail
  if type(args) == "string" then
    mail = args
  else
    mail = encode.mime.mail(args)
  end
  local envelope_from = args.envelope_from
  local command = {"/usr/sbin/sendmail", "-t", "-i"}
  if
    envelope_from and
    string.find(envelope_from, "^[0-9A-Za-z%.-_@0-9A-Za-z%.-_]+$")
  then
    command[#command+1] = "-f"
    command[#command+1] = envelope_from
  end
  local stdout, errmsg, status = extos.pfilter(mail, table.unpack(command))
  if not status then
    error("Error while calling sendmail: " .. errmsg)
  end
  if status == 0 then
    return true
  else
    return false
  end
end
    param.exchange( id, params )
function param.exchange(id, params)
  table.insert(param._saved, param._exchanged)
  param._exchanged = { id = id, params = params }
end
    value = -- value of the parameter casted to the chosen param_type param.get( key, -- name of the parameter param_type -- desired type of the returned value )
function param.get(key, param_type)
  local param_type = param_type or atom.string
  if param._exchanged then
    local value = param._exchanged.params[key]
    if value ~= nil and not atom.has_type(value, param_type) then
      error("Parameter has unexpected type.")
    end
    return value
  else
    local str         = cgi.params[key]
    local format_info = cgi.params[key .. "__format"]
    if not str then
      if not format_info then
        return nil
      end
      str = ""
    end
    return param._get_parser(format_info, param_type)(str)
  end
end
    params = param.get_all_cgi()
-- TODO: Remove this function.
function param.get_all_cgi()
  return request.get_param_strings()
end
    value = -- value of the id casted to the chosen param_type param.get_id( param_type -- desired type of the returned value )
function param.get_id(param_type)
  local param_type = param_type or atom.integer
  if param._exchanged then
    local value = param._exchanged.id
    if value ~= nil and not atom.has_type(value, param_type) then
      error("Parameter has unexpected type.")
    end
    return value
  else
    local str = request.get_id_string()
    if str then
      return param._get_parser(nil, param_type)(str)
    else
      return nil
    end
  end
end
    value =             -- id as string (or other type after if params.exchange(...) has been used), or nil
param.get_id_cgi()
    -- TODO: Remove this function.
function param.get_id_cgi()
  return request.get_id_string()
end
    values = -- list of values casted to the chosen param_type param.get_list( key, -- name of the parameter without "[]" suffix param_type, -- desired type of the returned values )
function param.get_list(key, param_type)
  local param_type = param_type or atom.string
  if param._exchanged then
    local values = param._exchanged.params[key] or {}
    if type(values) ~= "table" then
      error("Parameter has unexpected type.")
    end
    for idx, value in ipairs(values) do
      if not atom.has_type(value, param_type) then
        error("Element of parameter list has unexpected type.")
      end
    end
    return values
  else
    local format_info = cgi.params[key .. "__format"]
    local parser = param._get_parser(format_info, param_type)
    local raw_values = cgi.params[key .. "[]"]
    local values = {}
    if raw_values then
      for idx, value in ipairs(raw_values) do
        values[idx] = parser(raw_values[idx])
      end
    end
    return values
  end
end
    params = param.get_param_strings()
function request.get_param_strings()
  local t = {}
  for key, value in pairs(request._params) do
    if type(value) == 'table' then
      t[key] = table.new(value)
    else
      t[key] = value
    end
  end
  return t
end
    for index, -- index variable counting up from 1 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 in param.iterate( prefix -- prefix to be followed by an index in square brackets and another key ) do ... end
function param.iterate(prefix)
  local length = param.get(prefix .. "[len]", atom.integer) or 0
  if not atom.is_integer(length) then
    error("List length is not a valid integer or nil.")
  end
  local index = 0
  return function()
    index = index + 1
    if index <= length then
      return index, prefix .. "[" .. index .. "]"
    end
  end
end
    param.restore()
function param.restore()
  local saved = param._saved
  local previous = saved[#saved]
  saved[#saved] = nil
  if previous == nil then
    error("Tried to restore id and params without having exchanged it before.")
  end
  param._exchanged = previous
end
    param.update_relationship{
  param_name        = param_name,        -- name of request GET/POST request parameters containing primary keys for model B
  id                = id,                -- value of the primary key for model A
  connecting_model  = connecting_model,  -- model used for creating/deleting entries referencing both model A and B
  own_reference     = own_reference,     -- field name for foreign key in the connecting model referencing model A
  foreign_reference = foreign_reference  -- field name for foreign key in the connecting model referencing model B
}
    function param.update_relationship(args) local param_name = args.param_name local id = args.id local connecting_model = args.connecting_model local own_reference = args.own_reference local foreign_reference = args.foreign_reference local selected_ids = param.get_list(param_name, atom.integer) -- TODO: support other types than integer too local db = connecting_model:get_db_conn() local table = connecting_model:get_qualified_table() if #selected_ids == 0 then db:query{ 'DELETE FROM ' .. table .. ' WHERE "' .. own_reference .. '" = ?', args.id } else local selected_ids_sql = { sep = ", " } for idx, value in ipairs(selected_ids) do selected_ids_sql[idx] = {"?::int8", value} end db:query{ 'DELETE FROM ' .. table .. ' WHERE "' .. own_reference .. '" = ?' .. ' AND NOT "' .. foreign_reference .. '" IN ($)', args.id, selected_ids_sql } -- TODO: use VALUES SQL command, instead of this dirty array trick db:query{ 'INSERT INTO ' .. table .. ' ("' .. own_reference .. '", "' .. foreign_reference .. '")' .. ' SELECT ?, "subquery"."foreign" FROM (' .. 'SELECT (ARRAY[$])[i] AS "foreign"' .. ' FROM generate_series(1, ?) AS "dummy"("i")' .. ' EXCEPT SELECT "' .. foreign_reference .. '" AS "foreign"' .. ' FROM ' .. table .. ' WHERE "' .. own_reference .. '" = ?' .. ') AS "subquery"', args.id, selected_ids_sql, #selected_ids, args.id } end end
param.update( record, -- database record to be updated key_and_field_name1, -- name of CGI parameter and record field key_and_field_name2, -- another name of a CGI parameter and record field { key3, -- name of CGI parameter field_name3 -- name of record field } )
function param.update(record, mapping_info, ...)
  if not mapping_info then
    return
  end
  assert(record, "No record given for param.update(...).")
  assert(record._class, "Record passed to param.update(...) has no _class attribute.")
  local key, field_name
  if type(mapping_info) == "string" then
    key        = mapping_info
    field_name = mapping_info
  else
    key        = mapping_info[1]
    field_name = mapping_info[2]
  end
  assert(key, "No key given in parameter of param.update(...).")
  assert(field_name, "No field name given in parameter of param.update(...).")
  local column_info = record._class:get_columns()[field_name]
  if not column_info then
    error('Type of column "' .. field_name .. '" is unknown.')
  end
  local new_value = param.get(key, column_info.type)
  if new_value ~= record[field_name] then
    record[field_name] = new_value
  end
  return param.update(record, ...)  -- recursivly process following arguments
end
    request.force_absolute_baseurl()
function request.force_absolute_baseurl() request._force_absolute_baseurl = true end
request.forward{
  module = module,  -- module name
  view   = view     -- view name
}
    function request.forward(args)
  if request.is_rerouted() then
    error("Tried to forward after another forward or redirect.")
  end
  request._forward = args
  trace.forward { module = args.module, view = args.view }
end
    route_info =             -- table with 'module' and 'view' field
request.get_404_route()
    function request.get_404_route() return request._404_route end
baseurl = request.get_absolute_baseurl()
function request.get_absolute_baseurl()
  if request._absolute_baseurl then
    return request._absolute_baseurl
  else
    error("Absolute base URL is unknown. It should be set in the configuration by calling request.set_absolute_baseurl(...).")
  end
end
    action_name = request.get_action()
function request.get_action()
  if request._forward_processed then
    return nil
  else
    return request._action
  end
end
    path =                      -- path to directory of application with trailing slash
request.get_app_basepath()
    function request.get_app_basepath() return request._app_basepath end
app_name = request.get_app_name()
function request.get_app_name()
  return os.getenv("WEBMCP_APP_NAME") or 'main'
end
    config_name = request.get_config_name()
function request.get_config_name()
  return os.getenv("WEBMCP_CONFIG_NAME")
end
    secret =                   -- secret string, previously set with request.set_csrf_secret(...)
request.get_csrf_secret()
    function request.get_csrf_secret(secret) return request._csrf_secret end
id_string = request.get_id_string()
function request.get_id_string() return request._id end
slot_idents =                    -- list of names of slots to be returned as JSON data
request.get_json_request_slots()
    function request.get_json_request_slots(slot_idents)
  if not cgi then return end
  local slot_idents = cgi.params["_webmcp_json_slots[]"]
  if slot_idents and not request._json_requests_allowed then
    error("JSON requests have not been allowed using request.set_allowed_json_request_slots(...).")
  end
  return slot_idents
end
    module_name = request.get_module()
function request.get_module()
  if request._forward_processed then
    return request._forward.module or request._module or 'index'
  else
    return request._module or 'index'
  end
end
    perm_params =              -- table containing permanent parameters
request.get_perm_params()
    function request.get_perm_params()
  -- NOTICE: it's important to return a copy here
  return table.new(request._perm_params)
end
    redirect_data = request.get_redirect_data()
function request.get_redirect_data() return request._redirect end
baseurl = request.get_relative_baseurl()
function request.get_relative_baseurl()
  if request._force_absolute_baseurl then
    return (request.get_absolute_baseurl())
  else
    return request._relative_baseurl
  end
end
    status_string = request.get_status()
function request.get_status() return request._status end
view_name = request.get_view()
function request.get_view()
  if request._forward_processed then
    return request._forward.view or 'index'
  else
    if request._view then
      local suffix = request._suffix or "html"
      if suffix == "html" then
        return request._view
      else
        return request._view .. "." .. suffix
      end
    elseif not request._action then
      return 'index'
    else
      return nil
    end
  end
end
    is_404 =          -- boolean
request.is_404()
    function request.is_404() return request._is_404 end
bool =             -- true, if the current request is a POST request
request.is_post()
    function request.is_post()
  if request._forward_processed then
    return false
  else
    return cgi.method == "POST"
  end
end
    request.process_forward()
function request.process_forward()
  if request._forward then
    request._forward_processed = true
    trace.request{
      module = request.get_module(),
      view   = request.get_view()
    }
  end
end
    request.redirect{
  module = module,  -- module name
  view   = view,    -- view name
  id     = id,      -- optional id for view
  params = params,  -- optional view parameters
  anchor = anchor   -- anchor in URL
}
    function request.redirect(args) -- TODO: support redirects to external URLs too -- (needs fixes in the trace system as well) local module = args.module local view = args.view local id = args.id local params = args.params or {} local anchor = args.anchor or nil if type(module) ~= "string" then error("No module string passed to request.redirect{...}.") end if type(view) ~= "string" then error("No view string passed to request.redirect{...}.") end if type(params) ~= "table" then error("Params array passed to request.redirect{...} is not a table.") end if anchor and type(anchor) ~= "string" then error("Anchor passed to request.redirect{...} must be a string or nil.") end if request.is_rerouted() then error("Tried to redirect after another forward or redirect.") end request._redirect = { module = module, view = view, id = id, params = params, anchor = anchor } trace.redirect{ module = args.module, view = args.view } end
request.set_404_route{
  module = module,  -- module name
  view   = view     -- view name
}
    function request.set_404_route(tbl) request._404_route = tbl end
request.set_absolute_baseurl(
  url                          -- Base URL of the application
)
    function request.set_absolute_baseurl(url)
  if string.find(url, "/$") then
    request._absolute_baseurl = url
  else
    request._absolute_baseurl = url .. "/"
  end
end
    request.set_allowed_json_request_slots(
  slot_idents                            -- list of names of slots which can be requested in JSON format
)
    function request.set_allowed_json_request_slots(slot_idents)
  if cgi then  -- do nothing, when being in interactive mode
    local hash = {}
    for idx, slot_ident in ipairs(slot_idents) do
      hash[slot_ident] = true
    end
    if cgi.params["_webmcp_json_slots[]"] then
      for idx, slot_ident in ipairs(cgi.params["_webmcp_json_slots[]"]) do
        if not hash[slot_ident] then
          error('Requesting slot "' .. slot_ident .. '" is forbidden.')
        end
      end
    end
    request._json_requests_allowed = true
  end
end
    request.set_cookie{
  name   = name,     -- name of cookie
  value  = value,    -- value of cookie
  domain = domain,   -- optional domain domain where cookie is transmitted
  path   = path,     -- optional path where cookie is transmitted, defaults to application base
  secure = secure    -- optional boolean, indicating if cookie should only be transmitted over HTTPS
}
    function request.set_cookie(args)
  local path = args.path
  if not path then
    path = string.match(
      request.get_absolute_baseurl(),
      "://[^/]*(.*)"
    )
    if path == nil or path == "" then
      path = "/"
    end
  end
  local secure = args.secure
  if secure == nil then
    if string.find(
      string.lower(request.get_absolute_baseurl()),
      "^https://"
    ) then
      secure = true
    else
      secure = false
    end
  end
  cgi.set_cookie{
    name   = args.name,
    value  = args.value,
    domain = args.domain,
    path   = path,
    secure = secure
  }
end
    request.set_csrf_secret(
  secret                 -- secret random string
)
    function request.set_csrf_secret(secret)
  if
    request.get_action() and
    cgi.params._webmcp_csrf_secret ~= secret
  then
    error("Cross-Site Request Forgery attempt detected");
  end
  request._csrf_secret = secret
end
    request.set_perm_param( key, -- name of parameter value -- value of parameter )
function request.set_perm_param(key, value) request._perm_params[key] = value end
request.set_status(
  str                -- string containing a HTTP status code, e.g. "404 Not Found"
)
    function request.set_status(str)
  if str then
    local t = type(str)
    if type(str) == "number" then
      str = tostring(str)
    elseif type(str) ~= "string" then
      error("request.set_status(...) must be called with a string as parameter.")
    end
    request._status = str
  else
    request._status = nil
  end
end
    bool =                 -- true, if request.forard{...} or request.redirect{...} has been called before.
request_is_rerouted()
    function request.is_rerouted()
  if
    (request._forward and not request._forward_processed) or
    request._redirect
  then
    return true
  else
    return false
  end
end
    result1, -- result of first command result2, -- result of second command ... = <db_handle>:query( command1, -- first command (to be processed by "assemble_command" method) mode1, -- mode for first command: "list", "object" or "opt_object" command2, -- second command (to be processed by "assemble_command" method) mode2, -- mode for second command: "list", "object" or "opt_object" .. )
-- implemented in mondelefant_native.c as -- static int mondelefant_conn_query(lua_State *L)
rocketcgi.add_header( string_part1, -- string string_part2, -- optional second part of string to be concatted ... )
function add_header(...)
  if data_sent then
    error("Can not add header after data has been sent.", 2)
  end
  io.stdout:write(...)
  io.stdout:write("\r\n")
end
    rocketcgi.redirect(
  status             -- Absolute URL to redirect the browser to
)
    function redirect(location)
  set_status("303 See Other")
  add_header("Location: ", location)
end
    rocketcgi.send_data( string_part1, -- string string_part2, -- optional second part of string to be concatted ... )
function send_data(...)
  if not data_sent then
    io.stdout:write("\r\n")
    data_sent = true
  end
  io.stdout:write(...)
end
    rocketcgi.set_content_type(
  content_type               -- MIME content type
)
    function set_content_type(content_type)
  add_header("Content-Type: ", content_type)
end
    rocketcgi.set_cookie{
  name   = name,       -- name of cookie
  value  = value,      -- value of cookie
  domain = domain,     -- domain where cookie is transmitted
  path   = path,       -- path where cookie is transmitted
  secure = secure      -- boolean, indicating if cookie should only be transmitted over HTTPS
}
    function set_cookie(args)
  assert(string.find(args.name, "^[0-9A-Za-z%%._~-]+$"), "Illegal cookie name")
  assert(string.find(args.value, "^[0-9A-Za-z%%._~-]+$"), "Illegal cookie value")
  local parts = {"Set-Cookie: " .. args.name .. "=" .. args.value}
  if args.domain then
    assert(
      string.find(args.path, "^[0-9A-Za-z%%/._~-]+$"),
      "Illegal cookie domain"
    )
    parts[#parts+1] = "domain=" .. args.domain
  end
  if args.path then
    assert(
      string.find(args.path, "^[0-9A-Za-z%%/._~-]+$"),
      "Illegal cookie path"
    )
    parts[#parts+1] = "path=" .. args.path
  end
  if args.secure then
    parts[#parts+1] = "secure"
  end
  add_header(table.concat(parts, "; "))
end
    rocketcgi.set_status(
  status               -- Status code and description, e.g. "404 Not Found"
)
    function set_status(status)
  add_header("Status: ", status)
end
    blob =           -- string for later usage with slot.restore_all(...)
slot.dump_all()
    local function encode(str)
  return (
    string.gsub(
      str,
      "[=;%[%]]",
      function(char)
        if char == "=" then return "[eq]"
        elseif char == ";" then return "[s]"
        elseif char == "[" then return "[o]"
        elseif char == "]" then return "[c]"
        else end
      end
    )
  )
end
function slot.dump_all()
  local blob_parts = {}
  for key in pairs(slot._data) do
    if type(key) == "string" then
      local value = slot.get_content(key)
      if value ~= "" then
        blob_parts[#blob_parts + 1] = encode(key) .. "=" .. encode(value)
      end
    end
  end
  return table.concat(blob_parts, ";")
end
    content =
slot.get_content(
  slot_ident       -- name of the slot
)
    function slot.get_content(slot_ident)
  local slot_data = slot._data[slot_ident]
  if #slot_data.string_fragments > 1 then
    local str = table.concat(slot_data.string_fragments)
    slot_data.string_fragments = { str }
    return str
  else
    return slot_data.string_fragments[1] or ""
  end
end
    content_type =           -- content-type as selected with slot.set_layout(...)
slot.get_content_type()
    function slot.get_content_type() return slot._content_type or 'text/html; charset=UTF-8' end
state_table =           -- table for saving a slot's state
slot.get_state_table()
    function slot.get_state_table() return slot.get_state_table_of(slot._active_slot) end
state_table = -- table for saving the slot's state slot.get_state_table_of( slot_ident -- name of a slot )
function slot.get_state_table_of(slot_ident) return slot._data[slot_ident].state_table end
slot.put( string1, -- string to be written into the active slot string2, -- another string to be written into the active slot ... )
function slot.put(...) return slot.put_into(slot._active_slot, ...) end
slot.put_into( slot_ident -- name of a slot string1, -- string to be written into the named slot string2, -- another string to be written into the named slot ... )
function slot.put_into(slot_ident, ...)
  local t = slot._data[slot_ident].string_fragments
  for i = 1, math.huge do
    local v = select(i, ...)
    if v == nil then break end
    t[#t + 1] = v
  end
end
    output =              -- document/data to be sent to the web browser
slot.render_layout()
    function slot.render_layout()
  if slot._current_layout then
    local layout_file = assert(io.open(
      encode.file_path(
        request.get_app_basepath(),
        'app',
        request.get_app_name(),
        '_layout',
        slot._current_layout .. '.html'
      ),
      'r'
    ))
    local layout = layout_file:read("*a")
    io.close(layout_file)
    -- render layout
    layout = string.gsub(layout, "__BASEURL__/?", request.get_relative_baseurl())  -- TODO: find a better placeholder than __BASEURL__ ?
    layout = string.gsub(layout, '<!%-%- *WEBMCP +SLOT +([^ ]+) *%-%->',
      function(slot_ident)
        if #slot.get_content(slot_ident) > 0 then
          return '<div class="slot_' .. slot_ident .. '" id="slot_' .. slot_ident .. '">' .. slot.get_content(slot_ident).. '</div>'
        else
          return ''
        end
      end
    )
    layout = string.gsub(layout, '<!%-%- *WEBMCP +SLOTNODIV +([^ ]+) *%-%->',
      function(slot_ident)
        if #slot.get_content(slot_ident) > 0 then
          return slot.get_content(slot_ident)
        else
          return ''
        end
      end
    )
    return layout
  else
    return slot.get_content("data")
  end
end
    slot.reset(
  slot_ident  -- name of a slot to be emptied
)
    function slot.reset(slot_ident) slot._data[slot_ident] = nil end
slot.reset_all{
  except =  except  -- Reset all slots, except slots named in this list
}
    function slot.reset_all(args)
  local saved = {}
  if args and args.except then
    for i, key in ipairs(args.except) do
      saved[key] = slot._data[key]
    end
  end
  slot._data = setmetatable({}, slot._data_metatable)
  if saved then
    for key, value in pairs(saved) do
      slot._data[key] = value
    end
  end
end
    slot.restore_all(
  blob             -- string as returned by slot.dump_all()
)
    local function decode(str)
  return (
    string.gsub(
      str,
      "%[[a-z]+%]",
      function(char)
        if char == "[eq]" then return "="
        elseif char == "[s]" then return ";"
        elseif char == "[o]" then return "["
        elseif char == "[c]" then return "]"
        else end
      end
    )
  )
end
function slot.restore_all(blob)
  slot.reset_all()
  for encoded_key, encoded_value in string.gmatch(blob, "([^=;]*)=([^=;]*)") do
    local key, value = decode(encoded_key), decode(encoded_value)
    slot._data[key].string_fragments = { value }
  end
end
    slot.select( slot_ident, -- name of a slot function() ... -- code to be executed using the named slot end )
function slot.select(slot_ident, block) local old_slot = slot._active_slot slot._active_slot = slot_ident block() slot._active_slot = old_slot end
slot.set_layout( layout_ident, -- name of layout or nil for binary data in slot named "data" content_type -- content-type to be sent to the browser, or nil for default )
function slot.set_layout(layout_ident, content_type) slot._current_layout = layout_ident slot._content_type = content_type end
slot_content =
slot.use_temporary(
  function()
    ...
  end
)
    function slot.use_temporary(block)
  local old_slot = slot._active_slot
  local temp_slot_reference = {}  -- just a unique reference
  slot._active_slot = temp_slot_reference
  block()
  slot._active_slot = old_slot
  local result = slot.get_content(temp_slot_reference)
  slot.reset(temp_slot_reference)
  return result
end
    cloned_table = -- newly generated table table.new( table_or_nil -- keys of a given table will be copied to the new table )
function table.new(tbl)
  new_tbl = {}
  if tbl then
    for key, value in pairs(tbl) do
      new_tbl[key] = value
    end
  end
  return new_tbl
end
    blob = -- loaded string tempstore.pop( key -- key as returned by tempstore.save(...) )
function tempstore.pop(key)
  local filename = encode.file_path(
    request.get_app_basepath(), 'tmp', "tempstore-" .. key .. ".tmp"
  )
  local file = io.open(filename, "r")
  if not file then return nil end
  local blob = file:read("*a")
  io.close(file)
  os.remove(filename)
  return blob
end
    key = -- key to be used with tempstore.pop(...) to regain the stored string tempstore.save( blob -- string to be stored )
function tempstore.save(blob)
  local key = multirand.string(26, "123456789bcdfghjklmnpqrstvwxyz");
  local filename = encode.file_path(
    request.get_app_basepath(), 'tmp', "tempstore-" .. key .. ".tmp"
  )
  local file = assert(io.open(filename, "w"))
  file:write(blob)
  io.close(file)
  return key
end
    timestamp.invalid
timestamp.invalid = timestamp:_create{
  tsec = not_a_number,
  year = not_a_number, month = not_a_number, day = not_a_number,
  hour = not_a_number, minute = not_a_number, second = not_a_number,
  invalid = true
}
    trace.debug(...)
  ...     -- messages to be inserted into the trace log
    function trace.debug(...)
  if not trace._disabled then
    local message = ""
    local arg = {...}
    for i= 1,#arg,1 do
      message = message..tostring(arg[i]).." "
    end
    trace._new_entry{ type = "debug", message = message }
  end
end
    trace.debug_table(
  message     -- message to be inserted into the trace log
)
    function trace.debug_table(table)
  trace._new_entry{ type = "debug_table", message = table }
end
    trace.debug_trace(
  message     -- optional message to add
)
    function trace.debug_trace(message)
  trace._new_entry{ type = "traceback", message = tostring(debug.traceback(message or "", 2)) }
end
    trace.disable()
function trace.disable() trace._disabled = true end
trace.enter_action{
  module = module,
  action = action
}
    function trace.enter_action(args)
  if not trace._disabled then
    local module = args.module
    local action = args.action
    if type(module) ~= "string" then
      error("No module string passed to trace.enter_action{...}.")
    end
    if type(action) ~= "string" then
      error("No action string passed to trace.enter_action{...}.")
    end
    trace._open_section{ type = "action", module = module, action = action }
  end
end
    trace.enter_config{
  name = name
}
    function trace.enter_config(args)
  if not trace._disabled then
    local name = args.name
    if type(name) ~= "string" then
      error("No name string passed to trace.enter_config{...}.")
    end
    trace._open_section{ type = "config", name = name }
  end
end
    trace.enter_filter{
  path = path
}
    function trace.enter_filter(args)
  if not trace._disabled then
    local path = args.path
    if type(path) ~= "string" then
      error("No path string passed to trace.enter_filter{...}.")
    end
    trace._open_section{ type = "filter", path = path }
  end
end
    trace.enter_view{
  module = module,
  view   = view
}
    function trace.enter_view(args)
  if not trace._disabled then
    local module = args.module
    local view   = args.view
    if type(module) ~= "string" then
      error("No module passed to trace.enter_view{...}.")
    end
    if type(view) ~= "string" then
      error("No view passed to trace.enter_view{...}.")
    end
    trace._open_section{ type = "view", module = module, view = view }
  end
end
    trace.error{
}
    function trace.error(args)
  if not trace._disabled then
    trace._new_entry { type = "error" }
    local closed_section = trace._close_section()
    closed_section.hard_error = true  -- TODO: not used, maybe remove
    trace._stack = { trace._tree }
  end
end
    trace.exectime{
  real = real,   -- physical time in seconds
  cpu  = cpu     -- CPU time in seconds
}
    function trace.exectime(args)
  if not trace._disabled then
    local real = args.real
    local cpu  = args.cpu
    if type(real) ~= "number" then
      error("Called trace.exectime{...} without numeric 'real' argument.")
    end
    if type(cpu) ~= "number" then
      error("Called trace.exectime{...} without numeric 'cpu' argument.")
    end
    trace._new_entry{ type = "exectime", real = args.real, cpu = args.cpu }
  end
end
    trace.execution_return{
  status = status        -- optional status
}
    function trace.execution_return(args)
  if not trace._disabled then
    local status
    if args then
      status = args.status
    end
    if status and type(status) ~= "string" then
      error("Status passed to trace.execution_return{...} is not a string.")
    end
    local closed_section = trace._close_section()
    closed_section.status = status
  end
end
    trace.forward{
  module = module,
  view   = view
}
    function trace.forward(args)
  if not trace._disabled then
    local module = args.module
    local view   = args.view
    if type(module) ~= "string" then
      error("No module string passed to trace.forward{...}.")
    end
    if type(view) ~= "string" then
      error("No view string passed to trace.forward{...}.")
    end
    trace._new_entry{ type = "forward", module = module, view = view }
  end
end
    disabled =           -- boolean indicating if trace system is disabled
trace.is_disabled()
    function trace.is_disabled() return trace._disabled end
trace.redirect{
  module = module,
  view   = view
}
    function trace.redirect(args)
  if not trace._disabled then
    local module = args.module
    local view   = args.view
    if type(module) ~= "string" then
      error("No module string passed to trace.redirect{...}.")
    end
    if type(view) ~= "string" then
      error("No view string passed to trace.redirect{...}.")
    end
    trace._new_entry{ type = "redirect", module = module, view = view }
  end
end
    trace.render()
function trace.render()
  if not trace._disabled then
    -- TODO: check if all sections are closed?
    trace._render_sub_tree(trace._tree)
  end
end
    trace.request{
  module = module,
  view   = view,
  action = action
}
    function trace.request(args)
  if not trace._disabled then
    local module       = args.module
    local view         = args.view
    local action       = args.action
    if type(module) ~= "string" then
      error("No module string passed to trace.request{...}.")
    end
    if view and action then
      error("Both view and action passed to trace.request{...}.")
    end
    if not (view or action) then
      error("Neither view nor action passed to trace.request{...}.")
    end
    if view and type(view) ~= "string" then
      error("No view string passed to trace.request{...}.")
    end
    if action and type(action) ~= "string" then
      error("No action string passed to trace.request{...}.")
    end
    trace._new_entry{
      type = "request",
      module       = args.module,
      view         = args.view,
      action       = args.action
    }
  end
end
    trace.restore_slots{
}
    function trace.restore_slots(args)
  if not trace._disabled then
    trace._new_entry{ type = "restore_slots" }
  end
end
    trace.sql{
  command        = command,        -- executed SQL command as string
  error_position = error_position  -- optional position in bytes where an error occurred
}
    -- TODO: automatic use of this function?
function trace.sql(args)
  if not trace._disabled then
    local command = args.command
    local error_position = args.error_position
    if type(command) ~= "string" then
      error("No command string passed to trace.sql{...}.")
    end
    if error_position and type(error_position) ~= "number" then
      error("error_position must be a number.")
    end
    trace._new_entry{
      type = "sql",
      command = command,
      error_position = error_position
    }
  end
end
    ui._partial_load_js(
  {
    module = module,
    view   = view,
    id     = id,
    params = params,
    target = target
  },
  mode = mode
}
    function ui._partial_load_js(args, mode)
  local args = args or {}
  local module
  local view
  local id
  local params = {}
  local target
  if args.view and args.target then
    module = args.module
    view   = args.view
    id     = args.id
    target = args.target
  elseif not args.view and not args.target then
    if not ui._partial_state then
      return nil
    end
    module = ui._partial_state.module
    view   = ui._partial_state.view
    id     = ui._partial_state.id
    target = ui._partial_state.target
  else
    error("Unexpected arguments passed to ui._partial_load_js{...}")
  end
  if ui._partial_state then
    -- TODO: do this only if args.view and args.target are unset!?
    if ui._partial_state.params then
      for key, value in pairs(ui._partial_state.params) do
        params[key] = value
      end
    end
    for param_name, dummy in pairs(ui._partial_state.param_name_hash) do
      params[param_name] = cgi.params[param_name]
    end
  end
  if args.params then
    for key, value in pairs(args.params) do
      params[key] = value
    end
  end
  local encoded_url = encode.json(
    encode.url{
      module = module,
      view   = view,
      id     = id,
      params = params
    }
  )
  if mode == "form_normal" then
    -- NOTE: action in "action_mode" refers to WebMCP actions, while action
    -- in "this.action" refers to the action attribute of HTML forms
    slot.put('this.action = ', encoded_url, '; ')
  end
  return slot.use_temporary(function()
    slot.put(
      'partialMultiLoad({',
        -- mapping:
        '"trace": "trace", "system_error": "system_error", ',
        encode.json(target), ': "default" }, ',
        -- tempLoadingContents:
        '{}, ',
        -- failureContents:
        '"error", ',
        -- url:
        (mode == "form_normal" or mode == "form_action") and (
          'this'
        ) or (
          encoded_url
        ), ', ',
        -- urlParams:
        '"_webmcp_json_slots[]=default&_webmcp_json_slots[]=trace&_webmcp_json_slots[]=system_error", ',
        -- postParams:
        '{}, ',
        -- successHandler:
        'function() {}, ',
        -- failureHandler:
        'function() {} ',
      '); ',
      'return false;'
    )
  end)
end
    ui.add_partial_param_names( name_list )
function ui.add_partial_param_names(name_list)
  if ui._partial_state then
    for idx, param_name in ipairs(name_list) do
      ui._partial_state.param_name_hash[param_name] = true
    end
  end
end
    ui.anchor{
  name    = name,    -- name of anchor
  attr    = attr,    -- table of HTML attributes
  content = content  -- string to be HTML encoded, or function to be executed
}
    function ui.anchor(args)
  local attr = table.new(args.attr)
  attr.name = args.name
  return ui.tag{
    tag = "a",
    attr = attr,
    content = args.content
  }
end
    ui.autofield{
  name           = name,               -- field name (also used by default as HTML name)
  html_name      = html_name,          -- explicit HTML name to be used instead of 'name'
  value          = nihil.lift(value),  -- initial value, nil causes automatic lookup of value, use nihil.lift(nil) for nil
  container_attr = container_attr,     -- extra HTML attributes for the container (div) enclosing field and label
  attr           = attr,               -- extra HTML attributes for the field
  label          = label,              -- text to be used as label for the input field
  label_attr     = label_attr,         -- extra HTML attributes for the label
  readonly       = readonly_flag       -- set to true, to force read-only mode
  record         = record,             -- record to be used, defaults to record given to ui.form{...}
  ...                                  -- extra arguments for applicable ui.field.* helpers
}
    function ui.autofield(args)
  local args = table.new(args)
  assert(args.name, "ui.autofield{...} needs a field 'name'.")
  if not args.record then
    local slot_state = slot.get_state_table()
    if not slot_state then
      error("ui.autofield{...} was called without an explicit record to be used, and is also not called inside a form.")
    elseif not slot_state.form_record then
      error("ui.autofield{...} was called without an explicit record to be used, and the form does not have a record assigned either.")
    else
      args.record = slot_state.form_record
    end
  end
  local class = args.record._class
  assert(class, "Used ui.autofield{...} on a record with no class information stored in the '_class' attribute.")
  local fields, field_info, ui_field_type, ui_field_options
  fields = class.fields
  if fields then
    field_info = fields[args.name]
  end
  if field_info then
    ui_field_type    = field_info.ui_field_type
    ui_field_options = table.new(field_info.ui_field_options)
  end
  if not ui_field_type then
    ui_field_type = "text"
  end
  if not ui_field_options then
    ui_field_options = {}
  end
  local ui_field_func = ui.field[ui_field_type]
  if not ui_field_func then
    error(string.format("Did not find ui.field helper of type %q.", ui_field_type))
  end
  for key, value in pairs(ui_field_options) do
    if args[key] == nil then
      args[key] = value
    end
  end
  return ui_field_func(args)
end
    ui.container{
  auto_args = auto_args,
  attr          = attr,           -- HTML attributes for the surrounding div or fieldset
  label         = label,          -- text to be used as label
  label_for     = label_for,      -- DOM id of element to which the label should refer
  label_attr    = label_attr,     -- extra HTML attributes for a label tag
  legend        = legend,         -- text to be used as legend
  legend_attr   = legend_attr,    -- HTML attributes for a legend tag
  content_first = content_first,  -- set to true to place label or legend after the content
  content = function()
    ...
  end
}
    function ui.container(args)
  local attr, label, label_attr, legend, legend_attr, content
  local auto_args = args.auto_args
  if auto_args then
    attr        = auto_args.container_attr
    label       = auto_args.label
    label_attr  = auto_args.label_attr
    legend      = auto_args.legend
    legend_attr = auto_args.legend_attr
    if label and auto_args.attr and auto_args.attr.id then
      label_attr = table.new(label_attr)
      label_attr["for"] = auto_args.attr.id
    end
  else
    attr        = args.attr
    label       = args.label
    label_attr  = args.label_attr or {}
    legend      = args.legend
    legend_attr = args.legend_attr
    content     = content
    if args.label_for then
      label_attr["for"] = args.label_for
    end
  end
  local content = args.content
  if label and not legend then
    return ui.tag {
      tag     = "div",
      attr    = attr,
      content = function()
        if not args.content_first then
          ui.tag{ tag = "label", attr = label_attr, content = label }
          slot.put(" ")
        end
        if type(content) == "function" then
          content()
        elseif content then
          slot.put(encode.html(content))
        end
        if args.content_first then
          slot.put(" ")
          ui.tag{ tag = "label", attr = label_attr, content = label }
        end
      end
    }
  elseif legend and not label then
    return ui.tag {
      tag     = "fieldset",
      attr    = attr,
      content = function()
        if not args.content_first then
          ui.tag{ tag = "legend", attr = legend_attr, content = legend }
          slot.put(" ")
        end
        if type(content) == "function" then
          content()
        elseif content then
          slot.put(encode.html(content))
        end
        if args.content_first then
          slot.put(" ")
          ui.tag{ tag = "legend", attr = legend_attr, content = legend }
        end
      end
    }
  elseif fieldset and label then
    error("ui.container{...} may either get a label or a legend.")
  else
    return ui.tag{ tag = "div", attr = attr, content = content }
  end
end
    unique_id =            -- unique string to be used as an id in the DOM tree
ui.create_unique_id()
    function ui.create_unique_id() return "unique_" .. multirand.string(32, "bcdfghjklmnpqrstvwxyz") end
ui.enable_partial_loading()
function ui.enable_partial_loading() ui._partial_loading_enabled = true request.force_absolute_baseurl() end
ui.field.boolean{
  ...                        -- generic ui.field.* arguments, as described for ui.autofield{...}
  style       = style,       -- "radio" or "checkbox",
  nil_allowed = nil_allowed  -- set to true, if nil is allowed as third value
}
    function ui.field.boolean(args)
  local style = args.style
  if not style then
    if args.nil_allowed then
      style = "radio"
    else
      style = "checkbox"
    end
  end
  local extra_args = { fetch_value = true }
  if not args.readonly and args.style == "radio" then
    extra_args.disable_label_for_id = true
  end
  ui.form_element(args, extra_args, function(args)
    local value = args.value
    if value ~= true and value ~= false and value ~= nil then
      error("Boolean value must be true, false or nil.")
    end
    if value == nil then
      if args.nil_allowed then
        value = args.default
      else
        value = args.default or false
      end
    end
    if args.readonly then
      ui.tag{
        tag     = args.tag,
        attr    = args.attr,
        content = format.boolean(value, args.format_options)
      }
    elseif style == "radio" then
      local attr = table.new(args.attr)
      attr.type  = "radio"
      attr.name  = args.html_name
      attr.id    = ui.create_unique_id()
      attr.value = "1"
      if value == true then
        attr.checked = "checked"
      else
        attr.checked = nil
      end
      ui.container{
        attr          = { class = "ui_radio_div" },
        label         = args.true_as or "Yes",  -- TODO: localize
        label_for     = attr.id,
        label_attr    = { class = "ui_radio_label" },
        content_first = true,
        content       = function()
          ui.tag{ tag  = "input", attr = attr }
        end
      }
      attr.id    = ui.create_unique_id()
      attr.value = "0"
      if value == false then
        attr.checked = "1"
      else
        attr.checked = nil
      end
      ui.container{
        attr          = { class = "ui_radio_div" },
        label         = args.false_as or "No",  -- TODO: localize
        label_for     = attr.id,
        label_attr    = { class = "ui_radio_label" },
        content_first = true,
        content       = function()
          ui.tag{ tag  = "input", attr = attr }
        end
      }
      if args.nil_allowed then
        attr.id    = ui.create_unique_id()
        attr.value = ""
        if value == nil then
          attr.checked = "1"
        else
          attr.checked = nil
        end
        ui.container{
          attr          = { class = "ui_radio_div" },
          label         = args.nil_as or "N/A",  -- TODO: localize
          label_for     = attr.id,
          label_attr    = { class = "ui_radio_label" },
          content_first = true,
          content       = function()
            ui.tag{ tag  = "input", attr = attr }
          end
        }
      end
      ui.hidden_field{
        name = args.html_name .. "__format", value = "boolean"
      }
    elseif style == "checkbox" then
      if args.nil_allowed then
        error("Checkboxes do not support nil values.")
      end
      local attr = table.new(args.attr)
      attr.type  = "checkbox"
      attr.name  = args.html_name
      attr.value = "1"
      if value then
        attr.checked = "checked"
      else
        attr.checked = nil
      end
      ui.tag{ tag = "input", attr = attr }
      ui.hidden_field{
        name = args.html_name .. "__format",
        value = encode.format_info(
          "boolean",
          { true_as = "1", false_as = "" }
        )
      }
    else
      error("'style' attribute for ui.field.boolean{...} must be set to \"radio\", \"checkbox\" or nil.")
    end
  end)
end
    ui.field.date{
  ...           -- generic ui.field.* arguments, as described for ui.autofield{...}
}
    function ui.field.date(args)
  ui.form_element(args, {fetch_value = true}, function(args)
    local value_string = format.date(args.value, args.format_options)
    if args.readonly then
      ui.tag{ tag = args.tag, attr = args.attr, content = value_string }
    else
      local fallback_data = slot.use_temporary(function()
        local attr = table.new(args.attr)
        attr.type  = "text"
        attr.name  = args.html_name
        attr.value = value_string
        attr.class = attr.class or "ui_field_date"
        ui.tag{ tag  = "input", attr = attr }
        ui.hidden_field{
          name  = args.html_name .. "__format",
          value = encode.format_info("date", args.format_options)
        }
      end)
      local user_field_id, hidden_field_id
      local helper_data = slot.use_temporary(function()
        local attr = table.new(args.attr)
        user_field_id = attr.id or ui.create_unique_id()
        hidden_field_id = ui.create_unique_id()
        attr.id    = user_field_id
        attr.type  = "text"
        attr.class = attr.class or "ui_field_date"
        ui.tag{ tag = "input", attr = attr }
        local attr = table.new(args.attr)
        attr.id    = hidden_field_id
        attr.type  = "hidden"
        attr.name  = args.html_name
        attr.value = atom.dump(args.value)  -- extra safety for JS failure
        ui.tag{
          tag = "input",
          attr = {
            id   = hidden_field_id,
            type = "hidden",
            name = args.html_name
          }
        }
      end)
      -- TODO: localization
      ui.script{
        noscript = fallback_data,
        type     = "text/javascript",
        content  = function()
          slot.put(
            "if (gregor_addGui == null) document.write(",
            encode.json(fallback_data),
            "); else { document.write(",
            encode.json(helper_data),
            "); gregor_addGui({element_id: ",
            encode.json(user_field_id),
            ", 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: "
          )
          if (args.value) then
            slot.put(
              "{year: ", tostring(args.value.year),
              ", month: ", tostring(args.value.month),
              ", day: ", tostring(args.value.day),
              "}"
            )
          else
            slot.put("null")
          end
          slot.put(
           ", select_callback: function(date) { document.getElementById(",
           encode.json(hidden_field_id),
           ").value = (date == null) ? '' : date.iso_string; } } ) }"
          )
        end
      }
    end
  end)
end
    ui.field.file{
  ...                        -- generic ui.field.* arguments, as described for ui.autofield{...}
}
    function ui.field.file(args)
  ui.form_element(args, nil, function(args)
    if args.readonly then
      -- nothing
    else
      if not slot.get_state_table().form_file_upload then
        error('Parameter "file_upload" of ui.form{...} must be set to true to allow file uploads.')
      end
      local attr = table.new(args.attr)
      attr.type  = "file"
      attr.name  = args.html_name
      ui.tag{ tag  = "input", attr = attr }
    end
  end)
end
    ui.field.hidden{
  ...             -- generic ui.field.* arguments, as described for ui.autofield{...}
}
    function ui.field.hidden(args)
  ui.form_element(args, {fetch_value = true}, function(args)
    if not args.readonly then
      ui.hidden_field{
        attr  = args.attr,
        name  = args.html_name,
        value = args.value
      }
    end
  end)
end
    ui.field.integer{
  ...                              -- generic ui.field.* arguments, as described for ui.autofield{...}
  format_options = format_options  -- format options for format.decimal
}
    function ui.field.integer(args)
  ui.form_element(args, {fetch_value = true}, function(args)
    local value_string = format.decimal(args.value, args.format_options)
    if args.readonly then
      ui.tag{ tag = args.tag, attr = args.attr, content = value_string }
    else
      local attr = table.new(args.attr)
      attr.type  = "text"
      attr.name  = args.html_name
      attr.value = value_string
      ui.tag{ tag  = "input", attr = attr }
      ui.hidden_field{
        name  = args.html_name .. "__format",
        value = encode.format_info("decimal", args.format_options)
      }
    end
  end)
end
    ui.field.password{
  ...                        -- generic ui.field.* arguments, as described for ui.autofield{...}
}
    function ui.field.password(args)
  ui.form_element(args, {fetch_value = true}, function(args)
    local value_string = atom.dump(args.value)
    if args.readonly then
      -- nothing
    else
      local attr = table.new(args.attr)
      attr.type  = "password"
      attr.name  = args.html_name
      attr.value = value_string
      ui.tag{ tag  = "input", attr = attr }
    end
  end)
end
    ui.field.select{
  ...                                  -- generic ui.field.* arguments, as described for ui.autofield{...}
  foreign_records  = foreign_records,  -- list of records to be chosen from, or function returning such a list
  foreign_id       = foreign_id,       -- name of id field in foreign records
  foreign_name     = foreign_name,     -- name of field to be used as name in foreign records
  format_options   = format_options    -- format options for format.string
  selected_record  = selected_record   -- id of (or reference to) record which is selected (optional, overrides "value" argument when not nil)
  disabled_records = disabled_records  -- table with ids of (or references to) records that should be disabled (stored as table keys mapped to true)
}
    function ui.field.select(args)
  ui.form_element(args, {fetch_value = true}, function(args)
    local foreign_records = args.foreign_records
    if type(foreign_records) == "function" then
      foreign_records = foreign_records(args.record)
    end
    if args.readonly then
      local name
      for idx, record in ipairs(foreign_records) do
        if record[args.foreign_id] == args.value then
          name = record[args.foreign_name]
          break
        end
      end
      ui.tag{
        tag     = args.tag,
        attr    = args.attr,
        content = format.string(name, args.format_options)
      }
    else
      local attr = table.new(args.attr)
      attr.name  = args.html_name
      ui.tag{
        tag     = "select",
        attr    = attr,
        content = function()
          if args.nil_as then
            ui.tag{
              tag     = "option",
              attr    = { value = "" },
              content = format.string(
                args.nil_as,
                args.format_options
              )
            }
          end
          local one_selected = false
          for idx, record in ipairs(foreign_records) do
            local key = record[args.foreign_id]
            local selected = false
            if not one_selected then
              if args.selected_record == nil then
                if args.value == key then
                  selected = true
                end
              else
                if args.selected_record == record or args.selected_record == key then
                  selected = true
                end
              end
              one_selected = selected
            end
            local disabled = false
            if args.disabled_records then
              if args.disabled_records[record] or args.disabled_records[key] then
                disabled = true
              end
            end
            ui.tag{
              tag     = "option",
              attr    = {
                value    = key,
                disabled = disabled and "disabled" or nil,
                selected = selected and "selected" or nil
              },
              content = format.string(
                record[args.foreign_name],
                args.format_options
              )
            }
          end
        end
      }
    end
  end)
end
    ui.field.text{
  ...                              -- generic ui.field.* arguments, as described for ui.autofield{...}
  format_options = format_options  -- format options for format.string
}
    function ui.field.text(args)
  ui.form_element(args, {fetch_value = true}, function(args)
    local value_string = format.string(args.value, args.format_options)
    if args.readonly then
      ui.tag{ tag = args.tag, attr = args.attr, content = value_string }
    else
      local attr = table.new(args.attr)
      attr.name  = args.html_name
      if args.multiline then
        ui.tag { tag = "textarea", attr = attr, content = value_string }
      else
        attr.type  = "text"
        attr.value = value_string
        ui.tag{ tag  = "input", attr = attr }
      end
    end
  end)
end
    ui.filters{
  selector = selector,  -- selector to be modified
  label    = label,     -- text to be displayed when filters are collapsed
  {
    name  = name1,      -- name of first filter (used as GET param)
    label = label1,     -- label of first filter
    {
      name  = name1a,   -- name of first option of first filter
      label = label1a,  -- label of first option of first filter
      selector_modifier = function(selector)
        ...
      end
    },
    {
      name  = name1b,   -- name of second option of first filter
      label = label1b,  -- label of second option of first filter
      selector_modifier = function(selector)
        ...
      end
    },
    ...
  },
  {
    name  = name2,      -- name of second filter (used as GET param)
    label = label2,     -- label of second filter
    {
      ...
    }, {
      ...
    },
    ...
  },
  ...
  content = function()
    ...                 -- inner code where filter is to be applied
  end
}
    function ui.filters(args)
  local el_id = ui.create_unique_id()
  ui.container{
    attr = { class = "ui_filter" },
    content = function()
      ui.container{
        attr = {
          class = "ui_filter_closed_head"
        },
        content = function()
          ui.tag{
            tag = "span",
            content = function()
              local current_options = {}
              for idx, filter in ipairs(args) do
                local filter_name = filter.name or "filter"
                local current_option = atom.string:load(cgi.params[filter_name])
                if not current_option then
                  current_option = param.get(filter_name)
                end
                if not current_option or #current_option == 0 then
                  current_option = filter[1].name
                end
                for idx, option in ipairs(filter) do
                  if current_option == option.name then
                    current_options[#current_options+1] = encode.html(filter.label) .. ": " .. encode.html(option.label)
                  end
                end
              end
              slot.put(table.concat(current_options, "; "))
            end
          }
          slot.put(" (")
          ui.link{
            attr = {
              onclick = "this.parentNode.style.display='none'; document.getElementById('" .. el_id .. "_head').style.display='block'; return(false);"
            },
            text = args.label,
            external = "#"
          }
          slot.put(")")
        end
      }
      ui.container{
        attr = {
          id = el_id .. "_head",
          style = "display: none;"
        },
        content = function()
          for idx, filter in ipairs(args) do
            local filter_name = filter.name or "filter"
            local current_option = atom.string:load(cgi.params[filter_name])
            if not current_option then
              current_option = param.get(filter_name)
            end
            if not current_option or #current_option == 0 then
              current_option = filter[1].name
            end
            local id     = request.get_id_string()
            local params = request.get_param_strings()
            ui.container{
              attr = { class = "ui_filter_head" },
              content = function()
                slot.put(filter.label or "Filter", ": ")
                for idx, option in ipairs(filter) do
                  params[filter_name] = option.name
                  local attr = {}
                  if current_option == option.name then
                    attr.class = "active"
                    option.selector_modifier(args.selector)
                  end
                  ui.link{
                    attr    = attr,
                    module  = request.get_module(),
                    view    = request.get_view(),
                    id      = id,
                    params  = params,
                    text    = option.label,
                    partial = {
                      params = {
                        [filter_name] = option.name
                      }
                    }
                  }
                end
              end
            }
          end
        end
      }
    end
  }
  ui.container{
    attr = { class = "ui_filter_content" },
    content = function()
      args.content()
    end
  }
end
    ui.form_element( args, -- external arguments { -- options for this function call fetch_value = fetch_value_flag, -- true causes automatic determination of args.value, if nil fetch_record = fetch_record_flag, -- true causes automatic determination of args.record, if nil disable_label_for_id = disable_label_for_id_flag, -- true suppresses automatic setting of args.attr.id for a HTML label_for reference }, function(args) ... -- program code end )
-- TODO: better documentation
function ui.form_element(args, extra_args, func)
  local args = table.new(args)
  if extra_args then
    for key, value in pairs(extra_args) do
      args[key] = value
    end
  end
  local slot_state = slot.get_state_table()
  args.html_name = args.html_name or args.name
  if args.fetch_value then
    if args.value == nil then
      if not args.record and slot_state then
        args.record = slot_state.form_record
      end
      if args.record then
        args.value = args.record[args.name]
      end
    else
      args.value = nihil.lower(args.value)
    end
  elseif args.fetch_record then
    if not args.record and slot_state then
      args.record = slot_state.form_record
    end
  end
  if
    args.html_name and
    not args.readonly and
    slot_state.form_readonly == false
  then
    args.readonly = false
    local prefix
    if args.html_name_prefix == nil then
      prefix = slot_state.html_name_prefix
    else
      prefix = args.html_name_prefix
    end
    if prefix then
      args.html_name = prefix .. args.html_name
    end
  else
    args.readonly = true
  end
  if args.label then
    if not args.disable_label_for_id then
      if not args.attr then
        args.attr = { id = ui.create_unique_id() }
      elseif not args.attr.id then
        args.attr.id = ui.create_unique_id()
      end
    end
    if not args.label_attr then
      args.label_attr = { class = "ui_field_label" }
    elseif not args.label_attr.class then
      args.label_attr.class = "ui_field_label"
    end
  end
  ui.container{
    auto_args = args,
    content = function() return func(args) end
  }
end
    ui.form{
  record      = record,       -- optional record to be used
  read_only   = read_only,    -- set to true, if form should be read-only (no submit button)
  file_upload = file_upload,  -- must be set to true, if form contains file upload element
  external    = external,     -- external URL to be used as HTML form action
  module      = module,       -- module name to be used for HTML form action
  view        = view,         -- view name   to be used for HTML form action
  action      = action,       -- action name to be used for HTML form action
  routing = {
    default = {           -- default routing for called action
      mode   = mode,      -- "forward" or "redirect"
      module = module,    -- optional module name, defaults to current module
      view   = view,      -- view name
      id     = id,        -- optional id to be passed to the view
      params = params,    -- optional params to be passed to the view
      anchor = anchor     -- optional anchor for URL
    },
    ok    = { ... },      -- routing when "ok"    is returned by the called action
    error = { ... },      -- routing when "error" is returned by the called action
    ...   = { ... }       -- routing when "..."   is returned by the called action
  },
  partial = {             -- parameters for partial loading, see below
    module = module,
    view   = view,
    id     = id,
    params = params,
    target = target
  },
  content = function()
    ...                   -- code creating the contents of the form
  end
}
    local function prepare_routing_params(params, routing, default_module)
  local routing_default_given = false
  if routing then
    for status, settings in pairs(routing) do
      if status == "default" then
        routing_default_given = true
      end
      local module = settings.module or default_module or request.get_module()
      assert(settings.mode, "No mode specified in routing entry.")
      assert(settings.view, "No view specified in routing entry.")
      params["_webmcp_routing." .. status .. ".mode"]   = settings.mode
      params["_webmcp_routing." .. status .. ".module"] = module
      params["_webmcp_routing." .. status .. ".view"]   = settings.view
      params["_webmcp_routing." .. status .. ".id"]     = settings.id
      params["_webmcp_routing." .. status .. ".anchor"] = settings.anchor
      if settings.params then
        for key, value in pairs(settings.params) do
          params["_webmcp_routing." .. status .. ".params." .. key] = value
        end
      end
    end
  end
  if not routing_default_given then
    params["_webmcp_routing.default.mode"]   = "forward"
    params["_webmcp_routing.default.module"] = request.get_module()
    params["_webmcp_routing.default.view"]   = request.get_view()
  end
  return params
end
function ui.form(args)
  local args = args or {}
  local slot_state = slot.get_state_table()
  local old_record      = slot_state.form_record
  local old_readonly    = slot_state.form_readonly
  local old_file_upload = slot_state.form_file_upload
  slot_state.form_record = args.record
  if args.readonly then
    slot_state.form_readonly = true
    ui.container{ attr = args.attr, content = args.content }
  else
    slot_state.form_readonly = false
    local params = table.new(args.params)
    prepare_routing_params(params, args.routing, args.module)
    params._webmcp_csrf_secret = request.get_csrf_secret()
    local attr = table.new(args.attr)
    if attr.enctype=="multipart/form-data" or args.file_upload then
      slot_state.form_file_upload = true
      if attr.enctype == nil then
        attr.enctype = "multipart/form-data"
      end
    end
    attr.action = encode.url{
      external  = args.external,
      module    = args.module or request.get_module(),
      view      = args.view,
      action    = args.action,
    }
    attr.method = args.method and string.upper(args.method) or "POST"
    if ui.is_partial_loading_enabled() and args.partial then
      attr.onsubmit = slot.use_temporary(function()
        local partial_mode = "form_normal"
        if args.action then
          partial_mode = "form_action"
          slot.put(
            'var element; ',
            'var formElements = []; ',
            'for (var i=0; i<this.elements.length; i++) { ',
              'formElements[formElements.length] = this.elements[i]; ',
            '} ',
            'for (i=0; i<formElements.length; i++) { ',
              'element = formElements[i]; ',
              'if (element.name.search(/^_webmcp_routing\\./) >= 0) { ',
                'element.parentNode.removeChild(element); ',
              '} ',
            '}'
          )
          local routing_params = {}
          prepare_routing_params(
            routing_params,
            args.partial.routing,
            args.partial.module
          )
          for key, value in pairs(routing_params) do
            slot.put(
              ' ',
              'element = document.createElement("input"); ',
              'element.setAttribute("type", "hidden"); ',
              'element.setAttribute("name", ', encode.json(key), '); ',
              'element.setAttribute("value", ', encode.json(value), '); ',
              'this.appendChild(element);'
            )
          end
          slot.put(' ')
        end
        slot.put(ui._partial_load_js(args.partial, partial_mode))
      end)
    end
    if slot_state.form_opened then
      error("Cannot open a non-readonly form inside a non-readonly form.")
    end
    slot_state.form_opened = true
    ui.tag {
      tag     = "form",
      attr    = attr,
      content = function()
        if args.id then
          ui.hidden_field{ name = "_webmcp_id", value = args.id }
        end
        for key, value in pairs(params) do
          ui.hidden_field{ name = key, value = value }
        end
        if args.content then
          args.content()
        end
      end
    }
    slot_state.form_opened = false
  end
  slot_state.form_file_upload = old_file_upload
  slot_state.form_readonly    = old_readonly
  slot_state.form_record      = old_record
end
    ui.heading{
  level   = level,   -- level from 1 to 6, defaults to 1
  attr    = attr,    -- extra HTML attributes
  content = content  -- string or function for content
}
    function ui.heading(args)
  return ui.tag{
    tag     = "h" .. (args.level or 1),
    attr    = args.attr,
    content = args.content
  }
end
    ui.hidden_field{
  name = name,    -- HTML name
  value = value,  -- value
  attr  = attr    -- extra HTML attributes
}
    function ui.hidden_field(args)
  local args = args or {}
  local attr = table.new(args.attr)
  attr.type  = "hidden"
  attr.name  = args.name
  attr.value = atom.dump(args.value)
  return ui.tag{ tag  = "input", attr = attr }
end
    result = ui.is_partial_loading_enabled()
function ui.is_partial_loading_enabled() return ui._partial_loading_enabled end
ui.link{
  external  = external,   -- external URL
  static    = static,     -- URL relative to the static file directory
  module    = module,     -- module name
  view      = view,       -- view name
  action    = action,     -- action name
  attr      = attr,       -- for views: table of HTML attributes
  a_attr    = a_attr,     -- for actions: table of HTML attributes for the "a" tag
  form_attr = form_attr,  -- for actions: table of HTML attributes for the "form" tag
  id        = id,         -- optional id to be passed to the view or action to select a particular data record
  params    = params,     -- optional parameters to be passed to the view or action
  routing   = routing,    -- optional routing information for action links, as described for ui.form{...}
  anchor    = anchor,     -- for views: anchor in destination URL
  text      = text,       -- link text
  content   = content,    -- link content (overrides link text, except for submit buttons for action calls without JavaScript)
  partial   = {           -- parameters for partial loading, see below
    module = module,
    view   = view,
    id     = id,
    params = params,
    target = target
  }
}
    function ui.link(args)
  local args = args or {}
  local content = args.content or args.text
  assert(content, "ui.link{...} needs a text.")
  local function wrapped_content()
    if args.image then
      ui.image(args.image)
    end
    if type(content) == "function" then
      content()
    else
      slot.put(encode.html(content))
    end
  end
  if args.action then
    local form_attr   = table.new(args.form_attr)
    local form_id
    if form_attr.id then
      form_id = form_attr.id
    else
      form_id = ui.create_unique_id()
    end
    local quoted_form_id = encode.json(form_id)
    form_attr.id      = form_id
    local a_attr      = table.new(args.attr)
    a_attr.href       = "#"
    a_attr.onclick    =
      "var f = document.getElementById(" .. quoted_form_id .. "); if (! f.onsubmit || f.onsubmit() != false) { f.submit() }; return false;"
    ui.form{
      external = args.external,
      module   = args.module or request.get_module(),
      action   = args.action,
      id       = args.id,
      params   = args.params,
      routing  = args.routing,
      partial  = args.partial,
      attr     = form_attr,
      content  = function()
        ui.submit{ text = args.text, attr = args.submit_attr }
      end
    }
    ui.script{
      type = "text/javascript",
      script = (
        "document.getElementById(" ..
        quoted_form_id ..
        ").style.display = 'none'; document.write(" ..
        encode.json(
          slot.use_temporary(
            function()
              ui.tag{
                tag     = "a",
                attr    = a_attr,
                content = wrapped_content
              }
            end
          )
        ) ..
        ");"
      )
    }
  else
    -- TODO: support content function
    local a_attr = table.new(args.attr)
    a_attr.href = encode.url{
      external  = args.external,
      static    = args.static,
      module    = args.module or request.get_module(),
      view      = args.view,
      id        = args.id,
      params    = args.params,
      anchor    = args.anchor
    }
    if ui.is_partial_loading_enabled() and args.partial then
      a_attr.onclick = ui._partial_load_js(args.partial)
    end
    return ui.tag{ tag  = "a", attr = a_attr, content = wrapped_content }
  end
end
    ui.list{
  label   = list_label,  -- optional label for the whole list
  style   = style,       -- "table", "ulli" or "div"
  prefix  = prefix,      -- prefix for HTML field names
  records = records,     -- array of records to be displayed as rows in the list
  columns = {
    {
      label          = column_label,    -- label for the column
      label_attr     = label_attr,      -- table with HTML attributes for the heading cell or div
      field_attr     = field_attr,      -- table with HTML attributes for the data cell or div
      name           = name,            -- name of the field in each record
      html_name      = html_name,       -- optional html-name for writable fields (defaults to name)
      ui_field_type  = ui_field_type,   -- name of the ui.field.* function to use
      ....,                             -- other options for the given ui.field.* functions
      format         = format,          -- name of the format function to be used (if not using ui_field_type)
      format_options = format_options,  -- options to be passed to the format function
      content        = content          -- function to output field data per record (ignoring name, format, ...)
    },
    { ... },
    ...
  }
}
    -- TODO: documentation of the prefix option -- TODO: check short descriptions of fields in documentation -- 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? -- TODO: use field information of record class, if no columns are given -- TODO: callback to set row attr's for a specific row function ui.list(args) local args = args or {} local label = args.label local list_type = args.style or "table" local prefix = args.prefix local records = assert(args.records, "ui.list{...} needs records.") local columns = assert(args.columns, "ui.list{...} needs column definitions.") local outer_attr = table.new(args.attr) local header_existent = false for idx, column in ipairs(columns) do if column.label then header_existent = true break end end local slot_state = slot.get_state_table() local outer_tag, head_tag, head_tag2, label_tag, body_tag, row_tag if list_type == "table" then outer_tag = "table" head_tag = "thead" head_tag2 = "tr" label_tag = "th" body_tag = "tbody" row_tag = "tr" field_tag = "td" elseif list_type == "ulli" then outer_tag = "div" head_tag = "div" label_tag = "div" body_tag = "ul" row_tag = "li" field_tag = "td" elseif list_type == "div" then outer_tag = "div" head_tag = "div" label_tag = "div" body_tag = "div" row_tag = "div" field_tag = "div" else error("Unknown list type specified for ui.list{...}.") end outer_attr.class = outer_attr.class or "ui_list" ui.container{ auto_args = args, content = function() ui.tag{ tag = outer_tag, attr = outer_attr, content = function() if header_existent then ui.tag{ tag = head_tag, attr = { class = "ui_list_head" }, content = function() local function header_content() for idx, column in ipairs(columns) do if column.ui_field_type ~= "hidden" then local label_attr = table.new(column.label_attr) label_attr.class = label_attr.class or { class = "ui_list_label" } ui.tag{ tag = label_tag, attr = label_attr, content = column.label or "" } end end end if head_tag2 then ui.tag{ tag = head_tag2, content = header_content } else header_content() end end } end ui.tag{ tag = body_tag, attr = { class = "ui_list_body" }, content = function() for record_idx, record in ipairs(records) do local row_class if record_idx % 2 == 0 then row_class = "ui_list_row ui_list_even" else row_class = "ui_list_row ui_list_odd" end ui.tag{ tag = row_tag, attr = { class = row_class }, content = function() local old_html_name_prefix, old_form_record if prefix then old_html_name_prefix = slot_state.html_name_prefix old_form_record = slot_state.form_record slot_state.html_name_prefix = prefix .. "[" .. record_idx .. "]" slot_state.form_record = record end local first_column = true for column_idx, column in ipairs(columns) do if column.ui_field_type ~= "hidden" then local field_attr = table.new(column.field_attr) field_attr.class = field_attr.class or { class = "ui_list_field" } local field_content if column.content then field_content = function() return column.content(record) end elseif column.name then if column.ui_field_type then local ui_field_func = ui.field[column.ui_field_type] if not ui_field_func then error('Unknown ui_field_type "' .. column.ui_field_type .. '".') end local ui_field_options = table.new(column) ui_field_options.record = record ui_field_options.label = nil if not prefix and ui_field_options.readonly == nil then ui_field_options.readonly = true end field_content = function() return ui.field[column.ui_field_type](ui_field_options) end elseif column.format then local formatter = format[column.format] if not formatter then error('Unknown format "' .. column.format .. '".') end field_content = formatter( record[column.name], column.format_options ) else field_content = function() return ui.autofield{ record = record, name = column.name, html_name = column.html_name } end end else error("Each column needs either a 'content' or a 'name'.") end local extended_field_content if first_column then first_column = false extended_field_content = function() for column_idx, column in ipairs(columns) do if column.ui_field_type == "hidden" then local ui_field_options = table.new(column) ui_field_options.record = record ui_field_options.label = nil if not prefix and ui_field_options.readonly == nil then ui_field_options.readonly = true end ui.field.hidden(ui_field_options) end end field_content() end else extended_field_content = field_content end ui.tag{ tag = field_tag, attr = field_attr, content = extended_field_content } end end if prefix then slot_state.html_name_prefix = old_html_name_prefix slot_state.form_record = old_form_record end end } end end } end } end } if prefix then -- ui.field.hidden is used instead of ui.hidden_field to suppress output in case of read-only mode. ui.field.hidden{ html_name = prefix .. "[len]", value = #records } end end
ui.multiselect{
  name               = name,                -- HTML name ('html_name' is NOT a valid argument for this function)
  container_attr     = container_attr,      -- extra HTML attributes for the container (div) enclosing field and label
  container2_attr    = container2_attr,     -- extra HTML attributes for the container (div) of the real element (in checkbox case only)
  attr               = attr,                -- extra HTML attributes for the field
  label              = label,               -- text to be used as label for the input field
  label_attr         = label_attr,          -- extra HTML attributes for the label
  readonly           = readonly_flag        -- set to true, to force read-only mode
  foreign_records    = foreign_records,     -- list of records to be chosen from, or function returning such a list
  foreign_id         = foreign_id,          -- name of id field in foreign records
  foreign_name       = foreign_name,        -- name of field to be used as name in foreign records
  selected_ids       = selected_ids,        -- list of ids of currently selected foreign records
  connecting_records = connecting_records,  -- list of connection entries, determining which foreign records are currently selected
  own_id             = own_id,              -- TODO documentation needed
  own_reference      = own_reference,       -- name of foreign key field in connecting records, which references the main record
  foreign_reference  = foreign_reference,   -- name of foreign key field in connecting records, which references foreign records
  format_options     = format_options       -- format options for format.string
}
    function ui.multiselect(args)
  local style = args.style or "checkbox"
  local extra_args = { fetch_record = true }
  if not args.readonly and args.style == "checkbox" then
    extra_args.disable_label_for_id = true
  end
  ui.form_element(args, extra_args, function(args)
    local foreign_records = args.foreign_records
    if type(foreign_records) == "function" then
      foreign_records = foreign_records(args.record)
    end
    local connecting_records = args.connecting_records
    if type(connecting_records) == "function" then
      connecting_records = connecting_records(args.record)
    end
    local select_hash = {}
    if args.selected_ids then
      for idx, selected_id in ipairs(args.selected_ids) do
        select_hash[selected_id] = true
      end
    elseif args.own_reference then
      for idx, connecting_record in ipairs(args.connecting_records) do
        if connecting_record[args.own_reference] == args.record[args.own_id] then
          select_hash[connecting_record[args.foreign_reference]] = true
        end
      end
    else
      for idx, connecting_record in ipairs(args.connecting_records) do
        select_hash[connecting_record[args.foreign_reference]] = true
      end
    end
    local attr = table.new(args.attr)
    if not attr.class then
      attr.class = "ui_multi_selection"
    end
    if args.readonly then
      ui.tag{
        tag     = "ul",
        attr    = attr,
        content = function()
          for idx, record in ipairs(foreign_records) do
            if select_hash[record[args.foreign_id]] then
              ui.tag{
                tag     = "li",
                content = format.string(
                  record[args.foreign_name],
                  args.format_options
                )
              }
            end
          end
        end
      }
    elseif style == "select" then
      attr.name     = args.name
      attr.multiple = "multiple"
      ui.tag{
        tag     = "select",
        attr    = attr,
        content = function()
          if args.nil_as then
            ui.tag{
              tag     = "option",
              attr    = { value = "" },
              content = format.string(
                args.nil_as,
                args.format_options
              )
            }
          end
          for idx, record in ipairs(foreign_records) do
            local key = record[args.foreign_id]
            local selected = select_hash[key]
            ui.tag{
              tag     = "option",
              attr    = {
                value    = key,
                selected = (selected and "selected" or nil)
              },
              content = format.string(
                record[args.foreign_name],
                args.format_options
              )
            }
          end
        end
      }
    elseif style == "checkbox" then
      attr.type = "checkbox"
      attr.name = args.name
      for idx, record in ipairs(foreign_records) do
        local key = record[args.foreign_id]
        local selected = select_hash[key]
        attr.id   = ui.create_unique_id()
        attr.value = key
        attr.checked = selected and "checked" or nil
        ui.container{
          label = format.string(
            record[args.foreign_name],
            args.format_options
          ),
          attr          = args.container2_attr or { class = "ui_checkbox_div" },
          label_for     = attr.id,
          label_attr    = args.label_attr or { class = "ui_checkbox_label" },
          content_first = true,
          content       = function()
            ui.tag{ tag  = "input", attr = attr }
          end
        }
      end
    else
      error("'style' attribute for ui.multiselect{...} must be set to \"select\", \"checkbox\" or nil.")
    end
  end)
end
    ui.paginate{
  selector       = selector,       -- a selector for items from the database (will be modified)
  anchor         = anchor,         -- optional name of anchor in document to jump to
  per_page       = per_page,       -- items per page, defaults to 10
  container_attr = container_attr  -- html attr for the container element
  name           = name,           -- name of the CGI get variable, defaults to "page"
  page           = page,           -- directly specify a page, and ignore 'name' parameter
  content        = function()
    ...                            -- code block which should be encapsulated with page selection links
  end
}
    function ui.paginate(args)
  local selector = args.selector
  local per_page = args.per_page or 10
  local name     = args.name or 'page'
  local content  = args.content
  local count_selector = selector:get_db_conn():new_selector()
  count_selector:add_field('count(1)')
  count_selector:add_from(selector)
  count_selector:single_object_mode()
  local count = count_selector:exec().count
  local page_count = 1
  if count > 0 then
    page_count = math.floor((count - 1) / per_page) + 1
  end
  local current_page = atom.integer:load(cgi.params[name]) or 1
  if current_page > page_count then
    current_page = page_count
  end
  selector:limit(per_page)
  selector:offset((current_page - 1) * per_page)
  local id     = request.get_id_string()
  local params = request.get_param_strings()
  local function pagination_elements()
    if page_count > 1 then
      for page = 1, page_count do
        if page > 1 then
          slot.put(" ")
        end
        params[name] = page
        local attr = {}
        if current_page == page then
          attr.class = "active"
        end
        local partial
        if ui.is_partial_loading_enabled() then
          partial = {
            params = {
              [name] = tostring(page)
            }
          }
        end
        ui.link{
          attr   = attr,
          module = request.get_module(),
          view   = request.get_view(),
          id     = id,
          params = params,
          anchor = args.anchor,
          text   = tostring(page),
          partial = partial
        }
      end
    end
  end
  ui.container{
    attr = args.container_attr or { class = 'ui_paginate' },
    content = function()
      ui.container{
        attr = { class = 'ui_paginate_head ui_paginate_select' },
        content = pagination_elements
      }
      ui.container{
        attr = { class = 'ui_paginate_content' },
        content = content
      }
      ui.container{
        attr = { class = 'ui_paginate_foot ui_paginate_select' },
        content = pagination_elements
      }
    end
  }
end
    ui.partial{
  module  = module,     -- module to be used to reload inner contents
  view    = view,       -- view   to be used to reload inner contents
  id      = id,         -- id     to be used to reload inner contents
  params  = params,     -- params to be used to reload inner contents
  target  = target,     -- id of HTML element containing contents to be replaced
  content = function()
    ...
  end
}
    function ui.partial(args)
  local old_state = ui._partial_state
  ui._partial_state = table.new(args)
  ui._partial_state.param_name_hash = {}
  if args.param_names then
    ui.add_partial_param_names(args.param_names)
  end
  args.content()
  ui._partial_state = old_state
end
    ui.script{
  noscript_attr = noscript_attr,  -- HTML attributes for noscript tag
  noscript      = noscript,       -- string or function for noscript content
  attr          = attr,           -- extra HTML attributes for script tag
  type          = type,           -- type of script, defaults to "text/javascript"
  script        = script,         -- string or function for script content
}
    function ui.script(args)
  local args = args or {}
  local noscript_attr = args.noscript_attr
  local noscript = args.noscript
  local attr = table.new(args.attr)
  attr.type = attr.type or args.type or "text/javascript"
  local script = args.script
  if args.external then
    attr.src = encode.url{ external = args.external }
  elseif args.static then
    attr.src = encode.url{ static = args.static }
  end
  if noscript then
    ui.tag{ tag = "noscript", attr = attr, content = noscript }
  end
  if attr.src then
    ui.tag{ tag = "script", attr = attr, content = "" }
  elseif script then
    local script_string
    if type(script) == "function" then
      script_string = slot.use_temporary(script)
    else
      script_string = script
    end
    if string.find(script_string, "]]>") then
      error('Script contains character sequence "]]>" and is thus rejected to avoid ambiguity. If this sequence occurs as part of program code, please add additional space characters. If this sequence occurs inside a string literal, please encode one of this characters using the \\uNNNN unicode escape sequence.')
    end
    ui.tag{
      tag  = "script",
      attr = attr,
      content = function()
        slot.put("/* <![CDATA[ */")
        slot.put(script_string)
        slot.put("/* ]]> */")
      end
    }
  end
end
    ui.submit{
  name  = name,   -- optional HTML name
  value = value,  -- HTML value
  text  = value   -- text on button
}
    function ui.submit(args)
  if slot.get_state_table().form_readonly == false then
    local args = args or {}
    local attr = table.new(attr)
    attr.type  = "submit"
    attr.name  = args.name
    attr.value = args.value or args.text
    return ui.tag{ tag  = "input", attr = attr }
  end
end
    ui.tag{
  tag     = tag,     -- HTML tag, e.g. "a" for <a>...</a>
  attr    = attr,    -- table of HTML attributes, e.g. { class = "hide" }
  content = content  -- string to be HTML encoded, or function to be executed
}
    function ui.tag(args)
  local tag, attr, content
  tag     = args.tag
  attr    = args.attr or {}
  content = args.content
  if type(attr.class) == "table" then
    attr = table.new(attr)
    attr.class = table.concat(attr.class, " ")
  end
  if not tag and next(attr) then
    tag = "span"
  end
  if tag then
    slot.put('<', tag)
    for key, value in pairs(attr) do
      slot.put(' ', key, '="', encode.html(value), '"')
    end
  end
  if content then
    if tag then
      slot.put('>')
    end
    if type(content) == "function" then
      content()
    else
      slot.put(encode.html(content))
    end
    if tag then
      slot.put('</', tag, '>')
    end
  else
    if tag then
      slot.put(' />')
    end
  end
end
    url, -- normalized URL or nil auth.openid._normalize_url( url -- unnormalized URL )
function auth.openid._normalize_url(url) local url = string.match(url, "^(.-)??$") -- remove "?" at end local proto, host, path = string.match( url, "([A-Za-z]+)://([0-9A-Za-z.:_-]+)/?([0-9A-Za-z%%/._~-]*)$" ) if not proto then return nil end proto = string.lower(proto) host = string.lower(host) local port = string.match(host, ":(.*)") if port then if string.find(port, "^[0-9]+$") then port = tonumber(port) host = string.match(host, "^(.-):") if port < 1 or port > 65535 then return nil end else return nil end end if proto == "http" then if port == 80 then port = nil end elseif proto == "https" then if port == 443 then port = nil end else return nil end if string.find(host, "^%.") or string.find(host, "%.$") or string.find(host, "%.%.") then return nil end for part in string.gmatch(host, "[^.]+") do if not string.find(part, "[A-Za-z]") then return nil end end local path_parts = {} for part in string.gmatch(path, "[^/]+") do if part == "." then -- do nothing elseif part == ".." then path_parts[#path_parts] = nil else local fail = false local part = string.gsub( part, "%%([0-9A-Fa-f]?[0-9A-Fa-f]?)", function (hex) if #hex ~= 2 then fail = true return end local char = string.char(tonumber("0x" .. hex)) if string.find(char, "[0-9A-Za-z._~-]") then return char else return "%" .. string.upper(hex) end end ) if fail then return nil end path_parts[#path_parts+1] = part end end if string.find(path, "/$") then path_parts[#path_parts+1] = "" end path = table.concat(path_parts, "/") if port then host = host .. ":" .. tostring(port) end return proto .. "://" .. host .. "/" .. path end
Copyright (c) 2009-2010 Public Software Group e. V., Berlin