% \iffalse meta-comment
%
% Copyright 2009-2013 by Élie Roux <elie.roux@telecom-bretagne.eu>
% Copyright 2010, 2011 by Manuel Pégourié-Gonnard <mpg@elzevir.fr>
%
% 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

See the aforementioned source file(s) for copyright and licensing information.

\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         \~}
%
% \pkdate{luatexbase-modutils}{v0.6 2013-05-11}
%
% \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 note that Lua's unction |module()| is deprecated in
% Lua 5.2 and should be avoided. For examples of good practices for creating 
% modules, see section~\ref{template}. Chapter 15 
% of \href{http://www.lua.org/pil/15.html}{Programming in Lua, 3rd 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, then a warning is issued if the required date is
% strictly newer than the declared date (or if no date was declared). 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 reload
% it anyway) if a module is required several times.
%
% \begin{qcode}
% luatexbase.module_error(\meta{name}, \meta{message}, ...)
% luatexbase.module_warning(\meta{name}, \meta{message}, ...)
% luatexbase.module_info(\meta{name}, \meta{message}, ...)
% luatexbase.module_log(\meta{name}, \meta{message}, ...)
% \end{qcode}
% These functions are similar to \latex's |\PackageError|, |\PackageWarning|
% and |\PackageInfo| in the way they format the output.  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 (except for |log| which is
% intended for short messages in a non-verbose format).  The first argument
% is the name of the current module; the remaining arguments are passed to
% |string.format()|.
%
% Note that |module_error| raises an actual Lua error with |error()|, which
% currently means a call stack will be dumped. While this may not look pretty,
% at least it provides useful information for tracking the error down.
%
% \begin{qcode}
% local err, warn, info, log = luatexbase.errwarinf(\meta{name})
% local err, warn, info, log = 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.)
%
% \begin{qcode}
% local module_info = luatexbase.get_module_info(\meta{name})
% local version     = luatexbase.get_module_version(\meta{name})
% local date        = luatexbase.get_module_date(\meta{name})
% local date_int    = luatexbase.get_module_date_int(\meta{name})
% local is_loaded   = luatexbase.is_module_loaded(\meta{name})
% \end{qcode}
% These functions check for the availability or version of a module, and can
% even return a copy of the |module_info| table.
%
% \subsection{Templates}\label{template}
%
% Now, here is a module header template showing all the recommended elements:
% \begin{verbatim}
% local err, warn, info, log = luatexbase.provides_module({
%     -- required
%     name          = 'mymodule',
%     -- recommended
%     date          = '1970/01/01',
%     version       = 0.0,            -- or version = '0.0a',
%     description   = 'a Lua module template',
%     -- optional and ignored
%     author        = 'A. U. Thor',
%     licence       = 'LPPL v1.3+',
% })
%
% mynamespace           = mynamespace or { }
% local mynamespace     = mynamespace
%
% \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
%         name        = 'mymodule',
%         -- recommended
%         date        = '1970/01/01',
%         version     = 0.0,            -- or version = '0.0a',
%         description = 'a Lua module template',
%         -- optional and ignored
%         author      = 'A. U. Thor',
%         licence     = 'LPPL v1.3+',
%     })
% end
%
% mynamespace           = mynamespace or { }
% local mynamespace     = mynamespace
%
% local function err(msg)
%    -- etc.
% \end{verbatim}
%
%    \section{Implementation}
%
%    \subsection{\tex package}
%
%    \begin{macrocode}
%<*texpackage>
%    \end{macrocode}
%
%    \subsubsection{Preliminaries}
%
%    Catcode defenses and reload protection.
%
%    \begin{macrocode}
\begingroup\catcode61\catcode48\catcode32=10\relax% = and space
  \catcode123 1 % {
  \catcode125 2 % }
  \catcode 35 6 % #
  \toks0\expandafter{\expandafter\endlinechar\the\endlinechar}%
  \edef\x{\endlinechar13}%
  \def\y#1 #2 {%
    \toks0\expandafter{\the\toks0 \catcode#1 \the\catcode#1}%
    \edef\x{\x \catcode#1 #2}}%
  \y  13  5 % carriage return
  \y  61 12 % =
  \y  32 10 % space
  \y 123  1 % {
  \y 125  2 % }
  \y  35  6 % #
  \y  64 11 % @ (letter)
  \y  10 12 % new line ^^J
  \y  34 12 % "
  \y  39 12 % '
  \y  40 12 % (
  \y  41 12 % )
  \y  44 12 % ,
  \y  45 12 % -
  \y  46 12 % .
  \y  47 12 % /
  \y  58 12 % :
  \y  91 12 % [
  \y  93 12 % ]
  \y  94  7 % ^
  \y  95  8 % _
  \y  96 12 % `
  \toks0\expandafter{\the\toks0 \relax\noexpand\endinput}%
  \edef\y#1{\noexpand\expandafter\endgroup%
    \noexpand\ifx#1\relax \edef#1{\the\toks0}\x\relax%
    \noexpand\else \noexpand\expandafter\noexpand\endinput%
    \noexpand\fi}%
\expandafter\y\csname luatexbase@modutils@sty@endinput\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}[2013/05/11 v0.6 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-modutils}{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.}
  \expandafter\luatexbase@modutils@sty@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}
\expandafter\ifx\csname RequirePackage\endcsname\relax
  \input luatexbase-loader.sty
\else
  \RequirePackage{luatexbase-loader}
\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}
\luatexbase@modutils@sty@endinput%
%</texpackage>
%    \end{macrocode}
%
%    \subsection{Lua module}
%
%    \begin{macrocode}
%<*luamodule>
luatexbase          = luatexbase or { }
local luatexbase    = luatexbase
local string_gsub   = string.gsub
%    \end{macrocode}
%
%    \subsection{Internal functions and data}
%
%    Tables holding informations about the modules loaded and the versions
%    required. Keys are module names and values are the info tables as passed
%    to |provides_module()|.
%
%    \begin{macrocode}
local modules = modules or {}
%    \end{macrocode}
%
%    Convert a date in YYYY/MM/DD format into a number.
%
%    \begin{macrocode}
local function date_to_int(date)
    if date == '' then return -1 end
    local numbers = string_gsub(date, "(%d+)/(%d+)/(%d+)", "%1%2%3")
    return tonumber(numbers)
end
%    \end{macrocode}
%
%    \subsubsection{Error, warning and info function for modules}
%
%    Here are the reporting functions for the modules. 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 returned 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
local function module_error(mod, ...)
  module_error_int(mod, ...)
end
luatexbase.module_error = module_error
%    \end{macrocode}
%
%    Split the lines explicitly in order not to depend on the value of
%    |\newlinechar|.
%
%    \begin{macrocode}
local function module_warning(mod, ...)
  for _, line in ipairs(msg_format('warning', mod, ...):explode('\n')) do
    texio.write_nl(line)
  end
end
luatexbase.module_warning = module_warning
local function module_info(mod, ...)
  for _, line in ipairs(msg_format('info', mod, ...):explode('\n')) do
    texio.write_nl(line)
  end
end
luatexbase.module_info = module_info
%    \end{macrocode}
%
%    No line splitting or advanced formating here.
%
%    \begin{macrocode}
local function module_log(mod, msg, ...)
  texio.write_nl('log', mod..': '..msg:format(...))
end
luatexbase.module_log = module_log
%    \end{macrocode}
%
%    Produce custom versions of the reporting functions.
%
%    \begin{macrocode}
local function errwarinf(name)
  return function(...) module_error_int(name, ...) end,
    function(...) module_warning(name, ...) end,
    function(...) module_info(name, ...) end,
    function(...) module_log(name, ...) end
end
luatexbase.errwarinf = errwarinf
%    \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 with mandatory name checking and optional version checking.
%
%    \begin{macrocode}
local function require_module(name, req_date)
    require(name)
    local info = modules[name]
    if not info then
        warn("module '%s' was not properly identified", name)
    elseif req_date and info.date then
        if date_to_int(info.date) < date_to_int(req_date) then
            warn("module '%s' required in version '%s'\n"
            .. "but found in version '%s'", name, req_date, info.date)
        end
    end
end
luatexbase.require_module = require_module
%    \end{macrocode}
%
%    Provide identification information for a module. As a bonus, custom
%    reporting functions are returned. No need to do any check here,
%    everything done in |require_module()|.
%
%    \begin{macrocode}
local function provides_module(info)
    if not (info and info.name) then
        err('provides_module: missing information')
    end
    texio.write_nl('log', string.format("Lua module: %s %s %s %s\n",
    info.name, info.date or '', info.version or '', info.description or ''))
    modules[info.name] = info
    return errwarinf(info.name)
end
luatexbase.provides_module = provides_module
%    \end{macrocode}
%
%    \subsubsection{module availability and version checking}
%
%    A simple table copy function.
%
%    \begin{macrocode}
local fastcopy
fastcopy = table.fastcopy or function(old)
    if old then
        local new = { }
        for k,v in next, old do
            if type(v) == "table" then
                new[k] = fastcopy(v)
            else
                new[k] = v
            end
        end
        local mt = getmetatable(old)
        if mt then
            setmetatable(new,mt)
        end
        return new
    else
        return { }
    end
end
%    \end{macrocode}
%
%    Gives the table of the infos on a module, as given in |provides_module|.
%
%    \begin{macrocode}
local function get_module_info(name)
    local module_table = modules[name]
    if not module_table then 
      return nil 
    else
      return fastcopy(module_table)
    end
end
luatexbase.get_module_info = get_module_info
%    \end{macrocode}
%
%    Gives the version of a module, nil if the module is not loaded and empty
%    string if the module did not set its date.
%
%    \begin{macrocode}
function get_module_version(name)
    local module_table = modules[name]
    if not module_table then 
      return nil 
    else
      return module_table.version
    end
end
luatexbase.get_module_version = get_module_version
%    \end{macrocode}
%
%    Gives the date string of a module, nil if the module is not loaded and 
%    empty string of the modules did not set its date.
%
%    \begin{macrocode}
function get_module_date(name)
    local module_table = modules[name]
    if not module_table then 
      return nil 
    else
      return module_table.date
    end
end
luatexbase.get_module_date = get_module_date
%    \end{macrocode}
%
%    Gives the date number of a module, for date comparison, nil if the 
%    module is not loaded and -1 if the module did not set its date. The number
%    is formated as |yyyymmdd|.
%
%    \begin{macrocode}
function get_module_date_int(name)
    local module_table = modules[name]
    if not module_table then 
      return nil 
    else
      return module_table.date and date_to_int(module_table.date)
    end
end
luatexbase.get_module_date_int = get_module_date_int
%    \end{macrocode}
%
%    Returns true if the module is loaded, false otherwise.
%
%    \begin{macrocode}
function is_module_loaded(name)
    if modules[name] then
        return true
    else
        return false
    end
end
luatexbase.is_module_loaded = is_module_loaded
%    \end{macrocode}
%
%    We provide the module, for version checking.
%
%    \begin{macrocode}
provides_module({
  name        = 'luatexbase-modutils',
  date        = '2013/05/11',
  version     = 0.6,
  description = 'Module utilities for LuaTeX',
})
%    \end{macrocode}
%
%    \begin{macrocode}
%</luamodule>
%    \end{macrocode}
%
%    \section{Test files}
%
%    A dummy lua file for tests.
%
%    \begin{macrocode}
%<*testdummy>
local err, warn, info, log = luatexbase.provides_module {
  name        = 'test-modutils',
  date        = '2000/01/01',
  version     = 1,
  description = 'dummy test package',
}
luatexbase.provides_module {
  name        = 'test-modutils2',
  date        = '',
  version     = 1,
  description = 'dummy test package',
}
info('It works!\nOh, rly?\nYeah rly!')
log("I'm a one-line info.")
info("1 = "..luatexbase.get_module_version('test-modutils'))
if is_module_loaded('test-modutils') then
  info("ok!")
else
  err("problem!")
end
info("2000/01/01 = "..luatexbase.get_module_info('test-modutils').date)
info("20000101 = "..luatexbase.get_module_date_int('test-modutils'))
info("-1 = "..luatexbase.get_module_date_int('test-modutils2'))
%</testdummy>
%    \end{macrocode}
%
%    We just check that the package loads properly, under both LaTeX and Plain
%    TeX, is able to load and identify the above dummy module.
%
%    \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
