% \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.2 2010-05-12}
% \author{%
%  Manuel P\'egouri\'e-Gonnard \\ \email{mpg@elzevir.fr} \and
%  \'Elie Roux \\ \email{elie.roux@telecom-bretagne.eu}}
%
% \maketitle
%
% \begin{abstract}
% This package provides functions similar to \latex's |\usepackage| and
% |\ProvidesPackage| macros,\footnote{and their variants or synonyms such as
% \cs{documentclass} and \cs{RequirePackage} or \cs{ProvidesClass} and
% \cs{ProvidesFiles}} or more precisely the part of these macros that deals
% with identification and version checking (no attempt is done at implementing
% an option mechanism). It also provides functions for reporting errors and
% warnings in a standardised format.
% \end{abstract}
%
% \tableofcontents
%
% \section{Documentation}
%
% \subsection{Scope of this package}
%
% Lua's standard function |require()| is similar to \tex's |\input| primitive
% but is somehow more evolved in that it makes a few checks to avoid loading
% the same module twice. In the \tex world, this needs to be taken care of by
% macro packages; in the \latex world this is done by |\usepackage|.
%
% But |\usepackage| also takes care of many other things. Most notably, it
% implements a complex option system, and does some identification and version
% checking. The present package doesn't try to provide anything for options,
% but implements a system for identification and version checking similar to
% \latex's system.
%
% \medskip
%
% It is important to notice that Lua's standard function |module()| is
% completely orthogonal with the present package. It has nothing to do with
% identification and deals only with namespaces: more precisely, it
% modifies the current environment. So, you should continue to
% use it normally regardless of whether you chose to use this package's
% features for identification.
%
% It is recommended to always use |module()| or any other method that ensure
% the global name space remains clean. For example, you may heavily use the
% |local| keyword and explicitly qualify the name of every non-local symbol.
% Chapter 15 of \href{http://www.lua.org/pil/}{Programming in Lua, 1st ed.}
% discusses various methods for managing packages.
%
% \subsection{\tex macros}
%
% \begin{qcode}
% \cs{RequireLuaModule}\marg{name}\oarg{date}
% \end{qcode}
%
% The macro |\RequireLuaModule| is an interface to the Lua function
% |require_module|; it take the same arguments with the same meaning. The
% second argument is optional.
%
% \subsection{Lua functions}
%
% \begin{qcode}
% luatexbase.require_module({\meta{name}} [, \meta{required date}])
% \end{qcode}
%
% The function |luatexbase.require_module()| may be used as a replacement to
% |require()|. If only one argument is given, the only difference with
% |require()| is it checks that the module properly identifies itself (as
% explained below) with the same name.
%
% The second argument is optional; if used, it must be a
% string\footnote{Previous versions of the package supported floating-point
% version numbers as well, but it caused confusion with authors trying to use
% version strings such as |0.3a| and probably isn't worth the trouble.}
% containing a date in |YYYY//MM/DD| format which specifies the minimum
% version of the module required.
%
% \begin{qcode}
% luatexbase.provides_module(\meta{info})
% \end{qcode}
% This function is used by modules to identify themselves; the argument is a
% table containing information about the module. The required field |name|
% must contain the name of the module. It is recommended to provide a field
% |date| with the same format as above. Optional fields |version| (number or
% string) and |description| may be used if present. Other fields are ignored.
%
% If a date was required and a date is declared, then a warning is issued if
% the required date is strictly newer than the declared date. A list of loaded
% modules and their associated information is kept, and used to check the date
% without reloading the module (since |require()| won't relaod it anyway).
%
% \begin{qcode}
% luatexbase.module_error(\meta{name}, \meta{message}, ...)
% luatexbase.module_warning(\meta{name}, \meta{message}, ...)
% luatexbase.module_info(\meta{name}, \meta{message}, ...)
% \end{qcode}
% These functions are similar to \latex's |\PackageError|,
% |\PackageWarningNoLine| and |\PackageInfo| in the way they format the
% output. The first argument is the name of the current module; the remaining
% arguments are passed to |string.format()|. No automatic line breaking is
% done, you may still use |\n| as usual for that, and the name of the package
% will be prepended to each output line.
%
% \begin{qcode}
% local err, warn, info = luatexbase.errwarinf(\meta{name})
% local err, warn, info = luatexbase.provides_module(\meta{name})
% \end{qcode}
% Customised versions of the above commands maybe obtained by invoking
% |errwarinf()| and are also returned by |provides_module()|. They don't take
% the name of the module as their first argument any more, so that you don't
% need to repeat it all over the place. (Notice that |error| is the name of a
% standard Lua function, so you may want to avoid overwriting it, hence the
% use of |err| in the above example.)
%
% \subsection{Templates}
%
% Let me emphasize again that, while |luatexbase.require_module()| is meant to
% be used as a replacement for |require()|, the function
% |luatexbase.provides_module()| \emph{is not} a replacement for |module()|:
% they just don't do the same thing (declaring information vs changing the
% current name space).
%
% Now, here is how you module may begin:
% \begin{verbatim}
% local err, warn, info = luatexbase.provides_module({
%     -- required
%     -- recommended
%     name = 'mymodule',
%     date = '1970/01/01',
%     version = 0.0, -- or version = '0.0a',
%     description = 'a Lua module template',
%     author = 'A. U. Thor',
%     licence = 'LPPL v1.3+',
% })
%
% module('mynamespace', package.seeall)
% -- or any other method (see chapter 15 of PIL for examples)
% \end{verbatim}
%
% Alternatively, if you don't want to assume \pk{luatexbase-modutils} is
% loaded, you may load your module with:
% \begin{verbatim}
% (luatexbase.require_module or require)('mymodule')
% \end{verbatim}
% and begin your module's code with:
% \begin{verbatim}
% if luatexbase._provides_module then
%     luatexbase.provides_module({
%         -- required
%         -- recommended
%         name = 'mymodule',
%         date = '1970/01/01',
%         version = 0.0, -- or version = '0.0a',
%         description = 'a Lua module template',
%         author = 'A. U. Thor',
%         licence = 'LPPL v1.3+',
%     })
% end
%
% module('mynamespace', package.seeall)
% -- or any other method (see chapter 15 of PIL for examples)
%
% local function err(msg)
%    -- etc.
% \end{verbatim}
%
%    \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/05/27 v0.3-pre Module utilities for LuaTeX]
%    \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 PackageError\endcsname\relax
      \def\x#1#2#3{\begingroup \newlinechar10
        \errhelp{#3}\errmessage{Package #1 error: #2}\endgroup}
    \else
      \let\x\PackageError
    \fi
  \expandafter\endgroup
  \x{luatexbase-attr}{LuaTeX is required for this package. Aborting.}{%
    This package can only be used with the LuaTeX engine^^J%
    (command `lualatex' or `luatex').^^J%
    Package loading has been stopped to prevent additional errors.}
  \lltxb@modutils@AtEnd
  \expandafter\endinput
\fi
%    \end{macrocode}
%
%    Load \pk{luatexbase-loader} (hence \pk{luatexbase-compat}), require the
%    supporting Lua module and make sure |luaescapestring| is available.
%
%    \begin{macrocode}
\ifdefined\RequirePackage
  \RequirePackage{luatexbase-loader}
\else
  \input luatexbase-loader.sty
\fi
\luatexbase@directlua{require('luatexbase.modutils')}
\luatexbase@ensure@primitive{luaescapestring}
%    \end{macrocode}
%
%    \subsection{Auxiliary definitions}
%
%    We need a version of |\@ifnextchar|. The definitions for the not-\latex
%    case are stolen from \pk{ltxcmds} verbatim, only the prefix is changed.
%
%    \begin{macrocode}
\ifdefined\kernel@ifnextchar
  \let\lltxb@ifnextchar\kernel@ifnextchar
\else
  \chardef\lltxb@zero0
  \chardef\lltxb@two2
  \long\def\lltxb@ifnextchar#1#2#3{%
    \begingroup
    \let\lltxb@CharToken= #1\relax
    \toks\lltxb@zero{#2}%
    \toks\lltxb@two{#3}%
    \futurelet\lltxb@LetToken\lltxb@ifnextchar@
  }
  \def\lltxb@ifnextchar@{%
    \ifx\lltxb@LetToken\lltxb@CharToken
      \expandafter\endgroup\the\toks\expandafter\lltxb@zero
    \else
      \ifx\lltxb@LetToken\lltxb@SpaceToken
         \expandafter\expandafter\expandafter\lltxb@@ifnextchar
      \else
         \expandafter\endgroup\the\toks
         \expandafter\expandafter\expandafter\lltxb@two
      \fi
    \fi
  }
  \begingroup
    \def\x#1{\endgroup
      \def\lltxb@@ifnextchar#1{%
         \futurelet\lltxb@LetToken\lltxb@ifnextchar@
      }%
    }%
  \x{ }
  \begingroup
    \def\x#1{\endgroup
      \let\lltxb@SpaceToken= #1%
    }%
  \x{ }
\fi
%    \end{macrocode}
%
%    \subsubsection{User macro}
%
%    Interface to the Lua function for module loading. Avoid passing a second
%    argument to the function if empty (most probably not specified).
%
%    \begin{macrocode}
\def\RequireLuaModule#1{%
  \lltxb@ifnextchar[{\lltxb@requirelua{#1}}{\lltxb@requirelua{#1}[]}}
\def\lltxb@requirelua#1[#2]{%
  \luatexbase@directlua{luatexbase.require_module(
    "\luatexluaescapestring{#1}"
    \expandafter\ifx\expandafter\/\detokenize{#2}\/\else
      , "\luatexluaescapestring{#2}"
    \fi)}}
%    \end{macrocode}
%
%    \begin{macrocode}
\lltxb@modutils@AtEnd
%</texpackage>
%    \end{macrocode}
%
%    \subsection{Lua module}
%
%    \begin{macrocode}
%<*luamodule>
module("luatexbase", package.seeall)
%    \end{macrocode}
%
%    \subsection{Internal functions and data}
%
%    Tables holding informations about the modules loaded and the versions
%    required.
%
%    \begin{macrocode}
local modules = modules or {}
local requiredversions = {}
%    \end{macrocode}
%
%    Convert a date in YYYY/MM/DD format into a number
%
%    \begin{macrocode}
local function datetonumber(date)
    numbers = string.gsub(date, "(%d+)/(%d+)/(%d+)", "%1%2%3")
    return tonumber(numbers)
end
%    \end{macrocode}
%
%    Say if a string is a date in YYYY//MM/DD format.
%
%    \begin{macrocode}
local function isdate(date)
    for _, _ in string.gmatch(date, "%d+/%d+/%d+") do
        return true
    end
    return false
end
%    \end{macrocode}
%
%    Parse a version into a table indicating a type (date or number), a
%    numeric version and the original version string.
%
%    \begin{macrocode}
local date, number = 1, 2
local function parse_version(version)
    if isdate(version) then
        return {type = date, version = datetonumber(version), orig = version}
    else
        return {type = number, version = tonumber(version), orig = version}
    end
end
%    \end{macrocode}
%
%    \subsubsection{Error, warning and info function for modules}
%
%    Here are the reporting functions for the modules. For errors, Lua's
%    |error()| is used. For now, the error reports look less good than with
%    \tex's |\errmessage|, but hopefully it will be improved in future
%    versions of \luatex.\footnote{Actually, \luatex 0.61 and higher provides
%    |tex.error| but it's less usefull, since only the current line in the
%    TeX file is reported.} We could invoke |\errmessage| using |tex.sprint()|,
%    but it may cause problems on the \tex end,\footnote{Eg, it will not work
%    inside an \cs{edef}.} and moreover |error()| will still be used by Lua
%    for other errors, so it makes messages more consistent.
%
%    An internal function is used for error messages, so that the calling
%    level (last argument of |error()| remains constant using either
%    |module_error()| or a custom version as return by |errwarinf()|.
%
%    \begin{macrocode}
local function msg_format(msg_type, mod_name, ...)
  local cont = '('..mod_name..')' .. ('Module: '..msg_type):gsub('.', ' ')
  return 'Module '..mod_name..' '..msg_type..': '
    .. string.format(...):gsub('\n', '\n'..cont) .. '\n'
end
local function module_error_int(mod, ...)
  error(msg_format('error', mod, ...), 3)
end
function module_error(mod, ...)
  module_error_int(mod, ...)
end
%    \end{macrocode}
%
%    Split the lines explicitely in order not to depend on the value of
%    |\newlinechar|.
%
%    \begin{macrocode}
function module_warning(mod, ...)
  for _, line in ipairs(msg_format('warning', mod, ...):explode('\n')) do
    texio.write_nl(line)
  end
end
function module_info(mod, ...)
  for _, line in ipairs(msg_format('info', mod, ...):explode('\n')) do
    texio.write_nl(line)
  end
end
%    \end{macrocode}
%
%    Produce custom versions of the reporting functions.
%
%    \begin{macrocode}
function errwarinf(name)
  return function(...) module_error_int(name, ...) end,
    function(...) module_warning(name, ...) end,
    function(...) module_info(name, ...) end
end
%    \end{macrocode}
%
%    For our own convenience, local functions for warning and errors in the
%    present module.
%
%    \begin{macrocode}
local err, warn = errwarinf('luatexbase.modutils')
%    \end{macrocode}
%
%    \subsubsection{module loading and providing functions}
%
%    Load a module without version checking.
%
%    \begin{macrocode}
local function use_module(name)
    require(name)
    if not modules[name] then
        warn("Module didn't properly identified itself: %s", name)
    end
end
%    \end{macrocode}
%
%    Load a module with optional version checking.
%
%    \begin{macrocode}
function require_module(name, version)
    if not version then
        use_module(name)
        return
    end
    luaversion = parse_version(version)
    if modules[name] then
        if luaversion.type == date then
            if datetonumber(modules[name].date) < luaversion.version then
                err("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
                err("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}
%
%    Provide identification information for a module. As a bonus, custom
%    reporting functions are returned.
%
%    \begin{macrocode}
function provides_module(mod)
    if not mod then
        err('cannot provide nil module')
        return
    end
    if not mod.version or not mod.name or not mod.date
    or not mod.description then
        err("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
            err("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
            err("loading module %s in version %.02f, "
            .."but version %s was required",
            mod.name, mod.version, requiredversion.orig)
        end
    end
    modules[mod.name] = mod
    texio.write_nl('log', string.format("Lua module: %s %s v%.02f %s\n",
    mod.name, mod.date, mod.version, mod.description))
    return errwarinf(mod.name)
end
%    \end{macrocode}
%
%    \begin{macrocode}
%</luamodule>
%    \end{macrocode}
%
%    \section{Test files}
%
%    A dummy lua file for tests.
%
%    \begin{macrocode}
%<*testdummy>
local err, warn, info = luatexbase.provides_module {
  name        = 'test-modutils',
  date        = '2000/01/01',
  version     = 1,
  description = 'dummy test package',
}
info('It works!\nOh, rly?\nYeah rly!')
%</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>
\RequireLuaModule{test-modutils}
\RequireLuaModule{test-modutils}[1970/01/01]
%</testplain,testlatex>
%<testplain>\bye
%<testlatex>\stop
%    \end{macrocode}
%
% \Finale
\endinput
