% \iffalse meta-comment % % Copyright 2009-2013 by Élie Roux % Copyright 2010, 2011 by Manuel Pégourié-Gonnard % % 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 % %<*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 % %<*ignore> \fi % %<*driver> \documentclass{ltxdoc} \input{lltxb-dtxstyle} \begin{document} \DocInput{luatexbase-modutils.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 \~} % % \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% % % \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} % % \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')) % % \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} %\input luatexbase-modutils.sty %\RequirePackage{luatexbase-modutils} %<*testplain,testlatex> \RequireLuaModule{test-modutils} \RequireLuaModule{test-modutils}[1970/01/01] % %\bye %\stop % \end{macrocode} % % \Finale \endinput