6

This is in the category of "things that work, but make me nervous."

I use a very complicated, custom document class with LuaLaTeX. But my question can easily be illustrated with this generic MWE:

\documentclass{article}
\RequirePackage{xifthen}
\RequirePackage{letltxmacro}
\LetLtxMacro\myusepackage\usepackage\relax
\LetLtxMacro\myRequirePackage\RequirePackage\relax
\renewcommand\usepackage[2][]{%
  \ifthenelse{\equal{#2}{hyperref}}{}{\myusepackage[#1]{#2}}% 
}
\renewcommand\RequirePackage[2][]{%
  \ifthenelse{\equal{#2}{hyperref}}{}{\myRequirePackage[#1]{#2}}% 
}
%
\usepackage{xstring} % should work
\usepackage{hyperref} % should do nothing
%
\begin{document}
OK
\end{document}

If the user attempts to load hyperref, it will silently be ignored. I realize that there are alternative such as \PassOptionsToPackage, but that is not the effect I seek.

I routinely patch commands from various LaTeX packages, but those are high-level commands in optional packages, rather than something as fundamental as \usepackage or \RequirePackage. So my question is: Does the above MWE code have any hidden traps?

I only chose hyperref for purposes of the MWE, so there's no need to ask why I would want to block it in a real document.

EDIT: For those who wonder why I would do this: My custom document class requires certain things to be loaded in precise order, due to \immediate\write commands. If the user loads a package prematurely, currently I detect that by throwing an error via \@ifpackageloaded in the class code, just before my own code loads the package \AtEndPreamble. But it seems to me that it would be more user-friendly to simply ignore the premature loading.

EDIT2: Based on comments and answers, I believe that I should retain my current method, rather than doing what the question asks. "Bad user interface" (per DC) is an important point.

Without getting into the over-200K bytes of my custom document class, I can illustrate what I have been doing. This has been in good working order for many months. General concept of "myscustomclass.cls" is this:

a) When mycustomclass loads, it also loads and pre-configures numerous packages. It also provides its own commands.

b) It contains \AtEndPreamble that will load and configure additional packages. These cannot be loaded until after the user's preamble, since it needs to look at the user's preamble and decide what to do.

c) At least one of the later-loaded packages has \immediate\write, which cannot occur earlier than a certain time. That is why it is deferred until \AtEndPreamble.

Currently (good working code) I do this kind of thing:

\AtEndPreamble{
  % lots of code here
  @ifpackageloaded{hyperref}{ % Should not have been loaded by user! Too early!
    \ClassError{mycustomclass}%
    {Cannot load hyperref in preamble}{See myscustomclass docs section XX.XXX}
  }{
    \usepackage[various options]{hyperref}
  }
  % lots of code here
}
  • 2
    \expandafter\def\csname ver@hyperref.sty\endcsname{3000/12/31} seems a better choice for ignoring the loading of a package. See https://tex.stackexchange.com/a/85700/4427 for an example of what can go wrong. – egreg Jan 28 '18 at 00:59
  • 5
    silently disabling the package loading seems a very confusing interface for the user, if they load hyperref and use a command such as \href they will just get an undefined command error with no hint of why. Better if your class went \AtBeginDocument{\@ifpackageloaded{hyperref}{\ClassError{myclass}{I told you not to load hyperref}{}}{}} so the user gets a more relevant message. – David Carlisle Jan 28 '18 at 01:05
  • @DavidCarlisle Actually, I would load \hyperref in the class code. It is a matter of timing. See my edit to the question. –  Jan 28 '18 at 01:26
  • What if the user needs to load hyperref with options? What if they follow the loading with \hypersetup? What if they next load cleveref? What if they load bookmark? – cfr Jan 28 '18 at 01:31
  • @cfr I picked hyperref "for purposes of illustration." In fact, the user could not load hyperref, because I load it with options. The key is timing, not options. Several key packages must be loaded in specific order, controlled by the custom document class, \AtEndPreamble. So the question is not "what if," the question is "does my method work." As David Carlisle shows in an answer, there are indeed issues that I had not addressed. –  Jan 28 '18 at 01:39
  • if hyperref is loaded in the class there is nothing more to do, latex never loads a package twice. – David Carlisle Jan 28 '18 at 01:43
  • @DavidCarlisle hyperref is loaded \AtEndPreamble at a specific time. Cannot be loaded earlier, such as during Preamble by the user. That's not the only package involved, I just picked it for illustration. –  Jan 28 '18 at 01:44
  • i still think ignoring user commands is a bad interface and an error (as you now indicate you do would be better) but for the specific case of hyperref, it does very little when loaded so it seems surprising that your AtBeginDocument command can not detect if hyperref is loaded already and in that case just give whatever \hypersetup{...} commands you need at that point. – David Carlisle Jan 28 '18 at 01:52
  • @DavidCarlisle The fatal comment is "bad user interface." I try to avoid that. Apparently my concept was just a bad idea, and I will keep my existing code (see edit to question). –  Jan 28 '18 at 02:03
  • 1
    The user could not load hyperref, but then they'll get an error when the next line is \hypersetup or more mysterious failures with cleveref. Or they'll get an error if the next line loads bookmark. The same goes for lots of other packages. People often load them and then do config in the preamble. The fact that the config commands will be loaded at the end of the preamble doesn't help. Any package which loads a package you drop will also cause trouble, as it will try to execute commands not yet defined. This isn't just about hyperref. – cfr Jan 28 '18 at 02:10
  • @cfr Ineed. I've already been talked out of what I asked. But the current code works fine. After all, the documentation specifically tells the user NOT to load hyperref or the other forbidden packages, because they won't work as expected. The purpose of my code is to catch those who don't read the docs. Of course, I cannot deal with every single LaTeX package, because there are so many of them. But I can address the ones most likely to be used, out of force of habit. –  Jan 28 '18 at 02:16
  • 1
    Your current approach seems good to me. I'd comment the stray line endings, but I am not sure whether it matters here or not. That may just be habit. – cfr Jan 28 '18 at 02:17
  • 1
    Freaking templates. – Johannes_B Jan 28 '18 at 04:13

2 Answers2

9

Not sure what the question is here, but patching \usepackage as shown won't work as intended in cases such as

\usepackage{longtable,hyperref}

as it will not see hyperref as the argument, and it will completely break valid uses such as

\usepackage{hyperref}[2001/01/01]

as it removes the optional argument so latex will try to typeset [2001/01/01] in the preamble.

David Carlisle
  • 757,742
  • Ah, that actually answers my question, at least partially. I can parse the \usepackage mandatory argument for specific strings, and that would address the first issue. But I had not thought of the following optional argument. So indeed, there is a trap. More thought required on my part. Glad I asked. –  Jan 28 '18 at 01:19
5

In addition to David's answer, consider

\usepackage{bookmark}

bookmark.sty includes

\RequirePackage{hyperref}[2010/06/18]

and a bunch of other things and then a bunch of code which depends on hyperref.

Or

\usepackage[<options>]{hyperref}

You simply discard the user's options.

Or

\usepackage{hyperref}
\usepackage{cleveref}

or some other package which must be loaded after hyperref.

Or

\usepackage{hyperref}
\hypersetup{...}
cfr
  • 198,882
  • Indeed. But the user could not usebookmark anyway. This is PDF/X, where active content is forbidden. Package hyperref is only loaded because it does some encoding processing. No actual links allowed. In the case you raised, that would require an error. –  Jan 28 '18 at 01:41
  • 1
    @RobtAll It is just an example. Any package which provides macros which might be used in the preamble will cause trouble. Any package which another package may load and then use will cause trouble. In any case, saying \usepackage{bookmark} will give a weird and mysterious error, whereas you could have produced a helpful and intelligible one when you found hyperref had been loaded already. You are making it more difficult for the user to figure out what the problem is rather than making it easier or, failing that, at least no harder. – cfr Jan 28 '18 at 02:14
  • 1
    Yes, I see that now. My current working code is good enough. Bad idea to "fix" it. –  Jan 28 '18 at 02:17
  • @RobtAll If you want to prevent hyperlinks, perhaps it would simpler to load nohyperref instead. This will provide the hyperref interface but prevent the formation of links, and will also prevent the user from loading hyperref as the two packages conflict. (However, I'm not sure if nohyperref does the encoding processing you require, but you might want to investigate it.) – Nicola Talbot Jan 28 '18 at 15:23
  • @NicolaTalbot It is more complicated than hyperlinks. My MWE was "too minimal" in that it did not discuss the entire situation. –  Jan 28 '18 at 16:21