From 17ff8d11d679d6e241537d8ad3cdf35e4fc21589 Mon Sep 17 00:00:00 2001 From: Elie Roux 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 +% +% \iffalse +% +%\NeedsTeXFormat{LaTeX2e} +%\ProvidesPackage{luaextra.sty} +% [2009/04/15 v0.91 Lua additional functions.] +% +% \fi +%<*driver> +\documentclass{ltxdoc} +\EnableCrossrefs +\CodelineIndex +\begin{document} + \DocInput{luaextra.dtx}% +\end{document} +% +% \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 +% +% \fi +% \Finale +\endinput -- cgit v1.2.3