diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..57493eb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,51 @@ +# +# LuaDist Travis-CI Hook +# + +# We assume C build environments +language: C + +# Try using multiple Lua Implementations +env: + - TOOL="gcc" # Use native compiler (GCC usually) + - TOOL="clang" # Use clang + - TOOL="i686-w64-mingw32" # 32bit MinGW + - TOOL="x86_64-w64-mingw32" # 64bit MinGW + - TOOL="arm-linux-gnueabihf" # ARM hard-float (hf), linux + +# Crosscompile builds may fail +matrix: + allow_failures: + - env: TOOL="i686-w64-mingw32" + - env: TOOL="x86_64-w64-mingw32" + - env: TOOL="arm-linux-gnueabihf" + +# Install dependencies +install: + - git clone git://github.com/LuaDist/Tools.git ~/_tools + - ~/_tools/travis/travis install + +# Bootstap +before_script: + - ~/_tools/travis/travis bootstrap + +# Build the module +script: + - ~/_tools/travis/travis build + +# Execute additional tests or commands +after_script: + - ~/_tools/travis/travis test + +# Only watch the master branch +branches: + only: + - master + +# Notify the LuaDist Dev group if needed +notifications: + recipients: + - luadist-dev@googlegroups.com + email: + on_success: change + on_failure: always diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..005ce80 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2007-2013 LuaDist. +# Created by Peter Drahos, Peter Kapec +# Redistribution and use of this file is allowed according to the terms of the MIT license. +# For details see the COPYRIGHT file distributed with LuaDist. +# Please note that the package source code is licensed under its own license. + +project ( asklua NONE ) +cmake_minimum_required ( VERSION 2.8 ) +include ( cmake/dist.cmake ) +include ( lua ) + +install_lua_module ( ask ask.lua ) +install_doc ( doc/ ) +install_example ( example/ ) +install_data ( license.txt readme.txt ) diff --git a/ask.lua b/ask.lua new file mode 100644 index 0000000..2cd7d31 --- /dev/null +++ b/ask.lua @@ -0,0 +1,741 @@ +--[[ + Simple help system for Lua modules + + Author: Julio M. Fernandez-Diaz + Date: Feb, 2010 + (For Lua 5.1) + + It is not intended to be complete, instead to be `enough'. + + Error messages are displayed in stderr +--]] + +-- Change the name of this file for other name if you wish +-- other name for querying help +local NAME = ... + +module(NAME, package.seeall) + +-- saving global names ------------------------------------------- + +local format, rep, gsub, sub = string.format, string.rep, string.gsub, string.sub +local find, char, byte, setlocale = string.find, string.char, string.byte, os.setlocale +local type, io, os, table, ipairs, pairs = type, io, os, table, ipairs, pairs +local _G, setmetatable, require, pcall, error = _G, setmetatable, require, pcall, error +local print = print + +-- default css if not provided +local CSS = [[ +]] + +-- private functions ------------------------------------------- + +-- returns "utf-8" if detected, nil otherwise +local function detectutf8 () + local c = setlocale(nil, "ctype"):lower() + local e = c:find("utf-8", 1, true) or c:find("utf8", 1, true) + return e and "utf-8" +end + +-- convert utf8 to iso +-- http://lua-users.org/lists/lua-l/2003-10/msg00281.html +local function utf8toiso (s) + if s:find("[\224-\255]") then error("non-ISO char") end + s = s:gsub("([\192-\223])(.)", function (c1, c2) + c1 = byte(c1) - 192 + c2 = byte(c2) - 128 + return char(c1 * 64 + c2) + end) + return s +end + +-- convert iso to utf8 +-- the inverse of +-- http://lua-users.org/lists/lua-l/2003-10/msg00281.html +local function isotoutf8 (s) + s = s:gsub("([\128-\255])", function (c) + local b = c:byte() + return b < 192 and "\194"..c or "\195"..char(b-64) + end) + return s +end + +-- convert iso-8859 <-> utf-8 +local function convertcs (s, cs, uft8) + if cs == "utf-8" and not utf8 then -- convert to iso + return utf8toiso(s) + elseif cs ~= "utf-8" and utf8 then -- convert to utf-8 + return isotoutf8(s) + end + return s +end + +-- for formatted writing in a file +local function fprintf (filehandler, fmt, ...) + filehandler:write(format(fmt, ...)) +end + +-- print a line of s with n characters in stderr +local function line (s, n) + n = n or 72 + io.stderr:write(rep(s, n).."\n") +end + +-- Prints the file with name filename in the filehandler +local function printcss (filehandler, filename) + local data + local f = io.open(filename, "rb") + if f == nil then + data = CSS + else + data = f:read("*all") + f:close() + end + filehandler:write(data) +end + +-- extract parts in what, by separating +-- [/][spath]^[info] +local function parts (what) + local absol, spath, info = "" + + if what == nil then return nil end + + what = what:gsub("%s", "") -- delete blanks + what = what:gsub("[%.]+", ".") -- collapse multiple . + + if what:sub(1,1) == "/" then -- search initial / + absol = "/" + what = what:sub(2) + end + + -- split parts + -- if more than one "^" appears only the last part is info + what:gsub("%^", "&") + what:gsub("^([^&]*)^([^&]*)$", function (w1, w2) spath, info = w1, w2 end) + if spath == nil then spath, info = what, "" + else + spath:gsub("&", "%^") + info:gsub("&", "%^") + end + + -- delete possible initial and final . + if spath:sub(1,1) == "." then spath = spath:sub(2) end + if spath:sub(-1) == "." then spath = spath:sub(1,-2) end + + return absol, spath, info +end + +-- split a string in fields separated by "." +local function splitdot (s) + local t = {} + s:gsub('([^%.]+)(%.*)', function (w, d) t[#t+1] = w end) + return t +end + +-- private variables ------------------------------------------- + +local Cases = {"Basic information", "List of functions", "Usage of ", + "More specific information", "See also", "Examples", + "Description of functions", "Version", "Notes"} +local cases = {"basic", "list", "usage", "more", "seealso", "example", + "description", "version", "notes", + } +local order = {b = 1, l = 2, u = 3, m = 4, s = 5, + e = 6, d = 7, v = 8, n = 9} -- order for "all" + +local basis = NAME + + +-- public objects ------------------------------------------- + +_H = { +_CHARSET = "iso-8859-15", -- "utf-8" in other cases +_basic = '`'..NAME..[[` is a system that provides help for modules. + +It searches eight types of information: + + basic list usage more seealso example version notes + +Execute `]]..NAME..'"/'..NAME..'^usage"` and `'..NAME..'"/'..NAME.. +[[^more"` for more explanation. + +Apart from this, documentation in `html` format can +be generated (see `]]..NAME..'.doc`).', +_usage = '`'..NAME..[[(search)` + +@params: + +1. `search` is a string in the form `"what^kind"` (a caret + separates two parts of the argument). + +@returns: nothing. + +@effects: it prints in `io.stderr`. + +`what` is what are looking for; if the first character in it is `/` +an *absolute* path is searched; if not the string defined in `]].. +NAME..[[.base` is used as a basis; + +`kind` is the type of information we want, that can be: + + "basic" | "list" | "usage" | "more" | "seealso" + "b" | "l" | "u" | "m" | "s" + + "example" | "version" | "notes" | "all" + "e" | "v" | "n" | "a" + +(as we see, we can use only the first letter). +If `all` is requested then all information +(basic, list, usage, seealso, example, version, notes) *if exists* is given. +If no kind is provided (in this case the caret is optional) `"basic"` is assumed. + +`]]..NAME..[[(nil)` and `]]..NAME..[[.about(nil)` are equivalent to +`]]..NAME..'"/'..NAME..[[^basic"`, that is, help about this helping system +is given.]], +_more = [[The search of information is controlled by two strings: +`what` and `kind`. Both are typed separated by a caret, `"^"`. + +The first, `what`, is what we are searching. Normally this +is a sequence `name1[.name2[.name3 ...] ]` in which `name1` is a module +name, and `name2`, `name3`, ... indicate functions in the module or +tables with functions in the module. + +If `what` begins with a slash, `/`, then an absolute path is searched. +Otherwise the help system adds as a prefix the string stored in a local +variable assigned with the function `]]..NAME..[[.base`. This improves the +interactivity because the user is not enforced to always type the +complete path of help. + +The second, `kind`, is the type of information we want: + ++ `basic`: the purpose of the module or function inside a module, ++ `list`: the listing of functions in the module, ++ `usage`: the use of a function, describing the arguments and returns, ++ `more`: more specific information, ++ `seealso`: some related information, ++ `example`: an example of use, ++ `version`: information about version and author, ++ `notes`: other information, usually license one, + ++ `all`: show all the previous information. + +For activate the help system the user (interactively) or some module +should invoke + + require"]]..NAME..[[" + +**Note**: the present help system manages a module variable `_H`. +This means that `_H` cannot be used for other purposes in +the module.]], +_example = [[Some examples (with equivalences): + +We assume the *first* invoking after `require"]]..NAME..[["` (by the user +or by some module): + + ]]..NAME..[["" --><-- ]]..NAME..[["/]]..NAME..[[^basic" + ]]..NAME..[["^a" --><-- ]]..NAME..[["/]]..NAME..[[^all" + ]]..NAME..[["/somemodule.fun^u" --><-- ]]..NAME..[["/somemodule.fun^usage" + + ]]..NAME..[[.base"somemodule" -- changes basis for searching + + ]]..NAME..[["^l" --><-- ]]..NAME..[["/somemodule^list" + ]]..NAME..[["fun^u" --><-- ]]..NAME..[["/somemodule.fun^usage" + ]]..NAME..[["/]]..NAME..[[^m" --><-- ]]..NAME..[["/]]..NAME..[[^more"]], +_Name = NAME, +_version = [[by Julio M. Fernández-Díaz, Dept. of Physics, +University of Oviedo, Spain, Version 0.1, February 2010 + +julio a t uniovi d o t es]], +_notes = [[THIS CODE IS HEREBY PLACED IN PUBLIC DOMAIN. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE.]] +} + +_H.about = { +_basic = [[Main function for on line help (synonym of `]]..NAME..[[`)]], +_usage = '`'..NAME..[[.about(search)` + + +`]]..NAME..[[(search)` + +@params: + +1. `search` is a string in the form `"what^kind"` (a caret + separates two parts of the argument). + +@returns: nothing. + +@effects: it prints in `io.stderr`. + +`what` is what are looking for; if the first character in it is `/` +an *absolute* path is searched; if not the string defined through +`]]..NAME..[[.base` is used as a basis; + +`kind` is the type of information we want, that can be: + + "basic" | "list" | "usage" | "more" | "seealso" + "b" | "l" | "u" | "m" | "s" + + "example" | "version" | "notes" | "all" + "e" | "v" | "n" | "a" + +(as we can see we can use only the first letter). +If `all` is requested then all information, +(basic, list, usage, seealso, example, version, notes) *if exists* is given. +If no kind is provided `"basic"` is assumed. + +Spaces typed by the user in the set `what^kind` are deleted before +the search of the help. Also, multiple `"."` are collapsed. + +`]]..NAME..[[(nil)` and `]]..NAME..[[.about(nil)` are equivalent to +`]]..NAME..'"/'..NAME..[[^basic"`, that is, help about this helping system +is given.]], +} + +function about (what) + what = what or "/"..NAME.."^basic" + if what == "" then what = "/"..basis.."^basic" end + + local absol, spath, info = parts(what) + + if absol ~= "/" then + if spath ~= "" then spath = basis.."."..spath + else spath = basis end + end + if info == "" then info = "basic" end + + local ts = splitdot(spath) + table.insert(ts, 2, "_H") + + if #info == 1 then + if info == "a" then info = "all" + elseif order[info] then info = cases[order[info]] end + end + + local tt = {info} + if info == "all" then + for k, v in ipairs(cases) do tt[k] = v end + end + + local H = _G + local CHARSET = H[ts[1]][ts[2]]["_CHARSET"] + local UTF8 = detectutf8() + + for k = 1, #ts do + if H then H = H[ts[k]] end + end + if H == nil and info ~= "list" then + io.stderr:write("No information about '/"..spath.."^"..info.."'\n") + io.stderr:flush() + return + end + + if info == "list" then tt = {"list"} end + + local function fill (t) + local pH = "" + local nc, ic, lef = 3, 1, 25 + + for j, s in ipairs(t) do + pH = pH..s..(" "):rep(lef-#s) + if ic == nc then pH = pH.."\n" end + ic = ic+1 + if ic > nc then ic = 1 end + end + return pH.."\n" + end + + for k, v in ipairs(tt) do + local H1, pH = H["_"..v], "" + if v == "list" then + -- special case : list + local al = {} + for a in pairs(H) do + if a:sub(1,1) ~= "_" then al[#al+1] = a end + end + if #al > 0 then + table.sort(al) + io.stderr:write(">>>>>>>>>> "..spath..":"..v.." <<<<<<<<<<\n") + pH = 'List of functions in "'..spath..'":\n\n'..fill(al) + io.stderr:write(pH) + line("-") + end + elseif H1 and type(H1) ~= "table" then + io.stderr:write(">>>>>>>>>> "..spath.." ^ "..v.." <<<<<<<<<<\n") + H1 = convertcs(H1, CHARSET, UTF8) + io.stderr:write(pH..H1.."\n") + line("-") + elseif info ~= "all" then + io.stderr:write(">>No information about '/"..spath.." ^ "..info.."'\n") + end + end + + io.stderr:flush() +end + +_H.base = { +_basic = [[Establishes a *basis prefix* for help searching]], +_usage = [[`]]..NAME..[[.base(basis)` + +@params: + +1. `basis`, string. + +@returns: nothing. + +This function changes the `basis` (a string) to add as a prefix in the +desired information path when this does not begin with a slash, `/`. + +When providind `basis` the slashdot `"/"` is not required. + +Initially basis has the value `"]]..NAME..[["` but the loading of a module +sets the current `basis` to the module name. + +When calling `]]..NAME..[[.base""` the current basis is displayed. + +Calling `]]..NAME..[[.base(nil)` establishes `"]]..NAME..[["` as basis.]] +} + +function base (b) + if b == nil or type(b) ~= "string" then b = NAME end + if b~= "" then + if b:sub(1,1) == "/" then b = b:sub(2) end + basis, now = b + io.stderr:write('--> Changing help basis to "'..basis..'"\n') + else + io.stderr:write('--> Help basis is "'..basis..'"\n') + end +end + +_H.doc = { +_basic = [[Create `html` documentation for a module]], +_usage = [[`]]..NAME..[[.doc(modulename, filename)` + +@params: + +1. `modulename`: string (optional) is the name of the module + of which we want the documentation. + If not provided then the basis is used. +2. `filename`: string (optional) is the name of the output file, + in `html` format. If not given the name of the module is used. + If one of the extensions `".html"` or `".htm"` (in lowercase) is + not provided then automatically `".html"` is added to the filename. + +@returns: nothing + +@effects: it creates a file. + +For generating the documentation module +[markdown.lua](http://www.frykholm.se/programming.html) from Niklas Frykholm +must be accessible. +(Note: the version in [luaforge.net](http://luaforge.net) is obsolete.)]], +_more = [[A CSS file called `default.css`, which is possible +to customize, is used. This file is embeded in the `html` output file. +If not provided the system uses an internal style. + +The resulting `html` file can be converted, v.g., to PS +with [html2ps](http://user.it.uu.se/~jan/html2ps.html), from Jan Kärrman. +After that `ps2pdf` can be used to convert it to PDF format.]], +} + +function doc (name, filename) + if type(name) ~= "string" then + io.stderr:write("A name of a module must be given\n") + return + end + + if name == "" then name = basis end + if _G[name] == nil then + io.stderr:write("Module "..name.." not found\n") + return + end + + local gn = _G[name] + if gn._H == nil then + io.stderr:write("Module "..name.." has no help information\n") + return + end + name = gn._H._Name + local CHARSET = gn._H._CHARSET + + filename = filename or name + if type(filename) ~= "string" then + io.stderr:write("A filename for output must be given\n") + return + end + + -- check for .html or .htm extension in filename + if filename:sub(-5) ~= ".html" and filename:sub(-4) ~= ".htm" then + filename = filename..".html" + end + + local f = io.open(filename, "w") + if f == nil then + io.stderr:write('"'..filename..' cannot be created\n') + return + end + + local md, markdown = pcall(require, "markdown") + if not md then + io.stderr:write("Markdown not found. No conversion is applied\n") + markdown = function (s) return s end + end + + local cart = {} + + gn = gn._H + + local function pref (s) + return ''..s..'' + end + + local function listfun (gn) + -- prepare an alphabetical list of (sub)functions + local al = {} + for a in pairs(gn) do + if a:sub(1,1) ~= "_" then al[#al+1] = {name = a} end + end + if #al > 0 then + table.sort(al, function (a, b) return a.name < b.name end) + end + for i, a in ipairs(al) do + local d = gn[a.name] + if d then + local b = listfun(d) + if #b > 0 then al[i][1] = b end + end + end + return al + end + + local allfun = listfun(gn) + + local function showlist (allfun, prev) + local cart = "" + for i, a in ipairs(allfun) do + local pr = prev == "" and "" or prev.."_" + cart = cart..'
\n' + for j, fun in ipairs(al) do + pH = pH..''..fun..'\n' + end + pH = pH.."
\n\n" + local cc = level == 2 and ' id ="'..c..'"' or "" + cart[#cart+1] = '\nask
is a system that provides help for modules.
It searches eight types of information:
+ +basic list usage more seealso example version notes
+
+
+Execute ask"/ask^usage"
and ask"/ask^more"
for more explanation.
Apart from this, documentation in html
format can
+be generated (see ask.doc
).
ask(search)
@params:
+ +search
is a string in the form "what^kind"
(a caret
+ separates two parts of the argument).@returns: nothing.
+ +@effects: it prints in io.stderr
.
what
is what are looking for; if the first character in it is /
+an absolute path is searched; if not the string defined in ask.base
is used as a basis;
kind
is the type of information we want, that can be:
"basic" | "list" | "usage" | "more" | "seealso"
+"b" | "l" | "u" | "m" | "s"
+
+"example" | "version" | "notes" | "all"
+"e" | "v" | "n" | "a"
+
+
+(as we see, we can use only the first letter).
+If all
is requested then all information
+(basic, list, usage, seealso, example, version, notes) if exists is given.
+If no kind is provided (in this case the caret is optional) "basic"
is assumed.
ask(nil)
and ask.about(nil)
are equivalent to
+ask"/ask^basic"
, that is, help about this helping system
+is given.
The search of information is controlled by two strings:
+what
and kind
. Both are typed separated by a caret, "^"
.
The first, what
, is what we are searching. Normally this
+is a sequence name1[.name2[.name3 ...] ]
in which name1
is a module
+name, and name2
, name3
, ... indicate functions in the module or
+tables with functions in the module.
If what
begins with a slash, /
, then an absolute path is searched.
+Otherwise the help system adds as a prefix the string stored in a local
+variable assigned with the function ask.base
. This improves the
+interactivity because the user is not enforced to always type the
+complete path of help.
The second, kind
, is the type of information we want:
basic
: the purpose of the module or function inside a module,
list
: the listing of functions in the module,
usage
: the use of a function, describing the arguments and returns,
more
: more specific information,
seealso
: some related information,
example
: an example of use,
version
: information about version and author,
notes
: other information, usually license one,
all
: show all the previous information.
For activate the help system the user (interactively) or some module +should invoke
+ + require"ask"
+
+
+Note: the present help system manages a module variable _H
.
+This means that _H
cannot be used for other purposes in
+the module.
Some examples (with equivalences):
+ +We assume the first invoking after require"ask"
(by the user
+or by some module):
ask"" --><-- ask"/ask^basic"
+ask"^a" --><-- ask"/ask^all"
+ask"/somemodule.fun^u" --><-- ask"/somemodule.fun^usage"
+
+ask.base"somemodule" -- changes basis for searching
+
+ask"^l" --><-- ask"/somemodule^list"
+ask"fun^u" --><-- ask"/somemodule.fun^usage"
+ask"/ask^m" --><-- ask"/ask^more"
+
+
+
+
+
+Main function for on line help (synonym of ask
)
ask.about(search)
ask(search)
@params:
+ +search
is a string in the form "what^kind"
(a caret
+ separates two parts of the argument).@returns: nothing.
+ +@effects: it prints in io.stderr
.
what
is what are looking for; if the first character in it is /
+an absolute path is searched; if not the string defined through
+ask.base
is used as a basis;
kind
is the type of information we want, that can be:
"basic" | "list" | "usage" | "more" | "seealso"
+"b" | "l" | "u" | "m" | "s"
+
+"example" | "version" | "notes" | "all"
+"e" | "v" | "n" | "a"
+
+
+(as we can see we can use only the first letter).
+If all
is requested then all information,
+(basic, list, usage, seealso, example, version, notes) if exists is given.
+If no kind is provided "basic"
is assumed.
Spaces typed by the user in the set what^kind
are deleted before
+the search of the help. Also, multiple "."
are collapsed.
ask(nil)
and ask.about(nil)
are equivalent to
+ask"/ask^basic"
, that is, help about this helping system
+is given.
Establishes a basis prefix for help searching
+ + + + +ask.base(basis)
@params:
+ +basis
, string.@returns: nothing.
+ +This function changes the basis
(a string) to add as a prefix in the
+desired information path when this does not begin with a slash, /
.
When providind basis
the slashdot "/"
is not required.
Initially basis has the value "ask"
but the loading of a module
+sets the current basis
to the module name.
When calling ask.base""
the current basis is displayed.
Calling ask.base(nil)
establishes "ask"
as basis.
Create html
documentation for a module
ask.doc(modulename, filename)
@params:
+ +modulename
: string (optional) is the name of the module
+ of which we want the documentation.
+ If not provided then the basis is used.filename
: string (optional) is the name of the output file,
+ in html
format. If not given the name of the module is used.
+ If one of the extensions ".html"
or ".htm"
(in lowercase) is
+ not provided then automatically ".html"
is added to the filename.@returns: nothing
+ +@effects: it creates a file.
+ +For generating the documentation module +markdown.lua from Niklas Frykholm +must be accessible. +(Note: the version in luaforge.net is obsolete.)
+ + + + +A CSS file called default.css
, which is possible
+to customize, is used. This file is embeded in the html
output file.
+If not provided the system uses an internal style.
The resulting html
file can be converted, v.g., to PS
+with html2ps, from Jan Kärrman.
+After that ps2pdf
can be used to convert it to PDF format.
by Julio M. Fernández-Díaz, Dept. of Physics, +University of Oviedo, Spain, Version 0.1, February 2010
+ +julio a t uniovi d o t es
+ + + + +THIS CODE IS HEREBY PLACED IN PUBLIC DOMAIN.
+ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE.
+ + + +Julio Manuel Fernández-Díaz
+ +Profesor Titular, +Department of Physics, University of Oviedo (Spain), +February 2010
+ +Abstract:
+ +++ + + +We present
+ +AskLua
, a system for managing help: for on line use in + the interactive interpreter, and for generating documentation inhtml
+ and printed formats.Module
+ +ask
, provided byAskLua
, is little intrusive and, although + it occupies some memory, it can be deleted by the user at any time if + he/she does not want to continue with the help on line.The system is fairly integrated, in such a way that it is possible to + easily add help for an existing module, even of binary type.
+
When we learn a new subject it is suitable to have information, as +complete as possible, about the topic. Often this is achieved through an +expert (teacher). Nevertheless, we have not always access to one. For +this it is necessary to be self-taught. In these occasions a good helping +system, as interactive as possible, is very interesting.
+ +Both Python and Matlab, for example, have interactive help on +line. This allows the beginners a comfortable access to the system during +the first phases of their learning.
+ +Obviously tools like the mentioned languages will have more use +with an easy access to the documentation. And this is very interesting +for designers of Lua modules.
+ +Lua has a quite minimalist approach, almost opposite to two mentioned. +Therefore, it is practically a chimera the inclusion of a help system +in the standard.
+ +Lua does not impose policies, but it is very extensible. This allows us +to easily include a system (module) of help, which is loaded if we wish.
+ +Literate programming, very aimed to the programmer, does not convince +me as a mechanism to solve our problem, since it is necessary to learn +another pseudo-language and it really serves for another thing.
+ +The system of documentation that accompanies stdlua
with @param
,
+@returns
, etc., is not of my taste, since it consists of
+comments in the code. This is nice for checking and studying Lua
+code, but to obtain a system of documentation on line it is
+relatively complex. In addition, the documentation is also aimed to
+the programmer and not so much to the user of some module (for example,
+an engineer who wants to use Numlua).
A system very similar to stdlua
, though more advanced
+is Luadoc, which allows the creation of documentation in html
format,
+also starting from comments in the modules. The final format is friendly
+enough for managing the documentation in a browser, but it does
+not serve for on line help, which is what I initially wished.
In any case, both stdlua
and Luadoc can be easily adapted
+to our purposes (below explained): it is enough to integrate the
+comments (by hand, before their use) in the corresponding help tables.
When I was ending this documentation I discovered in LuaRocks +a module named LuaHelp (being December 13, 2009, the date of the +first version). Initially I thought that my work had been useless since +others have done the module that I was wishing. (Anyhow, new things are +always learnt on programming.) I have to say that the announce of it in +lua list goes unnoticed to me.
+ +Once analyzed the topic more deeply, I have realized the differences +(although LuaHelp might be adapted in many ways, obviously): the +documentation that I propose is included in the own module, whereas the +other one is included in auxiliary files placed in a concrete path. +In addition, the method here presented allows to consult separately +specific parts of the help: how a function in a module is used, +examples of use, the list of functions of a module, etc. They are two +different approaches with its advantages and disadvantages (that the +user can verify).
+ +The unique trouble is that we was compelled to change the name of my
+module. Now its name is ask
(and I had preferred help
). That way
+they can use both modules simultaneously. Anyhow, if the user wishes to
+rename ask
and to call it help
only renaming ask.lua
to help.lua
+is sufficient. In this case it is interesting to change the notices that
+a module gives when loaded (see section
+4.3 How we know that a module has help).
Another specific possibility is to load module ask
, by referencing it
+with a variable help
in the following way:
local help = require "ask"
+
+
+Et voilà. We can choose both ask
and help
for using the present module
+(from this moment we cannot use LuaHelp, obviously).
It has two aspects:
+ +On the one hand, for the programmer of a module who wants to add help. + It will have to be easy to implement, with the minimal modifications + to his/her module.
On the other hand, for the module users. The browsing of the + documentation will have to be as complete as possible but of easy managing.
As programmers, we are interested in the ease of +adding documentation, as complete as possible. Also, if possible several +formats are welcome:
+ +html
format (although some
+ others exist, html
is a standard),In addition, the programmer is very interested in introducing the +documentation only once, and the existence of an automatic system to +produce several formats is nearly a must. This avoids many mistakes and the +modifications, when necessary, have to be done only in one place.
+ +From the user point of view, he/she will want that documentation is:
+ +After many thoughts, many tests, and designing several versions of the help +module, finally the system explained below arised.
+ + +The information of help in Lua can go only (more or less) in tables, with +strings, or in files (as in LuaHelp). It has seemed to me that +file managing would be cumbersome enough since files must be accessible and +possibly where the module resides. It would be +necessary to search in the paths of Lua and there might be more than one +file with the name for that we are looking for (and not to correspond the help to +what it is wished). Therefore I have preferred the other option.
+ +Our module ask
must be able to access to these tables (if they
+exist, but throwing a warning if no available information about a certain
+module exists), then giving the proper required information.
I will describe module ask
.
The first one is where is held the help information of a module.
+In Lua it is obvious that a table is the right place, with possible
+sub-tables, and finally text (strings). Besides, help for a module
+(v.g., mininum
, the example attached to ask
) must be associated
+to it in some way: Lua have to know the help like it knows about
+module functions (otherwise it cannot "help" us).
The help must be sub-divided. The final idea consists of including in
+the module (v.g., inside module.lua
) a table _H
with fields and
+sub-tables. Here is the structure:
_H = {
+ _CHARSET = "iso-8985-15", -- or typically "utf-8"
+ _Name = [[Name of the module]],
+
+ _basic = [[Basic help]],
+ _usage = [[Usage help]],
+ _more = [[Complementary information]],
+ _seealso = [[Links to more information (internal or external)]],
+ _example = [[An example of use]],
+ _notes = [[Notes (Copyright, etc.)]]
+ _version = [[Version, name of the author and other subjects]],
+}
+
+
+(Note: _Name
has a capital "N"
.)
_CHARSET
identifies what encoding has been used in the texts of
+help. For help in html
format ask
directly uses the value
+in this field.
For on line help, ask
detects the operating system character encoding to convert
+from the set of characters indicated in _CHARSET
to that
+one of the system (this way help is correctly visualized on screen).
+At the moment this system only is implemented with UFT-8 and iso-8859:
+If ask
does not detect UTF-8 then it thinks iso-8859
is the enconding
+in the operating system.
This table is included in any point of the file module.lua
(although its
+logical place is near the beginning). None of the fields is mandatory:
+if the programmer does not gives an example the field _example
is
+not specified.
As we see, in order that the minimal interference exists with other
+variables and functions of the module, I have put "_"
in front of the
+table name. In this way, we will avoid collisions of names: it is rare to
+use names of this type (except for local variables). In any case, the user
+of ask
can change in the source code _H
into another name with a text
+editor (since the source code is provided) and to perform accordingly.
Every function of the module (v.g., about
, base
and doc
+in the module ask
) can have its associate help. For example, for the
+first function, it is:
_H.about = {
+ _basic = [[Basic help]],
+ _usage = [[Usage help]],
+ _more = [[Complementary information]],
+ _seealso = [[Links to more information (internal or external)]],
+ _example = [[An example of use]],
+}
+
+
+In some cases we have tables of functions. For example, if tabfun
+is a table with functions, namely f1
y f2
, we could put:
_H.tabfun = { ... }
+
+_H.tabfun.f1 = {
+ _basic = [[ ... ]],
+ _usage = [[ ... ]],
+ _example = [[ ... ]],
+}
+
+_H.tabfun.f2 = { ... }
+
+
+Obviously if no information exists about some function, then we do not
+have to specify it (as _more
and _seealso
in previous f1
).
Initially the format of the string going between [[ ]]
was pure
+text. With it, for the help on line it is enough. Nevertheless, it was
+my desire that the information can also be printed. Then, a more
+advanced format is better. It leaded me to the format markdown,
+which in addition has an implementation in pure Lua, markdown.lua,
+done by Niklas Frykholm. (Note: the version appearing in luaforge.net
+is obsolete, but the one in Luarocks is correct.)
+Actually, this document has been prepared in markdown format.
This format is not much intrusive. This is important because I did not +want to filter help on line, but I wanted it untouched in the strings +of help.
+ + +We have already the information in its place (Lua tables). When the +module is loaded also these tables are. Then, from this moment they +are accessible (evidently they occupy space; in +5 How to use the help interactively +we will see how to free it if wished).
+ +Now we must talk about how to access the information.
+For that, ask
has three functions: about
, base
and doc
.
This was also the first one in being designed: it provides the
+information on line. It is necessary to tell it about we want help
+and which of the diverse information piceces we wish (for example
+basic
, for the basic information). Function about
would be invoked,
+for example:
ask.about"/mininum.root^basic"
+
+
+being mininum
the name of the module, root
the name of one of the
+functions inside the module, and basic
the type of information we wish
+(I will explain below the inclusion of the initial "/"
).
+A caret "^"
separates what we wish from the type of information
+we want.
In another example we would have:
+ +ask.about"/somemod.tabfun.f1^usage"
+
+
+that is, we ask for usage
help of the function f1
inside the table
+tabfun
of the module somemod
.
Another important information for the user exists: the list of functions
+inside the module. The designer of the module has not to introduce it
+because ask
obtains this directly. For displaying it in
+screen we have to use:
ask.about"/mininum^list"
+
+
+
+Also it is possible to ask for all the information that exists about +something, by means of:
+ +ask.about"/mininum.root^all"
+
+
+As we see, the user in this basic format of invocation of about
has to
+type much. The following thing that I did was to admit abbreviations for
+the information type:
b
instead of basic
, that provides basic information;e
instead of example
, that shows us an example;l
instead of list
, that shows a list of functions in the module
+ (or in a table of functions);m
instead of more
, that shows additional information;s
instead of seealso
, that cites us links to more information
+ (external or internal);u
instead of usage
, that shows us how to use the module or function;v
instead of version
, that shows us version information, name of the
+ authors, etc.n
instead of notes
, that shows us notes as the Copyright, etc.a
instead of all
, that provides all the previous information.In this way we have the equivalence:
+ +ask.about"/mininum^list" --><-- ask.about"/mininum^l"
+
+
+The spaces typed in the argument of ask.about
are removed before
+the search. Also about
collapses multiple "."
that could appear in the
+searching string. For example, we would have the equivalence:
ask.about "/ mininum . root ^ usage" --><-- ask.about"/mininum.root^u"
+
+
+When information is not provided, by default it is supposed basic
.
By using nil
as argument of ask.about
we obtain basic information
+about the help system. Then,
ask.about() --><-- ask.about("/ask^basic")
+
+
+are equivalent.
+ +It is necessary to remember that ask
provides the information the
+designer of the module has included in the proper help tables (except for
+list
and all
, obviously). That is, the information might be
+incorrect, for example, if data about the version is included in _more
.
The reader can see we have still to type very much.
+Normally when one works with a module (let's say Numlua)
+we need help in an implicit way for it (and not for other module).
+For it, I have included the function ask.base
.
+This function allows to implicitly assume an information searching prefix.
+For example, by putting:
ask.base"mininum"
+--> Changing help basis to "mininum"
+
+
+from this moment the prefix "/mininum"
is added to the search path
+(sometimes with an added point "."
, when referring to a module or to
+a table of functions inside the module and we wish information about an
+specific function). Then, these are equivalent:
ask.about"/mininum^list" --><-- ask.about"^l"
+
+
+I have called this prefix basis
(stored in a local variable).
Invoking ask.base
using as parameter an empty string ""
, it
+shows us the current basis
:
ask.base""
+--> Help basis is "mininum"
+
+
+On the other hand, there has seemed suitable to me that:
+ +ask.base(nil)
+
+
+changes basis
to "ask"
.
The on line help system is already operative, but I liked no much the
+necessity of writing ask.about
whenever I wanted help (altough, as read
+ask.about"/mininum.root^usage"
is nearly equivalent to ask about
+mininum.root usage). The solution was to include the chunk of code:
__call = function (t, s, ...)
+ if s == nil then
+ about(nil)
+ elseif type(s) == "string" then
+ about(s)
+ else
+ return t[s](...)
+ end
+end
+
+setmetatable(_G.ask, ask)
+
+
+at the end of ask.lua
. The last line allows us to use directly ask
as
+a global variable after doing require "ask"
. The previous chunk of code
+allows the function about
is automatically invoked when ask
+receives a string as argument. Then
ask.about"/mininum.root^usage" --><-- ask"root^u"
+
+
+are equivalent (with basis = "mininum"
). The simplification is
+manifest, and it is near impossible to type less when asking help.
+Actually, slightly less we can type if the module ask
is invoked in
+the way: h = require "ask"
; from this moment we can put h"root^u"
.
+Nevertheless, it seems to me that ask
is not much writing, is more
+descriptive, and it is possible that h
has another use inside our
+program.
With the previous function I had already the module with the wished
+functionality. But it is a pity to have the information only on line and
+not to be able to print it on a decent format. Therefore, I have designed
+the function doc
. The first idea was to adapt a bit the text output. But
+a bit more of expense when writing the help, using the markdown
+format, allows us an output in html
format suitable enough. It was
+easy, since a module exists, markdown.lua (that obviously must be
+installed), which can be invoked from ask
.
The html
version of help is complementary to the on line one.
+It allows a more global vision of a given module, surfing the
+information by browsing among the different functions inside it.
+Hence, it is interesting enough.
In this point system ask
seems Luadoc: both generate html
+format. Nevertheless, the starting points are very different.
It is also possible to obtain the help in printed format, printing from +a browser. Probably it is better to use a program like html2ps +by Jan Kärrman, that generates a Postscript (PS) format (and then PDF, +also the latter with active hyperlinks).
+ +The function ask.doc
has been re-designed several times, and
+I am still not much happy with the result (though it works
+correctly, without being very elegant). It generates html
.
+In this case, partial information of functions is not needed inside a module.
+Therefore, the generated html
file contains all the information
+corresponding to the module (for it, basis
must contain the name of the
+module and not of one of the functions inside).
+For example:
ask.doc""
+
+
+would create html
help for basis
(v.g., "mininum"
previously),
+with the name "mininum.html"
, and:
ask.doc"ask"
+
+
+would generate html
help for ask
.
The html
file generated by ask.doc
contains first generic information
+about the module (the content of _H._basic
, _H._usage
, etc.), then
+the help of all functions (in alphabetical order), to finish with
+_H._version
and _H._notes
.
I apologize since my programming is a little "dirty" in some parts +of the module. My other occupations have not allowed me more time for +improving it, and as one of my professors (León Garzón) said "the best +is enemy of the good".
+ + +The markdown format is simple enough in order that a module
+programmer learns it in an hour. It is not necessary for
+the help on line (even some people can think it is
+something annoying). I believe that it does not introduce much
+noise in the on line help, and nevertheless it allows a good
+presentation in html
(and in PDF after other conversions).
It is possible to look up in the indicated address, but there also +exists a two pages PDF document with a simple help of the +system Markdown Syntax Cheat Sheet (the truth, it is necessary +little more than this simple help to learn markdown).
+ +Thinking a bit more about the help in html
and printed formats we could
+introduce even references to images. It is evident that in the on line help
+(always text mode) it is impossible to visualize an image, but its
+reference can be given in markdown format (not much
+intrusive). It would be in the following way:

+
+
+(Instead of JPG format we can also use PNG or GIF.)
+ +The markdown format also allows to show links to other documents
+(internal or external ones) in a simple way. In the on line help this is
+not relevant but in html
format is. For example, _seealso
+can be designed including links in the way:
internal: [label](#reference)
+
+external: [label](http://what.we.want)
+
+
+
+The general idea is simple: doing it in an incremental way. A complete
+documentation can be a long and tedious task. Therefore, it would be
+convenient to begin with _basic
help, then _usage
, since they are
+both primary aspects. Since it is usual to put those as comments in
+the head of functions, what we have to do is to convert the comments
+already written in a piece of _H
.
Later, other parts that still remain can be added. An example (commented +if wished) is convenient for the user could try it (even copying and +pasting code).
+ +The logical place of introducing this information is before the +corresponding function (as usual), and in case of modules near the +beginning of the file.
+ +If one wants to prepare the help for a compiled module we have to do +the following.
+ +If the binary module is ours, and we can recompile it, we can internally
+change the name of the table if we wish, renaming also the compiled
+file. Then one creates a Lua source file, with the included help, with
+the old name and extension .lua
) that loads the compiled file.
A second possibility is to name in a different way the compiled module
+and the one that we are going to load with require
. For example,
+we have a module compiled in binary format of name mymodule.so
(or
+mymodule.dll
). We will create a Lua file with name mymodule1.lua
+of the form:
-- file mymodule1.lua
+require "mymodule"
+mymodule._H = { ... }
+
+
+To load mymodule
we must use:
require "mymodule1"
+
+
+and we have available the functions mymodule.<function>
,
+and also the help mymodule._H
, since this moment.
Another third possibility is to utilize the same name,
+using the fact that Lua
+first looks for source files in LUA_PATH
and then binary ones in
+LUA_CPATH
. Then, we would create a file mymodule.lua
in some place
+in LUA_PATH
, in the way:
-- file mymodule.lua
+module(..., package.seeall)
+
+local p = package.path
+package.path = ""
+package.loaded["mymodule"] = nil
+
+require"mymodule"
+package.path = p
+
+mymodule._H = [[ ... help ... ]]
+
+
+In this way we can still use:
+ +require "mymodule"
+
+
+as before, but now with included help.
+ +Anyhow, this third system might fail if the binary module mymodule.so
+(or mymodule.dll
) loads in turn modules in Lua source code because
+during its load the search in package.path
is deactivated.
At the end of the Lua file containing the module it is possible to +include the following chunk of code to detect if Lua has been launched +in interactive mode:
+ +-- checks if Lua calling was interactive;
+-- it does not work in all cases, but it does in the normal ones
+local interactive = true
+if _G.arg then
+ for _, v in pairs(_G.arg) do
+ interactive = false
+ if v == "-i" then
+ interactive = true
+ break
+ end
+ end
+end
+
+
+This system of detection does not always work well (and it allows to
+generate the html
documentation with a simple line in the
+operating system shell; we will see it in the following
+section).
It would be interesting that in interactive mode the Lua interpreter
+creates a variable indicating it. Or at least it could use the existent
+variable _PROMPT
for it: at present, it is created only if the user
+defines it at interpreter launch time, otherwise it is nil
.
+It would be enough to define _PROMPT
in interactive mode to be
+">"
if the user did not declare another value, and leave it nil
+in non-interactive mode.
After detecting interactivity we have the following chunk:
+ +if interactive then
+ -- reusing interactive
+ interactive = pcall(require, "ask") --**
+ if interactive then
+ io.stderr:write('Module "mininum" loaded. ')
+ io.stderr:write('To obtain help invoke ask"mininum".\n') --**
+ io.stderr:write('Documentation occupies memory. ')
+ io.stderr:write('For freeing it let execute:\n')
+ io.stderr:write('\n mininum._H = nil\n\n')
+
+ ask.base"mininum"
+ else
+ io.stderr:write('Module "mininum" loaded. It has help\n')
+ io.stderr:write('but module "ask" is not accesible.\n') --**
+ io.stderr:write('Help removed.\n')
+ end
+end
+
+if not interactive then
+ -- deleting _H
+ _H = nil
+ _G.ask = nil
+ collectgarbage()
+end
+
+
+This frees not necessary memory in non-interactive mode, and in +addition it shows on screen some messages in interactive mode.
+ +If we want to change the name of ask.lua
to another one we will have
+also to change the lines indicated by --**
above in a similar way.
Apart from this, in the future there is possible the inclusion of an
+external utility to adapt the format that accompanies Luadoc (and
+also the one that accompanies LuaHelp) for its use in the module
+ask
. This allows to depart not from void for modules whose documentation
+was already prepared in another way.
As before we have exposed, a module that takes help will have to show when
+loaded with require
in order to the user knows that help is available.
The last loaded module that holds help will change basis
to the name
+of the module. The user will have to bear in mind this fact. Anyhow, at
+any time the user will be able to change basis
or he/she will be punctually
+able to use the system with the complete path (that begins with "/"
).
If one wants to delete the help from memory it is easy.
+Let's suppose that we have the module mininum
loaded with:
require "mininum"
+
+
+then, it is enough to type:
+ +mininum._H = nil -- repeat this with each module
+collectgarbage()
+
+
+After that, if we want to completely delete the help system, we will +write:
+ +_G.ask = nil
+collectgarbage()
+
+
+From this moment on, the memory is freed of the helping system and the
+help information of module mininum
(although, obviously its functions
+are still loaded).
html
(and PDF) documentation is generatedIn this type of documents it is important to indicate the set of
+characters used in the help. At these moments I use iso-8859-15
and
+therefore, in the help the field is included:
_H._CHARSET = "iso-8859-15" -- "utf-8" sometimes
+
+
+If one want to use utf-8
(more current these days) it is necessary
+to change it in identical way. If not done so, some characters as "ü",
+"ñ", "á", will appear incorrectly. Normally, as we present modules help
+in English, people names and similar are usually the only words affected
+by this matter.
A very simple way to obtain the documentation in html
format would be
+the following one in interactive mode:
$ lua
+> require "mininum"
+> ask.doc""
+
+
+This would create mininum.html
. Nevertheless, due to a small problem in
+the detection of the interactivity (see the following section), the same
+documentation can be also obtained by means of:
lua -e "require'mininum'; ask.doc''"
+
+
+Once file mininum.html
is created it is possible to visualize it
+with a browser to consult the help. The given style sheet default.css
+places at the left part of the page a fixed menu with the links of the
+whole document, including the list of functions inside the module.
The style of the html
output is customizable, through a style sheet
+with name default.css
(hardcoded inside ask.lua
). I recomend to
+do it only with knowledge of CSS, but it does not hurt to play with it.
When a lot of functions exist in the module it is possible that some links
+disappear at the bottom of the screen. In this case it is necessary
+to diminish the font size (or to visualize the document without style,
+though ugly, it is still operative). Occasionally the font size should
+be diminished to see the examples (the lines do not wrap in the html
+elements of type <pre>
; the examples are converted to <pre>
elements).
Apart from viewing the html
document it is also possible to
+print it, in the browser (not advisable) or with a
+specific program. We recommend the use of html2ps,
+which converts html
in PS, with a style sheet a bit simplified
+(see the documentation provided in the link). In this case, the menu of
+navigation is not printed.
Later, if wished, we can convert the PS file into PDF by means
+of ps2pdf
that accompanies Ghostscript. This method keeps
+the links, and we can navigate also in the PDF file.
Firstly, due to the fact that we modify some tables (internal ones in +the module that is been modified) it is possible that some collateral +effects appear (which I have still not discovered).
+ +On the other hand, a module can be loaded in a local variable, v.g., +in the way:
+ +local m = require"mininum"
+
+
+to use "m."
as prefix for the functions of mininum
and to write less.
+From this moment it is possible to access the help with mininum
and with
+m
. This does not matter very much in interactive mode (as it is possible to
+access the functions with m.fun
and with mininum.fun
).
Nonetheless, ask.doc"m"
creates a file mininum.html
with the correct name
+(and not with the name m.html
).
Functions in the module that are going to have help must not begin
+with "_"
since ask
supposes they are a part of
+its system. We can begin a function with "_"
but it cannot have help.
Sometimes the interactivity does not work well. For example:
+ +lua -e "require'mininum'" somefile.lua
+
+
+shows an initial message and in addition it does not erase the help. Then,
+when it executes somefile.lua
the help tables are in memory (what can
+be counter-productive).
Nevertheless it does not matter too much, because it is enough to +include:
+ +require"mininum"
+
+
+at the beginning of somefile.lua
, to invoke lua
without the
+option -e
, and then the module unloads ask
and the help of
+mininum
(as soon as it detects that the mode is non-interactive)
+before executing the rest of the program.
In interactive mode, in the future it is possible to include http
links
+for more complete help (for example for a description of an algorithm
+or to give a link to a scientific article), or to verify if there are new
+versions of a given module. When some of these links appear
+a browser might be started when the user was asking for help of this type
+(there would be necessary to include a new type, v.g., link
with
+k
as abbreviation).
Tests could also be added (for every function in the module; partly they
+might be the executable examples). For example, two fields would
+be included: _test
with code and _testsolution
with text.
+On having executed the first one it should obtain the second one
+(the module ask
would verify that they are equal). I do not know if
+this is interesting. There would be necessary to study it more deeply.
The system as presented here neither is closed nor is the definitive +solution. It can have much improvement. The author wishes this version +of the module can serve as an incentive for other programmers think about +the topic and develop even better systems (or complete this).
+ + +We have designed an interesting system to provide help for
+other modules. This is versatile, and it allows on line
+access, html
doc generation for its review in a browser,
+and printed format (PS and PDF).
This system is not perfect, but operative. The author believes
+that it can be the seed of a more complete help system
+for Lua. The advanced programmers can do the modifications
+they wish for their personal use. Actually, AskLua
is released
+in the public domain. It is awaiting improvements.
Julio Manuel Fernández-Díaz
+ +Profesor Titular, +Departamento de Física de la Universidad de Oviedo (España), +Febrero de 2010
+ +Resumen:
+ +++ + + +Se presenta
+ +AskLua
un sistema de + gestión de ayuda para Lua: en línea desde + el intérprete interactivo, en formatohtml
y en formato impreso.El módulo
+ +ask
, proporcionado porAskLua
, + es poco intrusivo ocupando memoria que puede ser + liberada por el usuario en cualquier momento si no desea seguir con + la ayuda en línea.El sistema está bastante bien integrado, de tal manera que + se puede añadir fácilmente ayuda para un módulo ya existente, + incluso de tipo binario.
+
Cuando aprendemos una nueva materia es conveniente disponer de información +lo más completa posible sobre el tema. Muchas veces esto se logra con +un experto (profesor). Sin embargo, no siempre tenemos acceso a uno, +por lo que hay que ser autodidacta. En esas ocasiones un buen sistema +de ayuda, lo más interactivo posible, es de gran ayuda.
+ +Tanto Python como Matlab, por poner dos ejemplos, tienen +ayuda interactiva en línea. Esto permite a los principiantes un +cómodo acceso durante las primeras fases de aprendizaje del sistema.
+ +Obviamente una herramienta como los lenguajes citados tendrá más +uso si se facilita el acceso a la documentación. Y eso le interesa +mucho al diseñador de un módulo concreto.
+ +Lua tiene un enfoque casi contrario a los dos lenguajes citados. +Por tanto, es prácticamente una quimera que pueda incluir +un sistema de ayuda en el estándar.
+ +Lua no impone políticas, pero es muy +extensible. Esto nos permite incluir fácilmente un sistema (módulo) +de ayuda, que se carga cuando se desea.
+ +La Programación literaria, muy orientada al programador, no me +convence como mecanismo, pues hay que aprender otro pseudolenguaje +y realmente sirve para otra cosa.
+ +El sistema de documentación que acompaña a stdlua
con @param
,
+@returns
, etc., tampoco es de mi agrado, pues consiste en comentarios al
+código. Esto está bien para revisar y estudiar código Lua, pero conseguir
+un sistema de documentación en línea en este caso es relativamente
+complejo. Además la documentación está también orientada al programador
+y no tanto al usuario de algún módulo (por ejemplo, un ingeniero que
+quiere usar Numlua).
Un sistema muy similar al que viene en stdlua
, aunque más avanzado
+es Luadoc, que permite generar documentación en formato html
+a partir también de comentarios en los módulos. El formato final
+es bastante amigable para consultar la documentación en un navegador,
+pero no sirve para consultas en línea, que es lo que se deseaba
+inicialmente.
En cualquier caso, tanto el sistema de stdlua
como el de Luadoc pueden
+adaptarse fácilmente a lo que se propone más abajo: basta con integrar
+los comentarios en las correspondientes tablas de ayuda.
Cuando estaba acabando esta documentación he descubierto en LuaRocks +un módulo denominado LuaHelp (con fecha de la primera versión de +13 de diciembre de 2009). Al principio pensé que mi +trabajo había sido en vano pues ya estaba hecho el módulo que yo deseaba. +(De todos modos siempre se aprenden cosas nuevas al programar.) +He de decir que se me pasó su anuncio en la lista de correo de lua.
+ +Una vez analizado el tema con más profundidad, me he dado cuenta de +las diferencias (aunque LuaHelp se podría adaptar de muchas maneras +evidentemente): la documentación que yo propongo va incluida en el propio +módulo, mientras que la otra se incluye en ficheros auxiliares situados en +un camino de búsqueda concreto. Además el método aquí presentado permite +consultar por separado partes concretas de la ayuda: cómo se usa una +función dentro de un módulo, ejemplos de uso, la lista de funciones de un +módulo, etc. Son dos enfoques distintos con sus ventajas e inconvenientes +(que el usuario puede comprobar por sí mismo).
+ +La única pega es que me ha hecho cambiar el nombre de mi módulo. Ahora se
+denomina ask
(y yo hubiera preferido help
). De esa manera se pueden
+utilizar ambos a la vez. De todos modos, si se desea renombrar ask
+y llamarlo help
sólo renombrando ask.lua
a help.lua
es suficiente.
+En ese caso es interesante cambiar los avisos que un módulo da al cargarse
+(véase el apartado 4.3 Cómo sabemos que un módulo tiene ayuda).
Otra posibilidad puntual es cargar el módulo ask
referenciándolo con
+variable de nombre help
de la manera siguiente:
local help = require`ask`
+
+
+Et voilà. Ya podemos usar tanto ask
como help
para usar el
+presente módulo (a partir de ese momento no podremos usar LuaHelp
+evidentemente).
Tiene dos aspectos:
+ +Por un lado, para el programador de un módulo al que se quiere + añadir ayuda. Deberá ser fácil de implementar, con las mínimas + modificaciones a su módulo.
Por otro, para los usuarios del módulo. La consulta de la + documentación deberá ser lo más completa posible pero de fácil + manejo.
En el primer aspecto, nos interesa como programadores que sea +fácil de añadir la documentación, lo más completa posible. También +que sea fácil darle uno de los tres formatos siguientes:
+ +html
+ (es preferible este formato estándar que otros existentes),Además, al programador le interesa muchísimo tener que +introducir la documentación una sola vez +y que un sistema automático genere los diferentes formatos. +Esto evita muchos errores y si hay que hacer algún cambio lo +deberá hacer en un solo lugar.
+ +Desde el punto de vista del usuario, éste querrá que sea:
+ +Tras darle vueltas a la cuestión, hacer diversas probaturas y +versiones del módulo de ayuda, al final he llegado al +sistema que se explica en el apartado siguiente.
+ + +La información de ayuda en Lua sólo (bueno más o menos) puede ir en +tablas, con strings, o en ficheros (como en LuaHelp). Me ha parecido +que el manejo de ficheros sería bastante engorroso pues deben estar +accesibles y deberían estar posiblemente donde el módulo propiamente +dicho. Habría que buscar en los path de Lua y podría haber más de un +fichero con el nombre que buscamos y no corresponder la ayuda a lo que +se desea. Por tanto he preferido la otra opción.
+ +Nuestro módulo de ayuda ask
debe poder acceder a esas
+tablas (si existen y sino avisar de que no hay disponible
+información sobre un determinado módulo) y procesar la
+ayuda como se le pida.
Vamos a describir el módulo ask
.
Lo primero es dónde se almacena la ayuda relativa a un módulo.
+En Lua es evidente que debe ir en una tabla, con posibles subtablas,
+y al final texto (strings). Además, la ayuda de un módulo (digamos
+mininum
, el ejemplo que se adjunta con ask
), debe estar asociada
+al mismo de alguna manera: Lua debe conocer la ayuda al igual que conoce
+las funciones del módulo (sino no podría "ayudarnos").
La ayuda debe estar compartimentada. La idea final consiste en
+incluir en el módulo una tabla _H
con campos y también subtablas.
+He aquí la estructura:
_H = {
+ _CHARSET = "iso-8985-15", -- otras veces "utf-8"
+ _Name = [[Nombre del módulo]],
+
+ _basic = [[Ayuda básica]],
+ _usage = [[Ayuda de cómo se utiliza]],
+ _more = [[Información complementaria]],
+ _seealso = [[Enlaces a más información interna o externa]],
+ _example = [[Un ejemplo de utilización]],
+ _version = [[Versión, nombre del autor y otras cuestiones]],
+ _notes = [[Notas, como pueden ser las de Copyright, etc.]]
+}
+
+
+(Nota: fíjense en la N
mayúscula de _Name
.)
_CHARSET
indica qué codificación se ha usado en los textos de
+ayuda. Para ayuda generada en formato html
el sistema usa
+directamente el valor indicado en este campo.
Para ayuda en línea, ask
detecta la codificación de caracteres
+del sistema operativo para convertir la ayuda del conjunto de
+caracteres indicado en _CHARSET
al del sistema (y así visualizarse
+correctamente en pantalla). De momento este sistema sólo está
+implementado con UTF-8 e iso-8859: si ask
no detecta UTF-8
+entonces piensa que el sistema operativo trabaja con iso-8859
.
Esta tabla se incluye en cualquier punto del fichero módulo.lua
+(aunque lo normal es que vaya cerca del principio). Ninguno de los
+campos es obligatorio. Digamos que si no se presenta un ejemplo
+el campo _example
no se especifica.
Como vemos, para que exista la mínima interferencia con otras
+variables y funciones del módulo le he puesto delante del nombre
+"_"
. Eso evitará colisiones de nombres: es raro utilizar nombres
+de ese tipo (excepto para variables locales). En cualquier caso
+el usuario de ask
puede cambiar en el código fuente del mismo
+_H
por otro nombre con un editor de textos (ya que se proporciona
+el código fuente) y tenerlo en cuenta en su propio módulo.
Cada una de las funciones del módulo (v.g. about
, base
y doc
+en el módulo ask
) debe tener su ayuda asociada de la manera (para
+la primera de las funciones):
_H.about = {
+ _basic = [[Una descripción somera del propósito de la función]],
+ _usage = [[Una descripción completa de la manera de invocar
+ la función, con sus argumentos y sus retornos]],
+ _more = [[Información complementaria]],
+ _seealso = [[Enlaces a más información interna o externa]],
+ _example = [[Un ejemplo de utilización]],
+}
+
+
+En algunos casos se tiene tablas de funciones. Si por ejemplo tabfun
+es una tabla con funciones, digamos f1
y f2
, deberíamos poner:
_H.tabfun = { ... }
+
+_H.tabfun.f1 = {
+ _basic = [[ ... ]],
+ _usage = [[ ... ]],
+ _example = [[ ... ]],
+}
+
+_H.tabfun.f2 = { ... }
+
+
+Obviamente si no existe algún tipo de información pues no se especifica
+(como _more
y _seealso
en la f1
anterior).
Inicialmente el formato del texto que va entre [[ ]]
era texto
+puro. Con eso para la ayuda en línea es suficiente. Sin embargo
+era mi deseo que la información también se pudiera imprimir y
+entonces algo de formato más avanzado es mejor. Eso me llevó
+al formato markdown, que además tiene una implementación
+en Lua puro markdown.lua realizada por Niklas Frykholm.
+(Nota: la versión que aparece en luaforge.net está anticuada,
+pero la que aparece en LuaRocks es correcta.)
+Realmente, este documento ha sido preparado usando markdown.
Ese formato no es muy intrusivo. Esto es importante porque no +deseaba hacer un filtrado de la ayuda en línea, sino que quería +que apareciera tal cual está en los strings de ayuda.
+ + +Bueno, ya tenemos la información en su sitio (tablas de Lua). +Cuando se carga el módulo también se cargan esas tablas que +están accesibles desde ese momento (evidentemente ocupan espacio; +luego, en 5 Cómo usar la ayuda interactivamente +veremos cómo liberarlo si se desea).
+ +Ahora debemos hablar de cómo se accede a la información.
+Para ello ask
tiene tres funciones: about
, base
y doc
.
Ésta fue la primera en ser diseñada:
+proporciona la información en línea. Hay que indicarle sobre
+qué queremos ayuda y cuál de las diversas partes de la
+información deseamos (por ejemplo basic
, para la información
+básica). Al final la función about
se invocaría por ejemplo:
ask.about"/mininum.root^basic"
+
+
+siendo mininum
el nombre del módulo, root
el nombre de una de
+las funciones dentro del mismo, y basic
el tipo de información que
+deseamos (luego explico la aparición de "/"
al principio del nombre).
+Como vemos separamos con un acento circunflejo "^"
sobre qué se desea
+información de qué tipo de información queremos.
En otro ejemplo tendríamos:
+ +ask.about"/nombre.tabfun.f1^usage"
+
+
+o sea, le pedimos ayuda de uso para la función f1
dentro de la
+tabla tabfun
del módulo nombre
.
Existe otra información importante para el usuario que no hace
+falta que la introduzca el diseñador del módulo: la lista de
+funciones dentro del módulo. Esa información la obtiene ask
+directamente y se presenta en pantalla mediante:
ask.about"/mininum^list"
+
+
+También se puede pedir de una vez toda la información que +exista sobre algo mediante:
+ +ask.about"/mininum.root^all"
+
+
+Como vemos, el usuario en este formato básico de invocación de
+about
tiene que teclear bastante. Lo siguiente que hice fue
+admitir abreviaturas para el tipo de información pedida:
b
por basic
, que proporciona información básica;e
por example
, que nos muestra un ejemplo;l
por list
, que nos da una lista de funciones en el módulo
+ o en la tabla de funciones sobre la que pedimos ayuda;m
por more
, que nos da información complementaria;s
por seealso
, que nos cita enlaces a más información interna
+ o externa,u
por usage
, que nos muestra cómo se usa el módulo o la función;v
por version
, que nos muestra información sobre la versión,
+ nombre del autor y otras cuestiones;n
por notes
, que nos muestra notas como las de Copyright, etc.a
por all
, que proporciona toda la información anterior.De esta manera se tendría la equivalencia:
+ +ask.about"/mininum^list" --><-- ask.about"/mininum^l"
+
+
+Los espacios tecleados como argumento de ask.about
se eliminan antes
+de la búsqueda. También se colapsan múltiples "."
que pudieran aparecer
+en el string de búsqueda. Por ejemplo, tendríamos la equivalencia:
ask.about"/ mininum . root ^ usage" -- ask.about"/mininum.root^u"
+
+
+
+Por defecto se supone basic
como información si no se proporciona.
Usando nil
como argumento de ask.about
obtenemos información
+básica sobre el sistema de ayuda:
ask.about() --><-- ask.about("/ask^basic")
+
+
+son equivalentes.
+ +Hay que recordar que ask
proporciona la información que el diseñador
+del módulo haya incluido en las correspondientes tablas de ayuda (excepto
+para list
y para all
, evidentemente). O sea que la información podría
+ser incorrecta si se introducen, por ejemplo, datos sobre la version como
+si fuera more
.
El lector verá que todavía tenemos
+que teclear mucho. Normalmente cuando se trabaja con un módulo
+(pongamos Numlua) necesitamos ayuda de manera implícita
+del mismo y no de otro. Para eso he incluido la función ask.base
.
+Esta función permite asumir implícitamente un prefijo de búsqueda
+de información. Por ejemplo, poniendo:
ask.base"mininum"
+--> Changing help basis to "mininum"
+
+
+a partir de ese momento se añade el prefijo "/mininum"
a la ruta de
+búsqueda (a veces con un punto "."
al final si basis
se refiere
+a un módulo o a una tabla de funciones dentro del módulo y deseamos
+información sobre una función concreta), y entonces son equivalentes:
ask.about"/mininum^list" --><-- ask.about"^l"
+
+
+A ese prefijo lo he llamado basis
(se guarda en una variable
+local).
Invocando ask.base
usando como parámetro un string vacío
+""
nos devuelve la basis
actual:
ask.base""
+--> Help basis is "mininum"
+
+
+Por otro lado, me ha parecido conveniente que
+ +ask.base(nil)
+
+
+establezca "ask"
como basis
.
El sistema de ayuda en línea ya está operativo, pero me gustaba poco
+tener que teclear ask.about
cada vez que quería ayuda (aunque según
+se lee ask.about"/mininum.root^usage"
es casi equivalente a la frase
+inglesa ask about mininum.root usage). La solución fue incluir el
+trozo de código:
__call = function (t, s, ...)
+ if s == nil then
+ about(nil)
+ elseif type(s) == "string" then
+ about(s)
+ else
+ return t[s](...)
+ end
+end
+
+setmetatable(_G.ask, ask)
+
+
+al final de ask.lua
. La última línea nos permite utilizar directamente
+ask
como variable global después de hacer require"ask"
. El
+trozo previo de código permite que la función about
sea invocada
+automáticamente cuando a ask
se le pasa un string como argumento.
+O sea que a partir de este momento son equivalentes:
ask.about"/mininum.root^usage" -- ask"root^u"
+
+
+(con basis = "mininum"
). Como podemos observar, la simplificación
+es manifiesta, y menos ya no se puede teclear al pedir ayuda (algo
+menos si se invoca al módulo ask
de la manera:
+h = require"ask"
pues a partir de ese momento se podría
+poner h"root^u"
; sin embargo me parece que ask
no es mucho
+escribir, es más descriptivo, y h
es posible que tenga otra
+utilidad dentro de nuestro programa).
Con lo anterior ya tenía el módulo con la funcionalidad deseada. Pero es
+una pena tener la información sólo en línea y no poder imprimirla en un
+formato decente. Por eso mismo he diseñado la función doc
. La primera
+idea era adaptar un poco la salida de texto. Pero un poco más de gasto al
+escribir la ayuda, usando el formato markdown, permite una salida en
+formato html
bastante conveniente. Eso fue fácil pues existe un módulo
+markdown.lua (que obviamente hay que tener instalado), el cual se
+puede invocar desde ask
.
La versión html
es complementaria a la versión en línea de la ayuda.
+Permite una visión más global de un módulo dado, y se puede consultar
+navegando entre la ayuda de las diferentes funciones. Por tanto, es
+bastante interesante.
Es en este punto donde se parece el sistema ask
y Luadoc, en que
+ambos generan formato html
. Sin embargo los puntos de partida son
+muy diferentes.
También se puede obtener en formato impreso, imprimiendo desde el +navegador, pero quizá es mejor usar un programa como html2ps de +Jan Kärrrman, el cual genera un formato de impresión (PS y luego podemos +obtener PDF, también este último con hiperenlaces activos).
+ +La función ask.doc
, que ha sido rediseñada varias veces, y todavía
+no he quedado totalmente conforme con el resultado (aunque funciona
+correctamente, sin ser muy elegante), es la que genera html
.
+En este caso, no se necesita información parcial de funciones dentro de
+un módulo. Por tanto, la misma genera un fichero html
con toda la
+información correspondiente al módulo (para ello basis
debe
+contener el nombre del módulo y no de una de las funciones del mismo).
+Por ejemplo:
ask.doc""
+
+
+generaría la ayuda para basis
(v.g., "mininum"
anteriormente),
+en formato html
con el nombre "mininum.html"
, y:
ask.doc"ask"
+
+
+generaría la ayuda para ask
.
El fichero html
generado por ask.doc
contiene primero información
+genérica sobre el módulo (el contenido de _H._basic
, _H._usage
,
+etc.) Luego, la ayuda de todas las funciones (en orden alfabético),
+para finalizar con _H._version
y _H._notes
.
He de pedir disculpas por la programación un poco "sucia" de algunas +partes del módulo. Mis otras ocupaciones no me han permitido más tiempo +de momento, y como decía uno de mis profesores (León Garzón) "lo mejor +es enemigo de lo bueno".
+ + + +El formato markdown es lo bastante simple como para que
+un programador de un módulo lo domine en una hora. No es necesario
+para la ayuda en línea (incluso hay algunos que pueden pensar
+en que es algo molesto). Yo creo que no introduce mucho
+ruido en la ayuda en línea y sin embargo permite una buena
+presentación en html
(y en PDF tras otras conversiones).
Se puede consultar en la dirección indicada, pero también +existe un documento PDF de un par de páginas impresas +con una ayuda simple del sistema Markdown Syntax Cheat Sheet +(la verdad es que hace falta poco más que esta ayuda simple +para dominar markdown).
+ +Pensando un poco más en la ayuda en formato html
e impreso
+se pueden incluso introducir referencias a alguna imagen. Es
+evidente que en la ayuda en línea, en modo texto, es imposible
+visualizar una imagen, pero se puede dar su referencia en
+formato markdown que no es demasiado intrusiva. Sería
+de la manera siguiente:

+
+
+(En vez de formato JPG se puede usar también PNG o GIF.)
+ +El formato markdown permite también indicar enlaces
+a otros documentos (internos o externos) de una manera
+simple. En la ayuda en línea no es relevante pero sí en
+formato html
. Por ejemplo, _seealso
puede diseñarse
+incluyendo enlaces de la manera:
internos: [etiqueta](#referencia)
+
+externos: [etiqueta](http://lo.que.sea)
+
+
+
+La idea general es sencilla: hacerlo de manera incremental.
+La documentación completa puede ser una labor larga y tediosa.
+Por tanto, habría que comenzar implementando _basic
y luego
+_usage
, pues son los dos aspectos primarios. Como ese
+suele coincidir con lo que se pone como comentario en la cabecera
+de una función lo que tenemos que hacer es convertir los comentarios
+ya escritos en parte de _H
.
Posteriormente se pueden ir añadiendo diferentes partes que nos fueron +quedando. Un ejemplo (comentado si se desea) suele ser conveniente para +que el usuario pueda probarlo (incluso copiando y pegando código).
+ +El lugar lógico de introducir esa información es delante de la +función correspondiente (como se suele hacer con los comentarios +descriptivos de su funcionalidad) y en el caso de módulos cerca +del principio del fichero.
+ +Si se desea preparar la ayuda para un módulo compilado tenemos +que hacer lo siguiente.
+ +Si el módulo binario es nuestro, y podemos recompilarlo a nuestro gusto,
+podemos cambiar el nombre de la tabla internamente si lo deseamos,
+y renombrar también el fichero compilado. Luego se crea un fichero
+fuente Lua, con la ayuda incluida, con el nombre antiguo pero extensión
+.lua
que carga el fichero compilado.
Una segunda posibilidad es denominar de manera diferente el módulo
+compilado y el que vamos a cargar con require
. Sea por ejemplo
+un módulo compilado en formato binario de nombre mymodule.so
(o
+mymodule.dll
). Crearemos un fichero Lua con nombre mymodule1.lua
+de la forma:
-- fichero mymodule1.lua
+require "mymodule"
+mymodule._H = { ... }
+
+
+Para cargar mymodule
debemos usar:
require "mymodule1"
+
+
+y tendremos a nuestra disposición las funciones mymodule.<function>
+y también la ayuda mymodule._H
, a partir de ese momento.
Otra tercera posibilidad es usar el mismo nombre, usando el hecho de
+que Lua primero busca ficheros fuente en LUA_PATH
y luego binarios en
+LUA_CPATH
. Crearíamos entonces un fichero mymodule.lua
en el mismo
+lugar donde está el binario, de la manera:
-- fichero mymodule.lua
+module(..., package.seeall)
+
+local p = package.path
+package.path = ""
+package.loaded["mymodule"] = nil
+
+require"mymodule"
+package.path = p
+
+mymodule._H = [[ayuda]]
+
+
+De esa manera podemos seguir usando
+ +require "mymodule"
+
+
+como siempre, pero ahora con ayuda incluida.
+ +De todos modos, este último método podría fallar si el módulo
+binario mymodule.so
(o mymodule.dll
) carga a su vez módulos
+en código fuente Lua pues durante el momento de su carga está
+desactivada la búsqueda en package.path
.
Al final de fichero Lua que contiene el módulo se puede incluir +el siguiente trozo de código para detectar si Lua ha sido +lanzado de manera interactiva:
+ +-- checks if Lua calling was interactive;
+-- it does not work in all cases, but it does in the normal ones
+local interactive = true
+if _G.arg then
+ for _, v in pairs(_G.arg) do
+ interactive = false
+ if v == "-i" then
+ interactive = true
+ break
+ end
+ end
+end
+
+
+Este sistema de detección no siempre funciona bien (y eso permite generar
+la documentación html
con una simple línea dentro del intérprete de
+comando del sistema operativo, como veremos en el siguiente apartado).
Sería interesante que en modo interactivo Lua crease una variable que
+lo indicara o al menos usara _PROMPT
para ello: si no se expresa
+explicitamente _PROMPT
está sin definir y Lua usa ">"
para ese
+cometido directamente. Bastaría sólo con definir _PROMPT
en modo
+interactivo (">"
si el usuario no definió otro) y dejarlo sin definir
+en otro caso.
Una vez detectada la interactividad del intérprete tendríamos +el siguiente bloque de código:
+ +if interactive then
+ -- reusing interactive
+ interactive = pcall(require, "ask") --**
+ if interactive then
+ io.stderr:write('Module "mininum" loaded. ')
+ io.stderr:write('To obtain help invoke ask"mininum".\n') --**
+ io.stderr:write('Documentation occupies memory. ')
+ io.stderr:write('For freeing it let execute:\n')
+ io.stderr:write('\n mininum._H = nil\n\n')
+
+ ask.base"mininum"
+ else
+ io.stderr:write('Module "mininum" loaded. It has help\n')
+ io.stderr:write('but module "ask" is not accesible.\n') --**
+ io.stderr:write('Help removed.\n')
+ end
+end
+
+if not interactive then
+ -- deleting _H
+ _H = nil
+ _G.ask = nil
+ collectgarbage()
+end
+
+
+Esto libera memoria no necesaria en modo no interactivo, y además +imprime en pantalla algunos mensajes en modo interactivo.
+ +Si deseamos cambiar el nombre de ask.lua
a otro
+nombre deberemos cambiar también las líneas indicadas por --**
+más arriba de manera acorde.
En otro orden de cosas, en el futuro es posible la inclusión de una
+utilidad externa para adaptar el formato que acompaña a Luadoc
+(y también el que acompaña a LuaHelp) para su uso con el módulo
+ask
. Esto permite no partir de vacío para módulos cuya documentación
+ya estaba preparada de algún modo.
Como antes hemos expuesto, un módulo que lleve ayuda deberá
+indicarlo al cargarse con require
para que el usuario sepa
+que tiene disponible la ayuda.
El último módulo cargado que posea ayuda cambiará basis
por el nombre
+del módulo. Esto deberá tenerlo presente el usuario. De todos modos,
+en cualquier momento éste podrá cambiar basis
a su gusto o podrá
+de manera puntual usar el sistema con la ruta completa (que empieza
+con "/"
).
Si se desea eliminar la ayuda de la memoria es fácil. Supongamos
+que tenemos el módulo mininum
cargado con
require "mininum"
+
+
+pues entonces basta con teclear:
+ +mininum._H = nil -- repetir esto con cada módulo
+collectgarbage()
+
+
+Luego, si queremos eliminar completamente el sistema de ayuda +teclearemos:
+ +_G.ask = nil
+collectgarbage()
+
+
+A partir de este momento ya no ocupa memoria ni la ayuda concreta
+del módulo mininum
(aunque evidentemente sus funciones siguen
+cargadas), ni el sistema ask
.
En este tipo de documentos es importante indicar el
+conjunto de caracteres usado en la ayuda. En estos momentos
+yo estoy trabajando con iso-8859-15
y por tanto, en la
+ayuda se incluye el campo:
_H._CHARSET = "iso-8859-15" -- "utf-8" en otros casos
+
+
+Si se está usando utf-8
(más corriente estos días) se debe cambiar
+de manera acorde. Si no se hace así algunos caracteres como "ü", "ñ",
+"á", saldrán incorrectamente. Se suele presentar la documentación en
+inglés por lo que el presente tema típicamente sólo afectaría a nombres
+de personas con esos caracteres.
Una manera muy sencilla de obtener la documentación en formato
+html
sería la siguiente en modo interactivo:
$ lua
+> require "mininum"
+> ask.doc""
+
+
+Esto generaría mininum.html
. Sin embargo, debido a un pequeño problema
+en la detección de la interactividad (véase el siguiente apartado),
+esta misma documentación también se puede lograr mediante:
lua -e "require'mininum'; ask.doc''"
+
+
+Una vez se tiene el fichero con formato html
se puede visualizar
+con un navegador cualquiera para consultar la ayuda. La hoja de estilo
+default.css
proporcionada sitúa en la parte izquierda un menú fijo
+con los enlaces de todo el documento, incluyendo la lista de funciones
+dentro del módulo.
El estilo del fichero html
de salida es modificable, a través de la
+hoja de estilo default.css
(nombre incluido dentro de ask.lua
).
+Se recomienda sólo hacerlo en caso de conocimientos de CSS, pero no
+hace daño jugar con él.
Si existen muchas funciones en el módulo es posible que no se vea el
+final de la lista dentro de la pantalla. En ese caso se debe disminuir el
+tamaño de letra (o visualizar el documento sin estilo, que aunque es más
+feo, es todavía operativo). En ocasiones también tiene que disminuirse
+el tamaño de letra para ver los ejemplos (las líneas no dan vuelta en
+los elementos html
de tipo <pre>
, a los que se convierten
+los ejemplos).
Aparte de visualizar el documento html
también se puede imprimir, ya sea
+con el navegador (no recomendable) o con un programa específico. Aquí se
+recomienda la utilización de html2ps, que convierte html
en PS,
+con una hoja de estilo un poco simplificada (véase la documentación
+proporcionada en el enlace). En este caso, el menú de navegación no
+se imprime.
Posteriormente, si se desea, se puede convertir el fichero PS en
+PDF mediante el programa ps2pdf
que acompaña a Ghostscript. Tiene
+este sistema la particularidad de que los enlaces se mantienen, pudiendo
+también navegarse con enlaceds en el fichero PDF.
En primer lugar, debido a que modificamos algunas tablas +(internas en el módulo del que se quiere ayuda) es posible +que haya algunos efectos colaterales (que yo todavía no he +descubierto).
+ +Por otro lado, cuando se carga un módulo en una variable local +de la manera, por ejemplo:
+ +local m = require"mininum"
+
+
+para usar "m."
como prefijo para las funciones de mininum
y escribir
+menos, a partir de ese momento se puede acceder a la ayuda con mininum
+y con m
, lo que no importa mucho en modo interactivo (al igual que se
+puede acceder a las funciones con m.fun
y con mininum.fun
).
Si embargo, ask.doc"m"
genera un fichero mininum.html
con el nombre
+del módulo mininum
(y no el fichero m.html
).
Las funciones en el módulo que vayan a tener ayuda no deben empezar por
+"_"
pues ask
supone que son parte de su sistema (no es que no se
+puedan definir y usar, sino que no se puede proporcionar ayuda).
Algunas veces la interactividad no funciona bien. Por ejemplo:
+ +lua -e "require'mininum'" somefile.lua
+
+
+imprime un mensaje al principio y además no borra la ayuda. O sea
+que cuando se ejecuta somefile.lua
las tablas de ayuda están en
+memoria (lo que puede ser contraproducente).
Sin embargo no importa demasiado pues basta incluir
+ +require"mininum"
+
+
+al principio de somefile.lua
e invocar a lua
sin la opción -e
,
+y entonces se descarga el módulo ask
y la ayuda de mininum
(una vez
+que detecta que el modo no es interactivo) antes de ejecutar el resto
+del programa.
En modo interactivo, en el futuro se puede hacer que haya enlaces http
+para ayuda más completa (por ejemplo para una descripción de un algoritmo
+o para dar un enlace a un artículo científico), o para comprobar si hay
+nuevas versiones de un módulo dado. Cuando apareciera uno de esos enlaces
+se podría lanzar un navegador cuando el usuario pidiera ayuda de ese
+tipo (habría que incluir un nuevo tipo, v.g., link
equivalente en el
+modo de una letra a k
).
También se pueden añadir test ejecutables (o sea pruebas en cada función
+del módulo; en parte podrían ser los ejemplos ejecutados). Por ejemplo,
+se incluirían dos campos: _test
con código y _testsolution
con
+texto. Al ejecutar el primero de ellos se debería obtener el segundo
+(el módulo ask
comprobaría que son iguales). No sé si esto es o no
+interesante. Habría que estudiarlo más.
El sistema según se ha presentado no está cerrado ni es la solución +definitiva. Puede tener muchas mejoras y así lo desea su autor, +que sea un acicate para que otros programadores piensen sobre el tema +y desarrollen incluso mejores sistemas (o completen éste).
+ + +Se ha diseñado un interesante sistema para proporcionar ayuda
+en otros módulos. El mismo es versátil, permitiendo ayuda
+en línea, en formato html
para su consulta en un navegador
+y en formato PS o PDF para impresión.
El sistema no es perfecto, pero sí operativo. El autor cree
+que puede ser la semilla de un sistema más completo.
+El lector avezado en Lua puede hacer las modificaciones y
+adaptaciones que crea convenientes para su uso personal.
+Realmente, AskLua
se distribuye como dominio público.
+Está esperando mejoras.
mininum
is a minimal numerical library (with only three
+functions), developed for accompanying the ask
helping system. The
+purpose is only to serve as an example.
mininum
displays error messages in stderr.
Note: this library is very simple, and it is not intended +for heavy calculation (but it is usable).
+ +Call ask"<function>"
for information on <function>
.
+derivative +quadrature +root +
+ + + + + +Calculates the first derivative of a function
+ + + + +mininum.derivative(f, x, aerr)
@params:
+ +f
: a function of a real variable.
x
: number, is the abscissa at which f'(x) is calculated.
aerr
: number (optional), is the intended absolute error of the solution
+ (1.0e-6 as default). The minimum value of aerr
is 1.0e-15.
@returns: number, the first derivative of function f at x.
+ + + + +Function mininum.derivative
uses the central difference formula:
f'(x0) ~ [f(x0+h)-f(x0-h)]/(2 h)
+ +with successive decreasing values of h: h/2, h/4, h/8, etc., +and applying the Richardson extrapolation method to improve +the convergence.
+ + + + +Press et al. (1992), +Numerical Recipes in Fortran, p. 180, CUP.
+ + + + +require"mininum"
+
+local eval = 0
+local function g (x)
+ eval = eval+1
+ return sin(x)
+end
+
+local aerr = 1.e-6
+local d1 = mininum.derivative(g, 2, aerr)
+local e1 = cos(2)
+
+print("calculated solution = ", d1) -- -0.41614683654713
+print("actual solution = ", e1) -- -0.41614683654714
+print("intended error = ", aerr) -- 1e-06
+print("actual absol. error = ", d1-e1) -- 9.9364960703952e-15
+print("function evaluations= ", eval) -- 10
+
+
+
+
+Calculates the definite integral of a function
+ + + + +mininum.quadrature(f, a, b, rerr)
@params:
+ +f
: a function of a real variable.
a
: number, the lower limit in the integral.
b
: number, the upper limit in the integral.
rerr
: number (optional), the relative intended error in the solution
+ (if not given 1.0e-6 is assumed). The minimum value of rerr
is 1.0e-15.
@returns: number, the definite integral of f
between abscissas a
and b
.
Function mininum.quadrature
uses the midpoint formula:
I ~ h*[sum f(xi)] with i = 1/2, 3/2, ...
+ +being n the number of intervals, h = (b-a)/n, and xi = a+h*i.
+ +This formula works even for quadratures when the function at +one or both limits is infinite but the integral exists.
+ +The method is iterative, multiplying n by 3 at each step, +until the relative error is achieved or a maximum of 14 iterations +are reached (1594323 function evaluations). +At least 9 ordinates are calculated.
+ +At each iteration an Aitken-delta^2 process is performed. +This normally accelerates very much the convergence.
+ + + + +Press et al. (1992), +Numerical Recipes in Fortran, p. 129 and p. 160, CUP.
+ +For the Aitken acceleration see the +Wikipedia.
+ + + + +require"mininum"
+
+local eval
+local function s (x)
+ eval = eval+1
+ -- a difficult case for Romberg quadrature
+ return 1/sqrt(x)
+end
+
+local rerr = 1.0e-5
+local eval = 0
+local q = mininum.quadrature(s, 0, 0.5, rerr)
+local e = 2*sqrt(0.5)
+
+print("calculated solution = ", q) -- 1.414213736863
+print("actual solution = ", e) -- 1.4142135623731
+print("intended error = ", rerr) -- 1e-05
+print("actual relat. error = ", (q-e)/e) -- 1.2338296867489e-07
+print("function evaluations= ", eval) -- 243
+
+
+
+
+Determines a root of a function between two abscissas
+ + + + +mininum.root(f, a, b, errx)
@params:
+ +f
: function of a real variable, of which we want the root,
+ x such that f(x) = 0.
a
: number.
b
: number. The root is searched between the abscissas a
and b
.
errx
: number (optional). The function returns a value when
+ the difference between two successive approximations of the
+ root is less than errx
(in absolute value).
+ If no value is provided for errx
, then 1.0e-6 is assumed.
+ The minimum value of errx
is 1.0e-15.
@returns: number, an estimation of the root.
+ + + + +function mininum.root
uses regula falsi method.
The function must be continuous, and the provided +abscissas a and b must accomplish f(a)*f(b) < 0. +In this case the method always converge towards a solution.
+ +It is an iterative method, with (somewhat) superlinear convergence. +As much 30 iterations are done.
+ +Here the "Illinois" version of the method is used.
+ + + + +require"mininum"
+
+local eval = 0
+local function f (x)
+ eval = eval+1
+ return x*(3+x*(-4+2*x)) -- difficult for classical regula falsi
+end
+
+local errx = 1.0e-8
+local x = mininum.root(f, -1, 1, errx)
+
+print("calculated solution = ", x) -- 3.9008079929199e-19
+print("actual solution = ", 0) -- 0
+print("intended error = ", errx) -- 1e-08
+print("actual error = ", x) -- 3.9008079929199e-19
+print("function evaluations= ", eval) -- 14
+
+
+
+
+
+by Julio M. Fernández-Díaz, Dept. of Physics, +University of Oviedo, Spain, Version 0.1, February 2010
+ +julio a t uniovi d o t es
+ + + + +THIS CODE IS HEREBY PLACED IN PUBLIC DOMAIN.
+ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE.
+ + + +