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?

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

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.