From 17ff8d11d679d6e241537d8ad3cdf35e4fc21589 Mon Sep 17 00:00:00 2001
From: Elie Roux <elie.roux@telecom-bretagne.eu>
Date: Fri, 17 Apr 2009 11:46:44 +0200
Subject: cleanup and documentation of luaextra

---
 luaextra.dtx | 965 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 965 insertions(+)
 create mode 100644 luaextra.dtx

(limited to 'luaextra.dtx')

diff --git a/luaextra.dtx b/luaextra.dtx
new file mode 100644
index 0000000..7a55d76
--- /dev/null
+++ b/luaextra.dtx
@@ -0,0 +1,965 @@
+% \iffalse meta-comment
+%
+% Copyright (C) 2009 by PRAGMA ADE / ConTeXt Development Team
+%
+% See ConTeXt's mreadme.pdf for the license.
+%
+% This work consists of the main source file luaextra.dtx
+% and the derived file luaextra.lua.
+%
+% Unpacking and documentation:
+%    pdftex luaextra.dtx
+%
+%    The class ltxdoc loads the configuration file ltxdoc.cfg
+%    if available. Here you can specify further options, e.g.
+%    use A4 as paper format:
+%       \PassOptionsToClass{a4paper}{article}
+%
+%
+%
+%<*ignore>
+\begingroup
+
+\input docstrip.tex
+\Msg{************************************************************************}
+\Msg{* Installation}
+\Msg{* Package: luaextra 2009/03/19 v0.91 Lua additional functions.}
+\Msg{************************************************************************}
+
+\keepsilent
+\askforoverwritefalse
+
+\let\MetaPrefix\relax
+
+\preamble
+This is a generated file.
+
+Copyright (C) 2009 by PRAGMA ADE / ConTeXt Development Team
+
+See ConTeXt's mreadme.pdf for the license.
+
+This work consists of the main source file luaextra.dtx
+and the derived file luaextra.lua.
+
+\endpreamble
+
+\let\MetaPrefix\DoubleperCent
+
+\generate{%
+  \file{luaextra.drv}{\from{luaextra.dtx}{driver}}%
+}
+
+% The following hacks are to generate a lua file with lua comments starting by -- instead of %%
+
+\def\MetaPrefix{-- }
+
+\def\luapostamble{%
+  \MetaPrefix^^J%
+  \MetaPrefix\space End of File `\outFileName'.%
+}
+
+\def\currentpostamble{\luapostamble}%
+
+\generate{%
+  \usedir{luatex/lua/luaextra}%
+  \file{luaextra.lua}{\from{luaextra.dtx}{lua}}%
+}
+
+\obeyspaces
+\Msg{************************************************************************}
+\Msg{*}
+\Msg{* To finish the installation you have to move the following}
+\Msg{* files into a directory searched by TeX:}
+\Msg{*}
+\Msg{*     luaextra.lua}
+\Msg{*}
+\Msg{* Happy TeXing!}
+\Msg{*}
+\Msg{************************************************************************}
+
+\endgroup
+%</ignore>
+% \iffalse
+%<driver>
+%<driver>\NeedsTeXFormat{LaTeX2e}
+%<driver>\ProvidesPackage{luaextra.sty}
+%<driver>  [2009/04/15 v0.91 Lua additional functions.]
+%<driver>
+% \fi
+%<*driver>
+\documentclass{ltxdoc}
+\EnableCrossrefs
+\CodelineIndex
+\begin{document}
+  \DocInput{luaextra.dtx}%
+\end{document}
+%</driver>
+% \fi
+% \CheckSum{0}
+%
+% \CharacterTable
+%  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
+%   Lower-case    \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
+%   Digits        \0\1\2\3\4\5\6\7\8\9
+%   Exclamation   \!     Double quote  \"     Hash (number) \#
+%   Dollar        \$     Percent       \%     Ampersand     \&
+%   Acute accent  \'     Left paren    \(     Right paren   \)
+%   Asterisk      \*     Plus          \+     Comma         \,
+%   Minus         \-     Point         \.     Solidus       \/
+%   Colon         \:     Semicolon     \;     Less than     \<
+%   Equals        \=     Greater than  \>     Question mark \?
+%   Commercial at \@     Left bracket  \[     Backslash     \\
+%   Right bracket \]     Circumflex    \^     Underscore    \_
+%   Grave accent  \`     Left brace    \{     Vertical bar  \|
+%   Right brace   \}     Tilde         \~}
+%
+% \GetFileInfo{luaextra.drv}
+%
+% \title{The \textsf{luaextra} package}
+% \date{2009/04/15 v0.91}
+% \author{Elie Roux \\ \texttt{elie.roux@telecom-bretagne.eu}}
+%
+% \maketitle
+%
+% \begin{abstract}
+% Additional lua functions taken from the libs of Con\TeX t.
+% \end{abstract}
+%
+% \section{Overview}
+%
+% Lua is a very minimal language, and it does not have a lot of built-in functions. Some functions will certainly be needed by a lot of packages. Instead of making each of them implement these functions, the aim of this file is to provide a minimal set of functions. All functions are taken from Con\TeX t libraries.
+%
+% There are some differences with the Con\TeX t funtions though, especially on names: for example the \texttt{file.*} funtions are renamed in \texttt{fpath.*}. It seems more logical as they deal with file paths, not files. Also the \texttt{file.is\_readable} and \texttt{file.is\_writable} are renamed \texttt{lfs.is\_readable} and \texttt{lfs.is\_writable}.
+%
+% If you use a function you think is missing in this file, please tell the maintainer.
+%
+% \texttt{Warning:} Even if the names will certainly remain the same, some implementations may differ, and some functions might appear or dissapear. As Lua\TeX\ is not stable, this file is not neither.
+%
+% All functions are described in this document, but the one of the functions you'll use most will certainly be \texttt{table.serialize} (also named \texttt{table.tostring}) that takes a table and returns an intented string describing the table. It describes the table so that Lua\TeX\ can read it again as a table. You can do a lot of things with this functions, like printing a table for debugging, or saving a table into a file. Functions are also converted into bytecode to be saved.
+%
+% \section{\texttt{luaextra.lua}}
+%
+% \iffalse
+%<*lua>
+% \fi
+%
+%    \begin{macrocode}
+local module = {
+    name          = "luaextra",
+    version       = 0.91,
+    date          = "2009/04/15",
+    description   = "Lua additional functions.",
+    author        = "Hans Hagen, PRAGMA-ADE, Hasselt NL & Elie Roux",
+    copyright     = "PRAGMA ADE / ConTeXt Development Team",
+    license       = "See ConTeXt's mreadme.pdf for the license",
+}
+
+luatextra.provides_module(module)
+
+%    \end{macrocode}
+%
+% \begin{macro}{string:stripspaces}
+%
+% A function to strip the spaces at the beginning and at the end of a string.
+%
+%    \begin{macrocode}
+
+function string:stripspaces()
+    return (self:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{string.is boolean}
+%
+% If the argument is a string describing a boolean, this function returns the boolean, otherwise it retuns nil.
+%
+%    \begin{macrocode}
+
+function string.is_boolean(str)
+    if type(str) == "string" then
+        if str == "true" or str == "yes" or str == "on" or str == "t" then
+            return true
+        elseif str == "false" or str == "no" or str == "off" or str == "f" then
+            return false
+        end
+    end
+    return nil
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{string.is number}
+%
+% Returns true if the argument string is a number.
+%
+%    \begin{macrocode}
+
+function string.is_number(str)
+    return str:find("^[%-%+]?[%d]-%.?[%d+]$") == 1
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{lpeg.space and lpeg.newline}
+%
+% Two small helpers for \texttt{lpeg}, that will certainly be widely used: spaces and newlines.
+%
+%    \begin{macrocode}
+
+lpeg.space    = lpeg.S(" \t\f\v")
+lpeg.newline  = lpeg.P("\r\n") + lpeg.P("\r") +lpeg.P("\n")
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.fastcopy}
+%
+% A function copying a table fastly.
+%
+%    \begin{macrocode}
+
+if not table.fastcopy then do
+
+    local type, pairs, getmetatable, setmetatable = type, pairs, getmetatable, setmetatable
+
+    local function fastcopy(old) -- fast one
+        if old then
+            local new = { }
+            for k,v in pairs(old) do
+                if type(v) == "table" then
+                    new[k] = fastcopy(v) -- was just table.copy
+                else
+                    new[k] = v
+                end
+            end
+            local mt = getmetatable(old)
+            if mt then
+                setmetatable(new,mt)
+            end
+            return new
+        else
+            return { }
+        end
+    end
+
+    table.fastcopy = fastcopy
+
+end end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.copy}
+%
+% A function copying a table in more cases than fastcopy, for example when a key is a table.
+%
+%    \begin{macrocode}
+
+if not table.copy then do
+
+    local type, pairs, getmetatable, setmetatable = type, pairs, getmetatable, setmetatable
+
+    local function copy(t, tables) -- taken from lua wiki, slightly adapted
+        tables = tables or { }
+        local tcopy = {}
+        if not tables[t] then
+            tables[t] = tcopy
+        end
+        for i,v in pairs(t) do -- brrr, what happens with sparse indexed
+            if type(i) == "table" then
+                if tables[i] then
+                    i = tables[i]
+                else
+                    i = copy(i, tables)
+                end
+            end
+            if type(v) ~= "table" then
+                tcopy[i] = v
+            elseif tables[v] then
+                tcopy[i] = tables[v]
+            else
+                tcopy[i] = copy(v, tables)
+            end
+        end
+        local mt = getmetatable(t)
+        if mt then
+            setmetatable(tcopy,mt)
+        end
+        return tcopy
+    end
+
+    table.copy = copy
+
+end end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.serialize}
+%
+% A bunch of functions leading to \texttt{table.serialize}.
+%
+%    \begin{macrocode}
+
+function table.sortedkeys(tab)
+    local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
+    for key,_ in pairs(tab) do
+        srt[#srt+1] = key
+        if kind == 3 then
+            -- no further check
+        else
+            local tkey = type(key)
+            if tkey == "string" then
+            --  if kind == 2 then kind = 3 else kind = 1 end
+                kind = (kind == 2 and 3) or 1
+            elseif tkey == "number" then
+            --  if kind == 1 then kind = 3 else kind = 2 end
+                kind = (kind == 1 and 3) or 2
+            else
+                kind = 3
+            end
+        end
+    end
+    if kind == 0 or kind == 3 then
+        table.sort(srt,function(a,b) return (tostring(a) < tostring(b)) end)
+    else
+        table.sort(srt)
+    end
+    return srt
+end
+
+do
+    table.serialize_functions = true
+    table.serialize_compact   = true
+    table.serialize_inline    = true
+
+    local function key(k)
+        if type(k) == "number" then -- or k:find("^%d+$") then
+            return "["..k.."]"
+        elseif noquotes and k:find("^%a[%a%d%_]*$") then
+            return k
+        else
+            return '["'..k..'"]'
+        end
+    end
+
+    local function simple_table(t)
+        if #t > 0 then
+            local n = 0
+            for _,v in pairs(t) do
+                n = n + 1
+            end
+            if n == #t then
+                local tt = { }
+                for i=1,#t do
+                    local v = t[i]
+                    local tv = type(v)
+                    if tv == "number" or tv == "boolean" then
+                        tt[#tt+1] = tostring(v)
+                    elseif tv == "string" then
+                        tt[#tt+1] = ("%q"):format(v)
+                    else
+                        tt = nil
+                        break
+                    end
+                end
+                return tt
+            end
+        end
+        return nil
+    end
+
+    local function serialize(root,name,handle,depth,level,reduce,noquotes,indexed)
+        handle = handle or print
+        reduce = reduce or false
+        if depth then
+            depth = depth .. " "
+            if indexed then
+                handle(("%s{"):format(depth))
+            else
+                handle(("%s%s={"):format(depth,key(name)))
+            end
+        else
+            depth = ""
+            local tname = type(name)
+            if tname == "string" then
+                if name == "return" then
+                    handle("return {")
+                else
+                    handle(name .. "={")
+                end
+            elseif tname == "number" then
+                handle("[" .. name .. "]={")
+            elseif tname == "boolean" then
+                if name then
+                    handle("return {")
+                else
+                    handle("{")
+                end
+            else
+                handle("t={")
+            end
+        end
+        if root and next(root) then
+            local compact = table.serialize_compact
+            local inline  = compact and table.serialize_inline
+            local first, last = nil, 0 -- #root cannot be trusted here
+            if compact then
+              for k,v in ipairs(root) do -- NOT: for k=1,#root do (why)
+                    if not first then first = k end
+                    last = last + 1
+                end
+            end
+            for _,k in pairs(table.sortedkeys(root)) do
+                local v = root[k]
+                local t = type(v)
+                if compact and first and type(k) == "number" and k >= first and k <= last then
+                    if t == "number" then
+                        handle(("%s %s,"):format(depth,v))
+                    elseif t == "string" then
+                        if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+                            handle(("%s %s,"):format(depth,v))
+                        else
+                            handle(("%s %q,"):format(depth,v))
+                        end
+                    elseif t == "table" then
+                        if not next(v) then
+                            handle(("%s {},"):format(depth))
+                        elseif inline then
+                            local st = simple_table(v)
+                            if st then
+                                handle(("%s { %s },"):format(depth,table.concat(st,", ")))
+                            else
+                                serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+                            end
+                        else
+                            serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+                        end
+                    elseif t == "boolean" then
+                        handle(("%s %s,"):format(depth,tostring(v)))
+                    elseif t == "function" then
+                        if table.serialize_functions then
+                            handle(('%s loadstring(%q),'):format(depth,string.dump(v)))
+                        else
+                            handle(('%s "function",'):format(depth))
+                        end
+                    else
+                        handle(("%s %q,"):format(depth,tostring(v)))
+                    end
+                elseif k == "__p__" then -- parent
+                    if false then
+                        handle(("%s __p__=nil,"):format(depth))
+                    end
+                elseif t == "number" then
+                    handle(("%s %s=%s,"):format(depth,key(k),v))
+                elseif t == "string" then
+                    if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+                        handle(("%s %s=%s,"):format(depth,key(k),v))
+                    else
+                        handle(("%s %s=%q,"):format(depth,key(k),v))
+                    end
+                elseif t == "table" then
+                    if not next(v) then
+                        handle(("%s %s={},"):format(depth,key(k)))
+                    elseif inline then
+                        local st = simple_table(v)
+                        if st then
+                            handle(("%s %s={ %s },"):format(depth,key(k),table.concat(st,", ")))
+                        else
+                            serialize(v,k,handle,depth,level+1,reduce,noquotes)
+                        end
+                    else
+                        serialize(v,k,handle,depth,level+1,reduce,noquotes)
+                    end
+                elseif t == "boolean" then
+                    handle(("%s %s=%s,"):format(depth,key(k),tostring(v)))
+                elseif t == "function" then
+                    if table.serialize_functions then
+                        handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(v)))
+                    else
+                        handle(('%s %s="function",'):format(depth,key(k)))
+                    end
+                else
+                    handle(("%s %s=%q,"):format(depth,key(k),tostring(v)))
+                --  handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(function() return v end)))
+                end
+            end
+            if level > 0 then
+                handle(("%s},"):format(depth))
+            else
+                handle(("%s}"):format(depth))
+            end
+        else
+            handle(("%s}"):format(depth))
+        end
+    end
+
+    function table.serialize(root,name,reduce,noquotes)
+        local t = { }
+        local function flush(s)
+            t[#t+1] = s
+        end
+        serialize(root, name, flush, nil, 0, reduce, noquotes)
+        return table.concat(t,"\n")
+    end
+
+    function table.tostring(t, name)
+        return table.serialize(t, name)
+    end
+
+    function table.tohandle(handle,root,name,reduce,noquotes)
+        serialize(root, name, handle, nil, 0, reduce, noquotes)
+    end
+
+    -- sometimes tables are real use (zapfino extra pro is some 85M) in which
+    -- case a stepwise serialization is nice; actually, we could consider:
+    --
+    -- for line in table.serializer(root,name,reduce,noquotes) do
+    --    ...(line)
+    -- end
+    --
+    -- so this is on the todo list
+
+    table.tofile_maxtab = 2*1024
+
+    function table.tofile(filename,root,name,reduce,noquotes)
+        local f = io.open(filename,'w')
+        if f then
+            local concat = table.concat
+            local maxtab = table.tofile_maxtab
+            if maxtab > 1 then
+                local t = { }
+                local function flush(s)
+                    t[#t+1] = s
+                    if #t > maxtab then
+                        f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
+                        t = { }
+                    end
+                end
+                serialize(root, name, flush, nil, 0, reduce, noquotes)
+                f:write(concat(t,"\n"),"\n")
+            else
+                local function flush(s)
+                    f:write(s,"\n")
+                end
+                serialize(root, name, flush, nil, 0, reduce, noquotes)
+            end
+            f:close()
+        end
+    end
+
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.tohash}
+%
+% Returning a table with all values of the argument table as keys, and \texttt{false} as values. This is what we will call a hash.
+%
+%    \begin{macrocode}
+
+function table.tohash(t)
+    local h = { }
+    for _, v in pairs(t) do -- no ipairs here
+        h[v] = true
+    end
+    return h
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.fromhash}
+%
+% Returning a table built from a hash, with simple integer keys.
+%
+%    \begin{macrocode}
+
+function table.fromhash(t)
+    local h = { }
+    for k, v in pairs(t) do -- no ipairs here
+        if v then h[#h+1] = k end
+    end
+    return h
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.contains value}
+%
+% A function returning true if the value \texttt{val} is in the table \texttt{t}.
+%
+%    \begin{macrocode}
+
+function table.contains_value(t, val)
+    if t then
+        for k, v in pairs(t) do
+            if v==val then
+                return true
+            end
+        end
+    end
+    return false
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.contains key}
+%
+% A function returning true if the key \texttt{key} is in the table \texttt{t}
+%
+%    \begin{macrocode}
+
+function table.contains_key(t, key)
+    if t then
+        for k, v in pairs(t) do
+            if k==key then
+                return true
+            end
+        end
+    end
+    return false
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.value position}
+%
+% A function returning the position of a value in a table. This will be important to be able to remove a value.
+%
+%    \begin{macrocode}
+
+function table.value_position(t, val)
+    if t then
+        local i=1
+        for k, v in pairs(t) do
+            if v==val then
+                return i
+            end
+            i=i+1
+        end
+    end
+    return 0
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.key position}
+%
+% A function returning the position of a key in a table.
+%
+%    \begin{macrocode}
+
+function table.key_position(t, key)
+    if t then
+        local i=1
+        for k,v in pairs(t) do
+            if k==key then
+                return i
+            end
+            i = i+1
+        end
+    end
+    return -1
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.remove value}
+%
+% Removes the first occurence of a value from a table.
+%
+%    \begin{macrocode}
+
+function table.remove_value(t, v)
+    local p = table.value_position(t,v)
+    if p ~= -1 then
+        table.remove(t, table.value_position(t,v))
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.remove key}
+%
+% Removing a key from a table.
+%
+%    \begin{macrocode}
+
+function table.remove_key(t, k)
+    local p = table.key_position(t,k)
+    if p ~= -1 then
+        table.remove(t, table.key_position(t,k))
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{table.is empty}
+%
+% Returns true if a table is empty.
+%
+%    \begin{macrocode}
+
+function table.is_empty(t)
+    return not t or not next(t)
+end
+
+%    \end{macrocode}
+%
+%  \texttt{fpath} will contain all the file path manipulation functions. Some functions certainly need a little update or cleanup...
+%
+%    \begin{macrocode}
+
+fpath = { }
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.removesuffix}
+%
+% A function to remove the suffix (extention) of a filename.
+%
+%    \begin{macrocode}
+
+function fpath.removesuffix(filename)
+    return filename:gsub("%.[%a%d]+$", "")
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.addsuffix}
+%
+% A function adding a suffix to a filename, except if it already has one.
+%
+%    \begin{macrocode}
+
+function fpath.addsuffix(filename, suffix)
+    if not filename:find("%.[%a%d]+$") then
+        return filename .. "." .. suffix
+    else
+        return filename
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.replacesuffix}
+%
+% A function replacing a suffix by a new one.
+%
+%    \begin{macrocode}
+
+function fpath.replacesuffix(filename, suffix)
+    if not filename:find("%.[%a%d]+$") then
+        return filename .. "." .. suffix
+    else
+        return (filename:gsub("%.[%a%d]+$","."..suffix))
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.dirname}
+%
+% A function returning the directory of a file path.
+%
+%    \begin{macrocode}
+
+function fpath.dirname(name)
+    return name:match("^(.+)[/\\].-$") or ""
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.basename}
+%
+% A function returning the basename (the name of the file, without the directories) of a file path.
+%
+%    \begin{macrocode}
+
+function fpath.basename(fname)
+    if not fname then
+        return nil
+    end
+    return fname:match("^.+[/\\](.-)$") or fname
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.nameonly}
+%
+% Returning the basename of a file without the suffix.
+%
+%    \begin{macrocode}
+
+function fpath.nameonly(name)
+    return ((name:match("^.+[/\\](.-)$") or name):gsub("%..*$",""))
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.suffix}
+%
+% Returns the suffix of a file name.
+%
+%    \begin{macrocode}
+
+function fpath.suffix(name)
+    return name:match("^.+%.([^/\\]-)$") or  ""
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.join}
+%
+% A function joining any number of arguments into a complete path.
+%
+%    \begin{macrocode}
+
+function fpath.join(...)
+    local pth = table.concat({...},"/")
+    pth = pth:gsub("\\","/")
+    local a, b = pth:match("^(.*://)(.*)$")
+    if a and b then
+        return a .. b:gsub("//+","/")
+    end
+    a, b = pth:match("^(//)(.*)$")
+    if a and b then
+        return a .. b:gsub("//+","/")
+    end
+    return (pth:gsub("//+","/"))
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.split}
+%
+% A function returning a table with all directories from a filename.
+%
+%    \begin{macrocode}
+
+function fpath.split(str)
+    local t = { }
+    str = str:gsub("\\", "/")
+    str = str:gsub("(%a):([;/])", "%1\001%2")
+    for name in str:gmatch("([^;:]+)") do
+        if name ~= "" then
+            name = name:gsub("\001",":")
+            t[#t+1] = name
+        end
+    end
+    return t
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.normalize sep}
+%
+% A function to change directory separators to canonical ones (\texttt{/}).
+%
+%    \begin{macrocode}
+
+function fpath.normalize_sep(str)
+    return str:gsub("\\", "/")
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{fpath.localize sep}
+%
+% A function changing directory separators into local ones (\texttt{/} on Unix, |\| on Windows).
+%
+%    \begin{macrocode}
+
+function fpath.localize_sep(str)
+    if os.type == 'windows' or type == 'msdos' then
+        return str:gsub("/", "\\")
+    else
+        return str:gsub("\\", "/")
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{lfs.is writable}
+%
+% Returns true if a file is writable. This function and the following ones are a bit too expensive, they should be made with |lfs.attributes|.
+%
+%    \begin{macrocode}
+
+function lfs.is_writable(name)
+    local f = io.open(name, 'w')
+    if f then
+        f:close()
+        return true
+    else
+        return false
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{lfs.is readable}
+%
+% Returns true if a file is readable.
+%
+%    \begin{macrocode}
+
+function lfs.is_readable(name)
+    local f = io.open(name,'r')
+    if f then
+        f:close()
+        return true
+    else
+        return false
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{math.round}
+%
+% Returns the closest integer.
+%
+%    \begin{macrocode}
+
+if not math.round then
+    function math.round(x)
+        return math.floor(x + 0.5)
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{math.div}
+%
+% Returns the quotient of the euclidian division of n by m.
+%
+%    \begin{macrocode}
+
+if not math.div then
+    function math.div(n,m)
+        return floor(n/m)
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}\begin{macro}{math.mod}
+%
+% Returns the remainder of the euclidian division of n by m.
+%
+%    \begin{macrocode}
+
+if not math.mod then
+    function math.mod(n,m)
+        return n % m
+    end
+end
+
+%    \end{macrocode}
+%
+% \end{macro}
+%
+% \iffalse
+%</lua>
+% \fi
+% \Finale
+\endinput
-- 
cgit v1.2.3

