LaTeX defines a lot of macros which are only supposed to be used in the preamble of a document (such as \usepackage, but also some internal kernel macros). It does this via \@onlypreamble\SomeMacro. This works by adding \SomeMacro to a list, which at \begin{document} are all set equal to \@notprerr, which is a macro that generates an error. In particular, the kernel declares \@onlypreamble\@onlypreamble. This means that we can test if we are in the preamble by doing
\ifx\@onlypreamble\@notprerr (document)\else (preamble)\fi
If we're in a package and would like to know if we are loaded before or after the \documentclass declaration, we can look at what \documentclass does: Among lots of other things, it says \let\documentclass\@twoclasseserror (which prevents invoking \documentclass twice). So testing if \documentclass is ifx-equal to \@twoclasseserror takes care of this.
\makeatletter
\def\MakeTest#1{%
\edef#1{%
\ifx\documentclass\@twoclasseserror % after \documentclass
\ifx\@onlypreamble\@notprerr % after \begin{document}
Hello
\else % before \begin{document}
Howdy
\fi
\else % before \documentclass
Hi!
\fi}}
\makeatother
\MakeTest\foo
\documentclass{minimal}
\MakeTest\bar
\begin{document}
\MakeTest\baz
foo says \foo; bar says \bar; baz says \baz
\end{document}
I think this is pretty robust; I don't think any packages change \documentclass or \@onlypreamble after they have been changed to their error-giving meanings.
etoolbox adds \AfterEndPreamble. Code in \AfterEndPreamble{...} will be detected as "after \begin{document}" (as pointed out by Ahmed Musa in the comments).
On the one hand per definitionem \AfterEndPreamble code is after \begin{document}, see http://mirrors.ctan.org/macros/latex/contrib/etoolbox/etoolbox.pdf:
\AtEndPreamble code is part of the preamble; \AfterEndPreamble
code is part of the document body and may contain printable text to be
typeset at the very beginning of the document. To sum that up, LaTeX
will perform the following tasks ‘inside’ \begin{document}:
- Execute any
\AtEndPreamble code
- Start initialization for document body (page layout, default fonts, etc.)
- Load the main aux file written on the previous LaTeX pass
- Open the main aux file for writing on the current pass
- Continue initialization for document body
- Execute any
\AtBeginDocument code
- Complete initialization for document body
- Disable all
\@onlypreamble commands
- Execute any
\AfterEndPreamble code
On the other hand it is still executed before any of the code typed after \begin{document}.
\AfterEndPreamble, this doesn't seem 'pretty robust'. When some code is hooked to the end of\document,\let\@onlypreamble=\@notprerrdoesn't signify the end of\begin{document}. – Ahmed Musa Nov 17 '12 at 18:12\begin{document}– Daniele Segato May 30 '18 at 09:07\ifx\@onlypreamble\@notprerr (document)\else (preamble)\fidoesn't work alone, you need\makeatletterand\makeatotherfor those checks to work – Daniele Segato May 30 '18 at 10:44