summaryrefslogtreecommitdiff
path: root/macros/latex/base/ltcmdhooks.dtx
blob: 40cd55e52c75ddb0932b7d8db61f1bc4913fc72e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
% \iffalse meta-comment
%
%% File: ltcmdhooks.dtx (C) Copyright 2020-2021
%%       Frank Mittelbach, Phelype Oleinik, LaTeX Team
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
% license or (at your option) any later version.  The latest version
% of this license is in the file
%
%    https://www.latex-project.org/lppl.txt
%
%
%%% From File: ltcmdhooks.dtx
%
\def\ltcmdhooksversion{v1.0b}
\def\ltcmdhooksdate{2021/05/26}
%
%
%
%<*driver>
\documentclass{l3doc}
%\usepackage{ltcmdhooks}
\makeatletter
\providecommand\footref[1]{%
  \begingroup
    \unrestored@protected@xdef\@thefnmark{\ref{#1}}%
  \endgroup
  \@footnotemark}
\makeatother
\EnableCrossrefs
\CodelineIndex
\begin{document}
  \DocInput{ltcmdhooks.dtx}
\end{document}
%</driver>
%
% \fi
%
%
% \providecommand\hook[1]{\texttt{#1}}
% \providecommand\fmi[1]{\marginpar{\footnotesize FMi: #1}}
% \providecommand\pho[1]{\marginpar{\footnotesize PhO: #1}}
% \providecommand\phoinline[1]{\begin{quote}\itshape\footnotesize PhO: #1\end{quote}}
%
% \title{The \texttt{ltcmdhooks} module\thanks{This file has version
%    \ltcmdhooksversion\ dated \ltcmdhooksdate, \copyright\ \LaTeX\
%    Project.}}
% \author{Frank Mittelbach \and Phelype Oleinik}
%
% \maketitle
%
%
% \tableofcontents
%
%
% \section{Introduction}
%
%    This file implements generic hooks for (arbitrary) commands.
%    In theory every command \tn[no-index]{\meta{name}} offers now two
%    associated hooks to which code can be added using \tn{AddToHook}
%    or \tn{AddToHookNext}.\footnote{In practice this is not supported
%    for all types of commands, see section~\ref{sec:look-ahead} for
%    the restrictions that apply and what happens if one tries to use
%    this with commands for which this is not supported.}  These are
%    \begin{description}
%    \item[\hook{cmd/\meta{name}/before}]
%
%       This hook is executed at the very start of the command
%       execution after its arguments (if any) are parsed.  The hook
%       \meta{code} is wrapped in the command inside a call to
%       \cs{UseHook}|{cmd/|\meta{name}|/before}|, so the arguments
%       passed to the command are \emph{not} available in the hook
%       \meta{code}.
%
%    \item[\hook{cmd/\meta{name}/after}]
%
%       This hook is similar to \hook{cmd/\meta{name}/before}, but it is
%       executed at the very end of the command body.  This hook is
%       implemented as a reversed hook.
%    \end{description}
%
%    The hooks are not physically present before
%    \verb=\begin{document}= (i.e., using a command in the preamble
%    will never execute them) and if nobody has declared any code for
%    them, then they are not added to the command code ever.  For
%    example, if we have the following definition
%\begin{verbatim}
%    \newcommand\foo[2]{Code #1 for #2!}
%\end{verbatim}
%    then executing \verb=\foo{A}{B}= will simply run
%    \verb*=Code A for B!=
%    as it was always the case. However, if somebody, somewhere (e.g.,
%    in a package) adds
%\begin{verbatim}
%    \AddToHook{cmd/foo/before}{<before code>}
%\end{verbatim}
%    then, after |\begin{document}| the definition of \cs[no-index]{foo} will be:
%\begin{verbatim}
%    \renewcommand\foo[2]{\UseHook{cmd/foo/before}Code #1 for #2!}
%\end{verbatim}
%    and similarly \verb=\AddToHook{cmd/foo/after}{<after code>}=
%    alters the definition to
%\begin{verbatim}
%    \renewcommand\foo[2]{Code #1 for #2!\UseHook{cmd/foo/after}}
%\end{verbatim}
%
%    In other words, the mechanism is similar to what \pkg{etoolbox}
%    offers with \tn{pretocmd} and \tn{apptocmd} with the important
%    differences 
%    \begin{itemize}
%    \item
%
%      that code can be prepended or appended (i.e., added to the
%      hooks) even if the command itself is not defined, because the
%      defining package has not yet been loaded
%
%    \item
%
%      and that by using the hook management interface it is now
%      possible to define how the code chunks added in these places
%      are ordered, if different packages want to add code at these
%      points.
%
%    \end{itemize}
%
%
%
%
% \section{Restrictions and Operational details}
% \label{sec:restrictions}
%
% Adding arbitrary material to commands is tricky because most of the
% time we do not know what the macro expects as arguments when expanding
% and \TeX{} doesn't have a reliable way to see that, so some guesswork
% has to be employed.
%
% \subsection{Patching}
%
% The code here tries to find out if a command was defined with
% \tn{newcommand} or \tn{DeclareRobustCommand} or
% \tn{NewDocumentCommand}, and if so it \emph{assumes} that the argument
% specification of the command is as expected (which is not fail-proof,
% if someone redefines the internals of these commands in devious ways,
% but is a reasonable assumption).
%
% If the command is one of the defined types, the code here does a
% sandboxed expansion of the command such that it can be redefined again
% exactly as before, but with the hook code added.
%
% If however the command is not a known type (it was defined with
% \tn{def}, for example), then the code uses an approach similar to
% \pkg{etoolbox}'s \tn{patchcmd} to retokenize the command with the hook
% code in place.  This procedure, however, is more likely to fail if the
% catcode settings are not the same as the ones at the time of command's
% definition, so not always adding a hook to a command will work.
%
% \subsubsection{Timing}
%
% When \cs{AddToHook} (or its \pkg{expl3} equivalent) is called with
% a generic |cmd| hook, say, \hook{cmd/foo/before}, for the first time
% (that is, no code was added to that same hook before), in the preamble
% of a document, it will store a patch instruction for that command
% until |\begin{document}|, and only then all the commands which had
% hooks added will be patched in one go.  That means that no command in
% the preamble will have hooks patched into them.
%
% At |\begin{document}| all the delayed patches will be executed, and
% if the command doesn't exist the code is still added to the hook,
% but it will not be executed.  After |\begin{document}|, when
% \cs{AddToHook} is called with a generic |cmd| hook the first time,
% the command will be immediately patched to include the hook, and if
% it doesn't exist or if it can't be patched for any reason, an error
% is thrown; if \cs{AddToHook} was already used in the preamble no new
% patching is attempted.
%
% This has the consequence that a command defined or redefined after
% |\begin{document}| only uses generic |cmd| hook code if
% \cs{AddToHook} is called for the first time after the definition is
% made, or if the command explicitly uses the generic hook in its
% definition by declaring it with \cs{NewHookPair} adding \cs{UseHook} as
% part of the code.\footnote{We might change this behavior in the main
% document slightly after gaining some usage experience.}
%
%
% \subsection{Commands that look ahead}
% \label{sec:look-ahead}
%
% Some commands are defined in different ``steps'' and they look ahead
% in the input stream to find more arguments.  If you try to add some
% code to the \hook{cmd/\meta{name}/after} hook of such command, it will
% not work, and it is not possible to detect that programmatically, so
% the user has to know (or find out) which commands can or cannot have
% hooks attached to them.
%
% One good example is the \tn{section} command.  You can add something
% to the \hook{cmd/section/before} hook, but if you try to add something
% to the \hook{cmd/section/after} hook, \tn{section} will no longer
% work.  That happens because the \tn{section} macro takes no argument,
% but instead calls a few internal \LaTeX{} macros to look for the
% optional and mandatory arguments.  By adding code to the
% \hook{cmd/section/after} hook, you get in the way of that scanning.
%
%
%
% \section{Package Author Interface}
%
% The \hook{cmd} hooks are, by default, available for all commands
% that can be patched to add the hooks.  For some commands, however,
% the very beginning or the very end of the code is not the best place
% to put the hooks, for example, if the command looks ahead for
% arguments (see section~\ref{sec:look-ahead}).
%
% If you are a package author and you want to add the hooks to your
% own commands in the proper position you can define the command and
% manually add the \cs{UseHook} calls inside the command in the proper
% positions, and manually define the hooks with \cs{NewHook} or
% \cs{NewReversedHook}.  When the hooks are explicitly defined,
% patching is not attempted so you can make sure your command works
% properly.  For example, an (admittedly not really useful) command
% that typesets its contents in a framed box with width optionally
% given in parentheses:
% \begin{verbatim}
%    \newcommand\fancybox{\@ifnextchar({\@fancybox}{\@fancybox(5cm)}}
%    \def\@fancybox(#1)#2{\fbox{\parbox{#1}{#2}}}
% \end{verbatim}
% If you try that definition, then add some code after it with
% \begin{verbatim}
%    \AddToHook{cmd/fancybox/after}{<code>}
% \end{verbatim}
% and then use the \cs[no-index]{fancybox} command you will see that it
% will be completely broken, because the hook will get executed in the
% middle of parsing for optional \texttt{(...)} argument.
%
% If, on the other hand, you want to add hooks to your command you can
% do something like:
% \begin{verbatim}
%   \newcommand\fancybox{\@ifnextchar({\@fancybox}{\@fancybox(5cm)}}
%   \def\@fancybox(#1)#2{\fbox{%
%                        \UseHook{cmd/fancybox/before}%
%                        \parbox{#1}{#2}%
%                        \UseHook{cmd/fancybox/after}}}
%   \NewHook{cmd/fancybox/before}
%   \NewReversedHook{cmd/fancybox/after}
% \end{verbatim}
% then the hooks will be executed where they should and no patching
% will be attempted.  It is important that the hooks are declared with
% \cs{NewHook} or \cs{NewReversedHook}, otherwise the command hook
% code will try to patch the command.  Note also that the call to
% |\UseHook{cmd/fancybox/before}| does not need to be in the
% definition of \cs[no-index]{fancybox}, but anywhere it makes sense
% to insert it (in this case in the internal
% \cs[no-index]{@fancybox}).
%
% Alternatively, if for whatever reason your command does not support
% the generic hooks provided here, you can disable a hook with
% \cs{DisableHook}\footnote{Please use \cs{DisableHook} if at all, only
% on hooks that you \enquote{own}, i.e., for commands that your
% package or class defines and not second guess
% whether or not hooks of other packages should get disabled!}, so
% that when someone tries to add code to it they will get an error.
% Or if you don't want the error, you can simply declare the hook with
% \cs{NewHook} and never use it.
%
%
% The above approach is useful for really complex commands where for
% one or the other reason the hooks can't be placed at the very
% beginning and end of the command body and some hand-crafting is
% needed. However, in the example above the real (and in fact only)
% issue is the cascading argument parsing in the style developed long
% ago in \LaTeX~2.09. Thus, a much simpler solution for this case is
% to replace it with the modern \cs{NewDocumentCommand} syntax and
% define the command as follows:
% \begin{verbatim}
%  \DeclareDocumentCommand\fancybox{D(){5cm}m}{\fbox{\parbox{#1}{#2}}}
% \end{verbatim}
% If you do that then both hooks automatically work and are patched
% into the right places.
%
% \StopEventually{\setlength\IndexMin{200pt}  \PrintIndex  }
%
%
%
% \section{The Implementation}
%
% \subsection{Execution plan}
%
% To add |before| and |after| hooks to a command we will need to peek
% into the definition of a command, which is always a tricky thing to
% do.  Some cases are easy because we know how the command was defined,
% so we can assume how its \meta{parameter text} looks like (for example
% a command defined with \tn{newcommand} may have an optional argument
% followed by a run of mandatory arguments), so we can just expand that
% command and make it grab |#1|, |#2|, etc.\@ as arguments and
% define it all back with the hooks added.
%
% Life's usually not that easy, so with some commands we can't do that
% (a |#1| might as well be |#|$_{12}$|1|$_{12}$ instead of the expected
% |#|$_{6}$|1|$_{12}$, for example) so we need to resort to ``patching''
% the command: read its \tn{meaning}, and tokenize it again with
% \tn{scantokens} and hope for the best.
%
% So the overall plan is:
% \begin{enumerate}
%   \item
%     Check if a command is of a known type (that is, defined with
%     \tn{newcommand}\footnote{It's not always possible to reliably
%     detect this case because a command defined with no optional
%     argument is indistinguishable from a \tn{def}ed command.},
%     \cs[no-index]{DeclareRobustCommand}, or
%     \cs[no-index]{New(Expandable)DocumentCommand}), and if is, take
%     appropriate action.
%   \item
%     If the command is not a known type, we'll check if the command can
%     be patched.  Two things will prevent a command from being
%     patched:  if it was defined in a nonstandard catcode setting, or
%     if it is an internal expl3 command with |__|\meta{module} in its
%     name, in which case we refuse to patch.
%   \item
%     If the command was defined in nonstandard catcode settings, we
%     will try a few standard ones to try our best to carry out the
%     pathing.  If this doesn't help either, the code will give up and
%     throw an error.
% \end{enumerate}
%
%
%    \begin{macrocode}
%<@@=hook>
%    \end{macrocode}
%
% \changes{v1.0b}{2021/05/24}{Use \cs{msg_...} instead of \cs{__kernel_msg...}}
%
%    \begin{macrocode}
%<*2ekernel|latexrelease>
\ExplSyntaxOn
%<latexrelease>\NewModuleRelease{2021/06/01}{ltcmdhooks}
%<latexrelease>                 {The~hook~management~system~for~commands}
%    \end{macrocode}
%
% \subsection{Variables}
%
% \begin{macro}[int]{\g_hook_patch_action_list_tl}
%    Pairs of |\if<cmd>..\patch<cmd>| to be used with
%    \tn{robust@command@act} when looking for a known patching
%    rule. This token list is exposed because we see some future
%    applications (with very specialized packages, such as
%    \pkg{etoolbox} that may want to extend the pairs processed. It is
%    not meant for general use which is why it is not documented in
%    the interface documentation above.
%    \begin{macrocode}
\tl_new:N \g_hook_patch_action_list_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\l_@@_patch_num_args_int}
%   The number of arguments in a macro being patched.
%    \begin{macrocode}
\int_new:N \l_@@_patch_num_args_int
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\l_@@_patch_prefixes_tl}
% \begin{macro}{\l_@@_patch_param_text_tl}
% \begin{macro}{\l_@@_patch_replacement_tl}
%   The prefixes and parameters of the definition for the macro being
%   patched.
%    \begin{macrocode}
\tl_new:N \l_@@_patch_prefixes_tl
\tl_new:N \l_@@_patch_param_text_tl
\tl_new:N \l_@@_patch_replacement_tl
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\g_@@_delayed_patches_prop}
%   A list containing the patches delayed to |\begin{document}|, so that
%   patching is not attempted twice.
%    \begin{macrocode}
\prop_new:N \g_@@_delayed_patches_prop
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_patch_debug:x}
%   A helper for patching debug info.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_debug:x #1
  { \@@_debug:n { \iow_term:x { [lthooks]~#1 } } }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Variants}
%
% \begin{macro}[int]{\tl_rescan:nV}
%   \pkg{expl3} function variants used throughout the code.
%    \begin{macrocode}
\cs_generate_variant:Nn \tl_rescan:nn { nV }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Patching or delaying}
%
% \begin{macro}{\@@_try_put_cmd_hook:n,\@@_try_put_cmd_hook:w}
%   Before |\begin{document}| all patching is delayed.
%   This function is called from within \cs{AddToHook}, when code is
%   added to a generic |cmd| hook is newly declared.  It checks whether
%   the patch position is valid, then proceeds to trying to patch or
%   delaying to |\begin{document}| if in the preamble.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_try_put_cmd_hook:n #1
  { \@@_try_put_cmd_hook:w #1 / / / \s_@@_mark {#1} }
\cs_new_protected:Npn \@@_try_put_cmd_hook:w
    #1 / #2 / #3 / #4 \s_@@_mark #5
  {
    \@@_debug:n { \iow_term:n { ->~Adding~cmd~hook~to~'#2'~(#3): } }
    \str_case:nnTF {#3}
        { { before } { } { after } { } }
      { \exp_args:Nc \@@_patch_cmd_or_delay:Nnn {#2} {#2} {#3} }
      { \msg_error:nnnn { hooks } { wrong-cmd-hook } {#2} {#3} }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_patch_cmd_or_delay:Nnn}
% \begin{macro}{\@@_cmd_begindocument_code:}
%   In the preamble, \cs{@@_patch_cmd_or_delay:Nnn} just adds the patch
%   instruction to a property list to be executed later.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_cmd_or_delay:Nnn #1 #2 #3
  {
    \@@_debug:n { \iow_term:n { ->~Add~generic~cmd~hook~for~#2~(#3). } }
    \@@_debug:n
      { \iow_term:n { !~In~the~preamble:~delaying. } }
    \prop_gput:Nnn \g_@@_delayed_patches_prop { #2 / #3 }
      { \@@_cmd_try_patch:nn {#2} {#3} }
  }
%    \end{macrocode}
%
%   The delayed patches are added to a property list to prevent
%   duplication, and the code stored in the property list for each
%   key is executed.  The function \cs{@@_patch_cmd_or_delay:Nnn} is
%   also redefined to be \cs{@@_patch_command:Nnn} so that no further
%   delaying is attempted.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_cmd_begindocument_code:
  {
    \cs_gset_eq:NN \@@_patch_cmd_or_delay:Nnn \@@_patch_command:Nnn
    \prop_map_function:NN \g_@@_delayed_patches_prop { \use_ii:nn }
    \prop_gclear:N \g_@@_delayed_patches_prop
    \cs_undefine:N \@@_cmd_begindocument_code:
  }
\g@addto@macro \@kernel@after@begindocument
  { \@@_cmd_begindocument_code: }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_cmd_try_patch:nn}
%   At |\begin{document}| tries patching the command if the hook
%   was not manually created in the meantime.  If the document does not
%   exist, no error is raised here as it may hook into a package that
%   wasn't loaded.  Hooks added to commands in the document body still
%   raise an error if the command is not defined.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_cmd_try_patch:nn #1 #2
  {
    \@@_debug:n
      { \iow_term:x { ->~\string\begin{document}~try~cmd / #1 / #2. } }
    \@@_if_declared:nTF { cmd / #1 / #2 }
      {
        \@@_debug:n
          { \iow_term:n { .->~Giving~up:~hook~already~created. } }
      }
      {
        \cs_if_exist:cT {#1}
          { \exp_args:Nc \@@_patch_command:Nnn {#1} {#1} {#2} }
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
%
%
% \subsection{Patching commands}
%
% \begin{macro}{\@@_patch_command:Nnn}
% \begin{macro}{\@@_patch_check:NNnn}
% \begin{macro}[TF]{\@@_if_public_command:N}
% \begin{macro}{\@@_if_public_command:w}
%   \cs{@@_patch_command:Nnn} will do some sanity checks on the
%   argument to detect if it is possible to add hooks to the command,
%   and raises an error otherwise.  If the command can contain hooks,
%   then it uses \tn{robust@command@act} to find out what type is the
%   command, and patch it accordingly.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_command:Nnn #1 #2 #3
  {
    \@@_patch_debug:x { analyzing~'\token_to_str:N #1' }
    \@@_patch_debug:x { \token_to_str:N #1 = \token_to_meaning:N #1 }
    \@@_patch_check:NNnn \cs_if_exist:NTF #1 { undef }
      {
        \@@_patch_debug:x { ++~control~sequence~is~defined }
        \@@_patch_check:NNnn \token_if_macro:NTF #1 { macro }
          {
            \@@_patch_debug:x { ++~control~sequence~is~a~macro }
            \@@_patch_check:NNnn \@@_if_public_command:NTF #1 { expl3 }
              {
                \@@_patch_debug:x { ++~macro~is~not~private }
                \robust@command@act
                  \g_hook_patch_action_list_tl #1
                  \@@_retokenize_patch:Nnn { #1 {#2} {#3} }
              }
          }
      }
  }
%    \end{macrocode}
%
%   And here's the auxiliary used above:
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_check:NNnn #1 #2 #3 #4
  {
    #1 #2 {#4}
      {
        \msg_error:nnxx { hooks } { cant-patch }
          { \token_to_str:N #2 } {#3}
      }
  }
%    \end{macrocode}
%   and a conditional \cs{@@_if_public_command:N} to check if a command
%   has |__| in its name (no other checking is performed).  Primitives
%   with |:D| in their name could be included here, but they are already
%   discarded in the \cs{token_if_macro:NTF} test above.
%    \begin{macrocode}
\use:x
  {
    \prg_new_protected_conditional:Npnn
        \exp_not:N \@@_if_public_command:N ##1 { TF }
      {
        \exp_not:N \exp_last_unbraced:Nf
          \exp_not:N \@@_if_public_command:w
            { \exp_not:N \cs_to_str:N ##1 }
          \tl_to_str:n { _ _ } \s_@@_mark
      }
  }
\exp_last_unbraced:NNNNo
\cs_new_protected:Npn \@@_if_public_command:w
    #1 \tl_to_str:n { _ _ } #2 \s_@@_mark
  {
    \tl_if_empty:nTF {#2}
      { \prg_return_true: }
      { \prg_return_false: }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
%
%
%
%
%
%
% \subsubsection{Patching by expansion and redefinition}
%
% \begin{macro}[int]{\g_hook_patch_action_list_tl}
%    This is the list of known command types and the function that
%    patches the command hooks into them.  The conditionals are taken
%    from \tn{ShowCommand}, \tn{NewCommandCopy} and
%    \cs{__kernel_cmd_if_xparse:NTF} defined in \texttt{ltcmd}.
%    \begin{macrocode}
\tl_gset:Nn \g_hook_patch_action_list_tl
  {
    { \@if@DeclareRobustCommand \@@_patch_DeclareRobustCommand:Nnn }
    { \@if@newcommand \@@_patch_newcommand:Nnn }
    { \__kernel_cmd_if_xparse:NTF \@@_cmd_patch_xparse:Nnn }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
%
% \begin{macro}{\@@_patch_DeclareRobustCommand:Nnn}
%   At this point we know that the commands can be patched by expanding
%   then redefining.  These are the cases of commands defined with
%   \tn{newcommand} with an optional argument or with
%   \tn{DeclareRobustCommand}.
%
%   With \cs{@@_patch_DeclareRobustCommand:Nnn} we check if the command
%   has an optional argument (with a test counter-intuitively called
%   \tn{@if@newcommand}).  If so, we forward the action to
%   \cs{@@_patch_newcommand:Nnn}, otherwise call the patching engine
%   \cs{@@_patch_expand_redefine:NNnn} with a \cs{c_false_bool} to
%   indicate that there is no optional argument.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_DeclareRobustCommand:Nnn #1
  {
    \exp_args:Nc \@if@newcommand { \cs_to_str:N #1 ~ }
      { \exp_args:Nc \@@_patch_newcommand:Nnn }
      { \exp_args:NNc \@@_patch_expand_redefine:NNnn \c_false_bool }
        { \cs_to_str:N #1 ~ }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}{\@@_patch_newcommand:Nnn}
%   If the command was defined with \tn{newcommand} and an optional
%   argument, call the patching engine with a \cs{c_true_bool} to flag
%   the presence of an optional argument, and with
%   \cs[no-index]{\string\command} to patch the actual code for
%   \cs[no-index]{command}.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_newcommand:Nnn #1
  {
    \exp_args:NNc \@@_patch_expand_redefine:NNnn \c_true_bool
      { \c_backslash_str \cs_to_str:N #1 }
  }
%    \end{macrocode}
% \end{macro}
%
%  \begin{macro}{\@@_cmd_patch_xparse:Nnn}
%    And for commands defined by the \pkg{xparse} commands use this
%    for patching:
%    \begin{macrocode}
\cs_new_protected:Npn \@@_cmd_patch_xparse:Nnn #1
  {
    \exp_args:NNc \@@_patch_expand_redefine:NNnn \c_false_bool
      { \cs_to_str:N #1 ~ code }
  }
%    \end{macrocode}
%  \end{macro}
%
%
%
%
% \begin{macro}{\@@_patch_expand_redefine:NNnn}
% \begin{macro}[EXP]{\@@_make_prefixes:w}
%   Now the real action begins.  Here we have in |#1| a boolean
%   indicating if the command has a |[|\ldots|]|-delimited argument, in
%   |#2| the command control sequence, in |#3| the name of the command
%   (note that |#1|${}\ne{}$|\csname#2\endcsname| at this point!), and
%   in |#4| the hook position, either |before| or |after|.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_expand_redefine:NNnn #1 #2 #3 #4
  {
    \@@_patch_debug:x { ++~command~can~be~patched~without~rescanning }
%    \end{macrocode}
%   We'll start by counting the number of arguments in the command by
%   counting the number of characters in the \cs{cs_argument_spec:N} of
%   the macro, divided by two, and subtracting one if the command has an
%   optional argument (that is, an extra |[]| in its
%   \meta{parameter text}).
%    \begin{macrocode}
    \int_set:Nn \l_@@_patch_num_args_int
      {
        \exp_args:Nf \str_count:n { \cs_argument_spec:N #2 } / 2
        \bool_if:NT #1 { -1 }
      }
%    \end{macrocode}
%   Now build two token lists:
%   \begin{description}
%     \item[\cs{l_@@_patch_param_text_tl}] will contain the
%       \meta{parameter text} to be used when redefining the macro.  It
%       should be identical to the \meta{parameter text} used when
%       originally defining that macro.
%     \item[\cs{l_@@_patch_replacement_tl}] will contain braced pairs of
%       |#|$_{12}$\meta{num} to feed to the macro when expanded.  This
%       token list as well as the previous will have the first item
%       surrounded by |[|\ldots|]| in the case of an optional argument.
%   \end{description}
%    \begin{macrocode}
    \int_compare:nNnTF { \l_@@_patch_num_args_int } > { \c_zero_int }
      {
        \tl_set:Nx \l_@@_patch_param_text_tl
          { \bool_if:NTF #1 { [####1] } {  ####1  } }
        \tl_set:Nx \l_@@_patch_replacement_tl
          { \bool_if:NTF #1 { [####1] } { {####1} } }
        \int_step_inline:nnn { 2 } { \l_@@_patch_num_args_int }
          {
            \tl_put_right:Nn \l_@@_patch_param_text_tl    { ## ####1 }
            \tl_put_right:Nn \l_@@_patch_replacement_tl { { ## ####1 } }
          }
      }
      {
        \tl_clear:N \l_@@_patch_param_text_tl
        \tl_clear:N \l_@@_patch_replacement_tl
      }
%    \end{macrocode}
%   Finally, before redefining, we need to also get the prefixes used
%   when defining the command.  Here we ensure that the \tn{escapechar}
%   is printable, otherwise a macro defined with prefixes
%   |\protected \long| will have it \tn{meaning} printed as
%   |protectedlong|, making life unnecessarily complicated.  Here the
%   \tn{escapechar} is changed to |/|, then we loop between pairs of
%   |/|\ldots|/| extracting the prefixes.
%    \begin{macrocode}
    \group_begin:
      \int_set:Nn \tex_escapechar:D { `\/ }
      \use:x
        {
    \group_end:
    \tl_set:Nx \exp_not:N \l_@@_patch_prefixes_tl
      { \exp_not:N \@@_make_prefixes:w \cs_prefix_spec:N #2 / / }
        }
%    \end{macrocode}
%   Now that all the needed tools are ready, without further ado we'll
%   redefine the command |#2|.  The definition uses the prefixes
%   gathered in \cs{l_@@_patch_prefixes_tl}, a primitive \cs{tex_def:D}
%   to avoid adding extra prefixes, and the \meta{parameter text} from
%   \cs{l_@@_patch_param_text_tl}.
%
%   Then finally, in the body of the definition, we insert
%   \hook{cmd/\#3/before} if |#4| is |before|, the code of the
%   command expanded once grabbing the parameters in
%   \cs{l_@@_patch_replacement_tl}, and \hook{cmd/\#3/after} if |#4| is
%   |after|.
%    \begin{macrocode}
    \use:x
      {
        \l_@@_patch_prefixes_tl \tex_def:D
            \exp_not:N #2 \exp_not:V \l_@@_patch_param_text_tl
          {
            \str_if_eq:nnT {#4} { before }
              { \exp_not:N \UseHook { cmd / #3 / #4 } }
            \exp_args:No \exp_not:o
              { \exp_after:wN #2 \l_@@_patch_replacement_tl }
            \str_if_eq:nnT {#4} { after }
              { \exp_not:N \UseHook { cmd / #3 / #4 } }
          }
      }
  }
%    \end{macrocode}
%
%   Here's the auxiliary that makes the prefix control sequences for the
%   redefinition.  Each item has to be \cs{tl_trim_spaces:n}'d because
%   the last item (and not any other) has a trailing space.
%    \begin{macrocode}
\cs_new:Npn \@@_make_prefixes:w / #1 /
  {
    \tl_if_empty:nF {#1}
      {
        \exp_not:c { tex_ \tl_trim_spaces:n {#1} :D }
        \@@_make_prefixes:w /
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
%
%
%
%
%
% \subsubsection{Patching by retokenization}
%
% At this point we've drained the possibilities of patching a command by
% expansion-and-redefinition, so we have to resort to patching by
% retokenizing the command.  Patching by retokenization is done by
% getting the \tn{meaning} of the command, doing the necessary
% manipulations on the generated string, and the retokenizing that again
% by using \tn{scantokens}.
%
% Patching by retokenization is definitely a riskier business, because
% it relies that the tokens printed by \tn{meaning} produce the exact
% same tokens as the ones in the original definition.  That is, the
% catcode régime must be exactly(ish) the same, and there is no way of
% telling except by trial and error.
%
% \begin{macro}{\@@_retokenize_patch:Nnn}
%   This is the macro that will control the whole process.  First we'll
%   try out one final, rather trivial case, of a command with no
%   arguments;  that is, a token list.  This case can be patched with
%   the expand-and-redefine routine but it has to be the very last case
%   tested for, because most (all?) robust commands start with a
%   top-level macro with no arguments, so testing this first would
%   short-circuit \tn{robust@command@act} and the top-level macros would
%   be incorrectly patched.  In that case, we just check if the
%   \cs{cs_argument_spec:N} is empty, and call
%   \cs{@@_patch_expand_redefine:NNnn}.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_retokenize_patch:Nnn #1 #2 #3
  {
    \@@_patch_debug:x { ..~command~can~only~be~patched~by~rescanning }
    \str_if_eq:eeTF { \cs_argument_spec:N #1 } { }
      { \@@_patch_expand_redefine:NNnn \c_false_bool #1 {#2} {#3} }
      {
%    \end{macrocode}
%
%   Otherwise, we start the actual patching by retokenization job.  The
%   code calls \cs{@@_try_patch_with_catcodes:Nnnnw} with a different
%   catcode setting:
%   \begin{itemize}
%     \item The current catcode setting;
%     \item Switching the catcode of |@|;
%     \item Switching the \pkg{expl3} syntax on or off;
%     \item Both of the above.
%   \end{itemize}
%
%   If patching succeeds, \cs{@@_try_patch_with_catcodes:Nnnnw} has the
%   side-effect of patching the macro |#1| (which may be an internal
%   from the command whose name is~|#2|).
%    \begin{macrocode}
        \tl_set:Nx \l_@@_tmpa_tl
          {
            \int_compare:nNnTF { \char_value_catcode:n {`\@ } } = { 12 }
              { \exp_not:N \makeatletter } { \exp_not:N \makeatother }
          }
        \tl_set:Nx \l_@@_tmpb_tl
          {
            \bool_if:NTF \l__kernel_expl_bool
              { \ExplSyntaxOff } { \ExplSyntaxOn }
          }
        \use:x
          {
            \exp_not:N \@@_try_patch_with_catcodes:Nnnnw
                \exp_not:n { #1 {#2} {#3} }
              { \prg_do_nothing: }
              { \exp_not:V \l_@@_tmpa_tl } % @
              { \exp_not:V \l_@@_tmpb_tl } % _:
              {
                \exp_not:V \l_@@_tmpa_tl   % @
                \exp_not:V \l_@@_tmpb_tl   % _:
              }
          }
              \q_recursion_tail \q_recursion_stop
%    \end{macrocode}
%
%   If no catcode setting succeeds, give up and raise an error.  The
%   command isn't changed in any way in that case.
%    \begin{macrocode}
          {
            \msg_error:nnxx { hooks } { cant-patch }
              { \c_backslash_str #2 } { retok }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}{\@@_try_patch_with_catcodes:Nnnnw}
%   This function is a simple wrapper around
%   \cs{@@_cmd_if_scanable:NnTF} and \cs{@@_patch_retokenize:Nnnn} if
%   the former returns \meta{true}, plus some debug messages.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_try_patch_with_catcodes:Nnnnw #1 #2 #3 #4
  {
    \quark_if_recursion_tail_stop_do:nn {#4} { \use:n }
    \@@_patch_debug:x { ++~trying~to~patch~by~retokenization }
    \@@_cmd_if_scanable:NnTF {#1} {#4}
      {
        \@@_patch_debug:x { ++~macro~can~be~retokenized~cleanly }
        \@@_patch_debug:x { ==~retokenizing~macro~now }
        \@@_patch_retokenize:Nnnn #1 {#2} {#3} {#4}
        \use_i_delimit_by_q_recursion_stop:nw \use_none:n
      }
      {
        \@@_patch_debug:x { --~macro~cannot~be~retokenized~cleanly }
        \@@_try_patch_with_catcodes:Nnnnw #1 {#2} {#3}
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
%
% \begin{macro}[int]{\kerneltmpDoNotUse}
%   This is an oddity required to be safe (as safe as reasonably
%   possible) when patching the command.  The entirety of
%   \begin{quote}
%     \meta{prefixes} \tn{def} \meta{cs} \meta{parameter text}
%       |{|\meta{replacement text}|}|
%   \end{quote}
%   will go through \tn{scantokens}.  The \meta{parameter text} and
%   \meta{replacement text} are what we are trying to retokenize, so not
%   much worry there.  The other items, however, should ``just work'',
%   so some care is needed to not use too fancy catcode settings.
%   Therefore we can't use an \pkg{expl3}-named macro for \meta{cs}, nor
%   the \pkg{expl3} versions of \tn{def} or the \meta{prefixes}.
%   That is why the definitions that will eventually go into
%   \tn{scantokens} will use the oddly (but hopefully clearly)-named
%   \cs{kerneltmpDoNotUse}:
%    \begin{macrocode}
\cs_new_eq:NN \kerneltmpDoNotUse !
%    \end{macrocode}
%   \phoinline{Maybe this can be avoided by running the \meta{parameter text}
%     and the \meta{replacement text} separately through \tn{scantokens}
%     and then putting everything together at the end.}
% \end{macro}
%
%
%
% \begin{macro}{\@@_patch_required_catcodes:}
%   Here are the catcode settings that are \emph{mandatory} when
%   retokenizing commands.  These are the minimum necessary settings to
%   perform the definitions:  they identify control sequences, which
%   must be escaped with |\|$_0$, delimit the definition with |{|$_1$
%   and |}|$_2$, and mark parameters with |#|$_6$.  Everything else may
%   be changed, but not these.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_required_catcodes:
  {
    \char_set_catcode_escape:N \\
    \char_set_catcode_group_begin:N \{
    \char_set_catcode_group_end:N \}
    \char_set_catcode_parameter:N \#
    % \int_set:Nn \tex_endlinechar:D { -1 }
    % \int_set:Nn \tex_newlinechar:D { -1 }
  }
%    \end{macrocode}
%   \phoinline{\pkg{etoolbox} sets the \tn{endlinechar} and \tn{newlinechar}
%     when patching, but as far as I tested these didn't make much of
%     a difference, so I left them out for now.  Maybe
%     \tn{newlinechar}|=-1| avoids a space token being added after the
%     definition.}
%   \phoinline{If the patching is split by \meta{parameter text} and
%     \meta{replacement text}, then only \# will have to stay in that
%     list.}
%   \phoinline{Actually now that we patch
%     \texttt{\cs{UseHook}\{cmd/foo/before\}}, all the tokens there need
%     to have the right catcodes, so this list now includes all
%     lowercase letters, U and H, the slash, and whatever characters in
%     the command name\ldots sigh\ldots}
% \end{macro}
%
%
%
%
% \begin{macro}[TF]{\@@_cmd_if_scanable:Nn}
%   Here we'll do a quick test if the command being patched can in fact
%   be retokenized with the specific catcode setting without changing
%   in meaning.  The test is straightforward:
%   \begin{enumerate}
%     \item apply \tn{meaning} to the command;
%     \item split the \meta{prefixes}, \meta{parameter text} and
%       \meta{replacement text} and arrange them as
%       \begin{quote}
%         \meta{prefixes}\tn{def}\cs{kerneltmpDoNotUse}%^^A
%           \meta{parameter text}|{|\meta{replacement text}|}|
%       \end{quote}
%     \item rescan that with the given catcode settings, and do
%       the definition; then finally
%     \item compare \cs{kerneltmpDoNotUse} with the original command.
%   \end{enumerate}
%   If both are \tn{ifx}-equal, the command can be safely patched.
%    \begin{macrocode}
\prg_new_protected_conditional:Npnn \@@_cmd_if_scanable:Nn #1 #2 { TF }
  {
    \cs_set_eq:NN \kerneltmpDoNotUse \scan_stop:
    \cs_set_eq:NN \@@_tmp:w \scan_stop:
    \use:x
      {
        \cs_set:Npn \@@_tmp:w
            ####1 \tl_to_str:n { macro: } ####2 -> ####3 \s_@@_mark
          { ####1 \def \kerneltmpDoNotUse ####2   {####3} }
        \tl_set:Nx \exp_not:N \l_@@_tmpa_tl
          { \exp_not:N \@@_tmp:w \token_to_meaning:N #1 \s_@@_mark }
      }
    \tl_rescan:nV { #2 \@@_patch_required_catcodes: } \l_@@_tmpa_tl
    \token_if_eq_meaning:NNTF #1 \kerneltmpDoNotUse
      { \prg_return_true: }
      { \prg_return_false: }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}{\@@_patch_retokenize:Nnnn}
%   Then, if \cs{@@_cmd_if_scanable:NnTF} returned true, we can go on
%   and patch the command.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_retokenize:Nnnn #1 #2 #3 #4
  {
%    \end{macrocode}
%   Start off by making some things \tn{relax} to avoid lots of
%   \tn{noexpand} below.
%    \begin{macrocode}
    \cs_set_eq:NN \kerneltmpDoNotUse \scan_stop:
    \cs_set_eq:NN \@@_tmp:w \scan_stop:
    \use:x
      {
%    \end{macrocode}
%   Now we'll define \cs{@@_tmp:w} such that it splits the \tn{meaning}
%   of the macro (|#1|) into its three parts:
%   \begin{enumerate}
%     \def\makelabel#1{\texttt{\#\#\#\##1}}
%     \item \meta{prefixes}
%     \item \meta{parameter text}
%     \item \meta{replacement text}
%   \end{enumerate}
%   and arrange that a complete definition, then place the |before|
%   or |after| hooks around the \meta{replacement text}:
%   accordingly.
%    \begin{macrocode}
        \cs_set:Npn \@@_tmp:w
            ####1 \tl_to_str:n { macro: } ####2 -> ####3 \s_@@_mark
          {
            ####1 \def \kerneltmpDoNotUse ####2
              {
                \str_if_eq:nnT {#3} { before }
                  { \token_to_str:N \UseHook { cmd / #2 / #3 } }
                ####3
                \str_if_eq:nnT {#3} { after }
                  { \token_to_str:N \UseHook { cmd / #2 / #3 } }
              }
          }
%    \end{macrocode}
%   Now we just have to get the \tn{meaning} of the command being
%   patched and pass it through the meat grinder above.
%    \begin{macrocode}
        \tl_set:Nx \exp_not:N \l_@@_tmpa_tl
          { \exp_not:N \@@_tmp:w \token_to_meaning:N #1 \s_@@_mark }
      }
%    \end{macrocode}
%   Now rescan with the given catcode settings (overridden by the
%   \cs{@@_patch_required_catcodes:}), and implicitly (by using the
%   rescanned token list) carry out the definition from above.
%    \begin{macrocode}
    \tl_rescan:nV { #4 \@@_patch_required_catcodes: } \l_@@_tmpa_tl
%    \end{macrocode}
%   And to close, copy the newly-defined command into the old name and
%   the patching is finally completed:
%    \begin{macrocode}
    \cs_set_eq:NN #1 \kerneltmpDoNotUse
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Messages}
%
%    \begin{macrocode}
\msg_new:nnnn { hooks } { wrong-cmd-hook }
  {
    Generic~hook~`cmd/#1/#2'~is~invalid.
%    The~hook~should~be~`cmd/#1/before'~or~`cmd/#1/after'.
  }
  {
    You~tried~to~add~a~generic~hook~to~command~\iow_char:N \\#1,~but~`#2'~
    is~an~invalid~component.~Only~`before'~or~`after'~are~allowed.
  }
\msg_new:nnnn { hooks } { cant-patch }
  {
    Generic~hooks~cannot~be~added~to~'#1'.
  }
  {
    You~tried~to~add~a~hook~to~'#1',~but~LaTeX~was~unable~to~
    patch~the~command~because~it~\@@_unpatchable_cases:n {#2}.
  }
\cs_new:Npn \@@_unpatchable_cases:n #1
  {
    \str_case:nn {#1}
      {
        { undef } { doesn't~exist }
        { macro } { is~not~a~macro }
        { expl3 } { is~a~private~expl3~macro }
        { retok } { can't~be~retokenized~cleanly }
      }
  }
%    \end{macrocode}
%
%
%    \begin{macrocode}
%<latexrelease>\IncludeInRelease{0000/00/00}{ltcmdhooks}%
%<latexrelease>                 {The~hook~management~system~for~commands}
%<latexrelease>
%    \end{macrocode}
%    The command \cs{@@_cmd_begindocument_code:} is used in an
%    internal hook, so we need to make sure it has a harmless
%    definition after rollback as that will not remove it from the
%    kernel hook.
%    \begin{macrocode}
%<latexrelease>\cs_set_eq:NN \@@_cmd_begindocument_code: \prg_do_nothing:
%<latexrelease>
%<latexrelease>\EndModuleRelease
\ExplSyntaxOff
%</2ekernel|latexrelease>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=>
%    \end{macrocode}
%
% \Finale
%