3

I'm writing a package mostly in Lua files. So far I haven't found a way to test in the Lua code whether a given package is loaded. What I'm doing is using \@ifpackageloaded and call into a Lua function:

% Configure cref names if cleveref is loaded
\makeatletter
\@ifpackageloaded{cleveref}%
{\directlua{lyluatexmp.set_cref_names()}}%
\makeatother

But I think the information should be available for Lua as well, making it significantly easier to deal with this issue.

[EDIT:]
While the solution in this answer does work (on TeX Live 2019/2017) it doesn't work when the function is moved into an external Lua file (which is where I'd want it to be):

local function loaded(ext)
  local fmt = "ver@\\csstring\\%s." .. ext
  return function(name)
    return not not token.get_macro(fmt:format(name))
  end
end
return {
    package = loaded"sty",
    class = loaded"cls"
}

in a test-two.lua file (note the double backslashes that are necessary here) and

\directlua{
local mod = require('test-two.lua')
classloaded = mod.class
packageloaded = mod.package
}
\directlua{
if classloaded"article" then
  texio.write_nl"`article` loaded"
else
  texio.write_nl"`article` not loaded"
end
}
\documentclass{article}
\directlua{
if classloaded"article" then
  texio.write_nl"`article` loaded"
else
  texio.write_nl"`article` not loaded"
end
if packageloaded"cleveref" then
  texio.write_nl"`cleveref` loaded"
else
  texio.write_nl"`cleveref` not loaded"
end
}
\usepackage{cleveref}
\directlua{
  if packageloaded"cleveref" then
    texio.write_nl"`cleveref` loaded"
  else
    texio.write_nl"`cleveref` not loaded"
  end
}
\stop

always return false. It turns out that the result of get_macro is always nil in the second implementation (both TeX Live 2017 and 2019) while token.create(fmt:format(name)).command always returns the value for undefined_cs in the first implementation.

Is there anything that can be done about that?

2 Answers 2

8

You can implement \@ifpackageloaded (and \@ifclassloaded) in Lua by basically translating the original TeX-level implementation into Lua:

\directlua{
  local not_loaded_cmds = {
    [token.command_id'undefined_cs'] = true,
    [token.command_id'relax'] = true,
  }
  local function loaded(ext)
    local fmt = "ver@\csstring\%s." .. ext
    return function(name)
      return not not_loaded_cmds[token.create(fmt:format(name)).command]
    end
  end
  packageloaded = loaded"sty"
  classloaded = loaded"cls"
}
\directlua{
if classloaded"article" then
  texio.write_nl"`article` loaded"
else
  texio.write_nl"`article` not loaded"
end
}
\documentclass{article}
\directlua{
if classloaded"article" then
  texio.write_nl"`article` loaded"
else
  texio.write_nl"`article` not loaded"
end
if packageloaded"cleveref" then
  texio.write_nl"`cleveref` loaded"
else
  texio.write_nl"`cleveref` not loaded"
end
}
\usepackage{cleveref}
\directlua{
  if packageloaded"cleveref" then
    texio.write_nl"`cleveref` loaded"
  else
    texio.write_nl"`cleveref` not loaded"
  end
}
\stop

This writes to standard output (and to the log)

[...]
`article` not loaded
(/usr/local/texlive/2019/texmf-dist/tex/latex/base/article.cls
Document Class: article 2018/09/03 v1.4i Standard LaTeX document class
(/usr/local/texlive/2019/texmf-dist/tex/latex/base/size10.clo))
`article` loaded
`cleveref` not loaded
(/usr/local/texlive/2019/texmf-dist/tex/latex/cleveref/cleveref.sty)
`cleveref` loaded)
[...]

Another version which is more compatible with older LuaTeX versions (works with TeXLive 2017, I haven't tested any earlier versions) can be created by using token.get_macro instead of inspecting the command id. This is theoretically less compatible with the TeX macro, but I currently can't think of any case where is matters:

\directlua{
  local function loaded(ext)
    local fmt = "ver@\csstring\%s." .. ext
    return function(name)
      return not not token.get_macro(fmt:format(name))
    end
  end
  packageloaded = loaded"sty"
  classloaded = loaded"cls"
}
\directlua{
if classloaded"article" then
  texio.write_nl"`article` loaded"
else
  texio.write_nl"`article` not loaded"
end
}
\documentclass{article}
\directlua{
if classloaded"article" then
  texio.write_nl"`article` loaded"
else
  texio.write_nl"`article` not loaded"
end
if packageloaded"cleveref" then
  texio.write_nl"`cleveref` loaded"
else
  texio.write_nl"`cleveref` not loaded"
end
}
\usepackage{cleveref}
\directlua{
  if packageloaded"cleveref" then
    texio.write_nl"`cleveref` loaded"
  else
    texio.write_nl"`cleveref` not loaded"
  end
}
\stop

Both answers use the line

local fmt = "ver@\csstring\%s." .. ext

Here \csstring\% is just expanded into a % (with catcode other). This is necessary because TeX otherwise treats % as the comment character. If this code is moved into a normal Lua file (or a luacode* block), TeX never sees the line, so \csstring\% has to be replaced by %. Then the line becomes

local fmt = "ver@%s." .. ext
0
0

I have accepted an answer already but want to show the result I came up with, implementing the solution in a Lua file

local function _loaded(ext)
--[[
    Returns a function that checks whether a class or a package is loaded
--]]
    local fmt = "ver@%s." .. ext
    return function(name)
        local macro = token.get_macro(fmt:format(name))
        return macro ~= nil
    end
end

-- Lua implementations of \@ifpackageloaded and \@ifclassloaded
lib.class_loaded = _loaded('cls')
lib.package_loaded = _loaded('sty')

lib is the table that is returned from the module, and the code is used in the luaoptions package (https://github.com/lualatex-tools/luaoptions/blob/master/luaoptions-lib.lua#L142) which is to be released on CTAN pretty soon.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .