% \iffalse meta-comment
%
% Written in 2009, 2010 by Manuel Pégourié-Gonnard and Élie Roux.
%     <mpg@elzevir.fr>
%     <elie.roux@telecom-bretagne.eu>
%
% This work is under the CC0 license.
%
% This work consists of the main source file luatexbase-modutils.dtx
% and the derived files
%    luatexbase-modutils.sty modutils.lua
%    test-modutils-plain.tex test-modutils-latex.tex test-modutils.lua
%
% Unpacking:
%    tex luatexbase-modutils.dtx
% Documentation:
%    pdflatex luatexbase-modutils.dtx
%
%<*ignore>
\begingroup
  \def\x{LaTeX2e}%
\expandafter\endgroup
\ifcase 0\ifx\install y1\fi\expandafter
         \ifx\csname processbatchFile\endcsname\relax\else1\fi
         \ifx\fmtname\x\else 1\fi\relax
\else\csname fi\endcsname
%</ignore>
%<*install>
\input docstrip.tex

\keepsilent
\askforoverwritefalse

\let\MetaPrefix\relax

\preamble

Written in 2009, 2010 by Manuel Pegourie-Gonnard and Elie Roux.

This work is under the CC0 license.
See source file '\inFileName' for details.

\endpreamble

\let\MetaPrefix\DoubleperCent

\generate{%
  \usedir{tex/luatex/luatexbase}%
  \file{luatexbase-modutils.sty}{\from{luatexbase-modutils.dtx}{texpackage}}%
}

\generate{%
  \usedir{doc/luatex/luatexbase}%
  \file{test-modutils-plain.tex}{\from{luatexbase-modutils.dtx}{testplain}}%
  \file{test-modutils-latex.tex}{\from{luatexbase-modutils.dtx}{testlatex}}%
}

\def\MetaPrefix{-- }

\def\luapostamble{%
  \MetaPrefix^^J%
  \MetaPrefix\space End of File `\outFileName'.%
}

\def\currentpostamble{\luapostamble}%

\generate{%
  \usedir{tex/luatex/luatexbase}%
  \file{modutils.lua}{\from{luatexbase-modutils.dtx}{luamodule}}%
  \usedir{doc/luatex/luatexbase}%
  \file{test-modutils.lua}{\from{luatexbase-modutils.dtx}{testdummy}}%
}

\obeyspaces
\Msg{************************************************************************}
\Msg{*}
\Msg{* To finish the installation you have to move the following}
\Msg{* files into a directory searched by TeX:}
\Msg{*}
\Msg{*     luatexbase-modutils.sty modutils.lua}
\Msg{*}
\Msg{* Happy TeXing!}
\Msg{*}
\Msg{************************************************************************}

\endbatchfile
%</install>
%<*ignore>
\fi
%</ignore>
%<*driver>
\documentclass{ltxdoc}
\input{lltxb-dtxstyle}
\begin{document}
  \DocInput{luatexbase-modutils.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         \~}
%
% \title{The \pk{luatexbase-modutils} package}
% \date{v0.1 2010-03-27}
% \author{%
%  Manuel P\'egouri\'e-Gonnard \\ \texttt{mpg@elzevir.fr} \and
%   \'Elie Roux \\ \texttt{elie.roux@telecom-bretagne.eu}}
%
% \maketitle
%
% \begin{abstract}
% \end{abstract}
%
% \section{Documentation}
%
% Lua has some embedded module management, with the functions \texttt{module}
% and \texttt{require}. With this package we try get more control on the
% module system, by implementing something close to the \LaTeX 's
% \texttt{\string\usepackage} and \texttt{\string\RequirePackage} macros: the
% \texttt{\string\luatexUseModule} and \texttt{\string\luatexRequireModule}
% that act like them, but for lua files. The functions \texttt{module} and
% \texttt{require} should not be used, in profit of the lua functions
% \texttt{luatexbase.provides\_module} and \texttt{luatexbase.use\_module} or
% \texttt{luatexbase.require\_module}.
%
%    \section{Implementation}
%
%    \subsection{\tex package}
%
%    \begin{macrocode}
%<*texpackage>
%    \end{macrocode}
%
%    \subsubsection{Preliminaries}
%
%    Reload protection, especially for \plaintex.
%
%    \begin{macrocode}
                \csname lltxb@modutils@loaded\endcsname
\expandafter\let\csname lltxb@modutils@loaded\endcsname\endinput
%    \end{macrocode}
%
%    Catcode defenses.
%
%    \begin{macrocode}
\begingroup
  \catcode123 1 % {
  \catcode125 2 % }
  \catcode 35 6 % #
  \toks0{}%
  \def\x{}%
  \def\y#1 #2 {%
    \toks0\expandafter{\the\toks0 \catcode#1 \the\catcode#1}%
    \edef\x{\x \catcode#1 #2}}%
  \y 123 1  % {
  \y 125 2  % }
  \y  35 6  % #
  \y  10 12 % ^^J
  \y  34 12 % "
  \y  36 3  % $ $
  \y  39 12 % '
  \y  40 12 % (
  \y  41 12 % )
  \y  42 12 % *
  \y  43 12 % +
  \y  44 12 % ,
  \y  45 12 % -
  \y  46 12 % .
  \y  47 12 % /
  \y  60 12 % <
  \y  61 12 % =
  \y  64 11 % @ (letter)
  \y  62 12 % >
  \y  95 12 % _ (other)
  \y  96 12 % `
  \edef\y#1{\endgroup\edef#1{\the\toks0\relax}\x}%
\expandafter\y\csname lltxb@modutils@AtEnd\endcsname
%    \end{macrocode}
%
%    Package declaration.
%
%    \begin{macrocode}
\begingroup
  \expandafter\ifx\csname ProvidesPackage\endcsname\relax
    \def\x#1[#2]{\immediate\write16{Package: #1 #2}}
  \else
    \let\x\ProvidesPackage
  \fi
\expandafter\endgroup
\x{luatexbase-modutils}[2010/03/26 v0.1  Module utilities for LuaTeX  (mpg)]
%    \end{macrocode}
%
%    Make sure \luatex is used.
%
%    \begin{macrocode}
\begingroup\expandafter\expandafter\expandafter\endgroup
\expandafter\ifx\csname RequirePackage\endcsname\relax
  \input ifluatex.sty
\else
  \RequirePackage{ifluatex}
\fi
\ifluatex\else
  \begingroup
    \expandafter\ifx\csname PackageWarningNoLine\endcsname\relax
      \def\x#1#2{\begingroup\newlinechar10
        \immediate\write16{Package #1 warning: #2}\endgroup}
    \else
      \let\x\PackageWarningNoLine
    \fi
  \expandafter\endgroup
  \x{luatexbase-modutils}{LuaTeX is required for this package. Aborting.}
  \lltxb@modutils@AtEnd
  \expandafter\endinput
\fi
%    \end{macrocode}
%
%    Load the package loader.
%
%    \begin{macrocode}
\begingroup\expandafter\expandafter\expandafter\endgroup
\expandafter\ifx\csname RequirePackage\endcsname\relax
  \input luatexbase-loader.sty
\else
  \RequirePackage{luatexbase-loader}
\fi
%    \end{macrocode}
%
%    \subsubsection{Main code}
%
%    The \texttt{\string\luatexModuleError} macro is called by the lua function
%    \texttt{luatexbase.module\_error}. It is necessary because we can't call
%    directly \texttt{\string\errmessage} in lua.
%
%    \begin{macrocode}

\def\luatexModuleError#1#2{%
  \errorcontextlines=0\relax
  \immediate\write16{}%
  \errmessage{Module #1 error: #2^^J^^J%
See the module #1 documentation for explanation.^^J ...^^J}%
}

%    \end{macrocode}
%
%    Then we define
%    \texttt{\string\luatexUseModule} that simply calls
%    \texttt{luatexbase.use\_module}.
%    If the package is loaded with Plain, we define
%    \texttt{\string\luaRequireModule} with two mandatory arguments.
%
%    \begin{macrocode}
\def\luatexUseModule#1{\directlua{luatexbase.use_module([[#1]])}}

\expandafter\ifx\csname ProvidesPackage\endcsname\relax
  \def\luatexRequireModule#1#2{\directlua{%
      luatexbase.require_module([[#1]], [[#2]])}}
\else
%    \end{macrocode}
%
%    If the package is loaded with \LaTeX , we define 
%    \texttt{\string\luaRequireModule} with one mandatory
%    argument (the name of the package) and one optional (the version or the
%    date).
%
%    \begin{macrocode}
  \newcommand\luatexRequireModule[2][0]{\directlua{luatexbase.require_module([[#2]], [[#1]])}}
\fi
%    \end{macrocode}
%
%    \begin{macrocode}
\directlua{dofile(assert(kpse.find_file('modutils.lua', 'tex')))}
%    \end{macrocode}
%
%    \begin{macrocode}
\lltxb@modutils@AtEnd
%</texpackage>
%    \end{macrocode}
%
%    \subsection{Lua module}
%
%    \begin{macrocode}
%<*luamodule>
%    \end{macrocode}
%
%    Initialisation borrowed from luatextra
%
%    \begin{macrocode}
module("luatexbase", package.seeall)

local format = string.format

luatexbase.modules = luatexbase.modules or {}
%    \end{macrocode}
%
%    A small patch, for the \texttt{module} function to work in this file. I
%    can't understand why it doens't otherwise.
%
%    \begin{macrocode}

luatexbase.module = module

%    \end{macrocode}
%
%    Here we define the warning and error functions specific to
%    \texttt{luatexbase}.
%
%    \begin{macrocode}

internal_warning_spaces = "                   "

function internal_warning(msg)
    if not msg then return end
    texio.write_nl(format("\nLuaTeXtra Warning: %s\n\n", msg))
end

internal_error_spaces = "                 "

function internal_error(msg)
    if not msg then return end
    tex.sprint(format("\\immediate\\write16{}\\errmessage{LuaTeXtra error: %s^^J^^J}", msg))
end

%    \end{macrocode}
%
%    \subsubsection{Error, warning and info function for modules}
%
%    Some module printing functions are provided, they have the same
%    philosophy as the \LaTeX 's \texttt{\string\PackageError} and
%    \texttt{\string\PackageWarning} macros: their first argument is the name
%    of the module, and the second is the message. These functions are meant
%    to be used by lua module writers.
%
%    \begin{macrocode}

function module_error(package, msg, helpmsg)
    if not package or not msg then
        return
    end
    if helpmsg then
        tex.sprint(format("\\errhelp{%s}", helpmsg))
    end
    tex.sprint(format("\\luatexModuleError{%s}{%s}", package, msg))
end

function module_warning(modulename, msg)
    if not modulename or not msg then
        return
    end
    texio.write_nl(format("\nModule %s Warning: %s\n\n", modulename, msg))
end

function module_log(modulename, msg)
    if not modulename or not msg then
        return
    end
    texio.write_nl('log', format("%s: %s", modulename, msg))
end

function module_term(modulename, msg)
    if not modulename or not msg then
        return
    end
    texio.write_nl('term', format("%s: %s", modulename, msg))
end

function module_info(modulename, msg)
    if not modulename or not msg then
        return
    end
    texio.write_nl(format("%s: %s\n", modulename, msg))
end

%    \end{macrocode}
%
%    \subsubsection{module loading and providing functions}
%
%    \begin{macro}{use module}
%
%    This macro is the one used to simply load a lua module file. It does not
%    reload it if it's already loaded, and prints the filename in the terminal
%    and the log. A lua module must call the macro
%    \texttt{provides\_module}.
%
%    \begin{macrocode}
function use_module(name)
    if not name or modules[name] then
        return
    end
    require(name)
    if not modules[name] then
        internal_warning(format("You have requested module `%s',\n%s but the file %s does not provide it.", name, internal_warning_spaces, filename))
    end
    if not package.loaded[name] then
        module(name, package.seeall)
    end
end
%    \end{macrocode}
%
%    \end{macro}
%
%    Some internal functions to convert a date into a number, and to determine
%    if a string is a date. It is useful for
%    \texttt{require\_package} to understand if a user asks a
%    version with a date or a version number.
%
%    \begin{macrocode}

function datetonumber(date)
    numbers = string.gsub(date, "(%d+)/(%d+)/(%d+)", "%1%2%3")
    return tonumber(numbers)
end

function isdate(date)
    for _, _ in string.gmatch(date, "%d+/%d+/%d+") do
        return true
    end
    return false
end

local date, number = 1, 2

function versiontonumber(version)
    if isdate(version) then
        return {type = date, version = datetonumber(version), orig = version}
    else
        return {type = number, version = tonumber(version), orig = version}
    end
end

requiredversions = {}

%    \end{macrocode}
%
%    \begin{macro}{require module}
%
%    This function is like the \texttt{use\_module} function, but
%    can accept a second argument that checks for the version of the module.
%    The version can be a number or a date (format yyyy/mm/dd).
%
%    \begin{macrocode}

function require_module(name, version)
    if not name then
        return
    end
    if not version then
        return use_module(name)
    end
    luaversion = versiontonumber(version)
    if modules[name] then
        if luaversion.type == date then
            if datetonumber(modules[name].date) < luaversion.version then
                internal_error(format("found module `%s' loaded in version %s, but version %s was required", name, modules[name].date, version))
            end
        else
            if modules[name].version < luaversion.version then
                internal_error(format("found module `%s' loaded in version %.02f, but version %s was required", name, modules[name].version, version))
            end
        end
    else
        requiredversions[name] = luaversion
        use_module(name)
    end
end

%    \end{macrocode}
%
%    \end{macro}
%
%    \begin{macro}{provides module}
%
%    This macro is the one that must be called in the module files. It takes a
%    table as argument. You can put any information you want in this table,
%    but the mandatory ones are \texttt{name} (a string), \texttt{version} (a
%    number), \texttt{date} (a string) and \texttt{description} (a string).
%    Other fields are usually \texttt{copyright}, \texttt{author} and
%    \texttt{license}.
%
%    This function logs informations about the module the same way \LaTeX\
%    does for informations about packages.
%
%    \begin{macrocode}

function provides_module(mod)
    if not mod then
        internal_error('cannot provide nil module')
        return
    end
    if not mod.version or not mod.name or not mod.date or not mod.description then
        internal_error('invalid module registered, fields name, version, date and description are mandatory')
        return
    end
    requiredversion = requiredversions[mod.name]
    if requiredversion then
        if requiredversion.type == date and requiredversion.version > datetonumber(mod.date) then
            internal_error(format("loading module %s in version %s, but version %s was required", mod.name, mod.date, requiredversion.orig))
        elseif requiredversion.type == number and requiredversion.version > mod.version then
            internal_error(format("loading module %s in version %.02f, but version %s was required", mod.name, mod.version, requiredversion.orig))
        end
    end
    modules[mod.name] = module
    texio.write_nl('log', format("Lua module: %s %s v%.02f %s\n", mod.name, mod.date, mod.version, mod.description))
end

%    \end{macrocode}
%
%    \end{macro}
%
%    \begin{macrocode}
%</luamodule>
%    \end{macrocode}
%
%    \section{Test files}
%
%    A dummy lua file for tests.
%
%    \begin{macrocode}
%<*testdummy>
luatexbase.provides_module {
  name        = 'test-modutils',
  date        = '2000/01/01',
  version     = 1,
  description = 'dummy test package',
}
%</testdummy>
%    \end{macrocode}
%
%    We just check that the package loads properly, under both LaTeX and Plain
%    TeX. Anyway, the test files of other modules using this one already are a
%    test\dots
%
%    \begin{macrocode}
%<testplain>\input luatexbase-modutils.sty
%<testlatex>\RequirePackage{luatexbase-modutils}
%<*testplain,testlatex>
\luatexRequireModule
%<testlatex>[1970/01/01]
{test-modutils}
%<testplain>{1970/01/01}
%</testplain,testlatex>
%<testplain>\bye
%<testlatex>\stop
%    \end{macrocode}
%
% \Finale
\endinput