summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/morewrites/morewrites.dtx
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/latex/contrib/morewrites/morewrites.dtx
Initial commit
Diffstat (limited to 'macros/latex/contrib/morewrites/morewrites.dtx')
-rw-r--r--macros/latex/contrib/morewrites/morewrites.dtx1289
1 files changed, 1289 insertions, 0 deletions
diff --git a/macros/latex/contrib/morewrites/morewrites.dtx b/macros/latex/contrib/morewrites/morewrites.dtx
new file mode 100644
index 0000000000..6e52eff394
--- /dev/null
+++ b/macros/latex/contrib/morewrites/morewrites.dtx
@@ -0,0 +1,1289 @@
+% \iffalse meta-comment
+%
+%% File: morewrites.dtx Copyright (C) 2011-2018 Bruno Le Floch
+%%
+%% 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
+%%
+%% http://www.latex-project.org/lppl.txt
+%%
+%% This work has the LPPL maintenance status 'maintained'
+%% and the current maintainer is Bruno Le Floch.
+%% -----------------------------------------------------------------------
+%
+%<*driver>
+\RequirePackage{morewrites}
+\ExplSyntaxOn
+\prg_replicate:nn { 300 } { \newwrite \foo }
+\ExplSyntaxOff
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{The \textsf{morewrites} package: \\
+% Always room for a new \tn{write}}
+% \author{Bruno Le Floch}
+% \date{2018/12/29}
+%
+% \maketitle
+% \tableofcontents
+%
+% \begin{documentation}
+%
+% \section{\pkg{morewrites} documentation}
+%
+% This \LaTeX{} package is a solution for the error
+% \enquote{no room for a new \tn{write}},
+% which occurs when a document reserves too many streams to write data
+% to various auxiliary files.
+% It is in principle possible to rewrite other packages so that they
+% are less greedy on resources, but that is often unpractical for the
+% end-user. Instead, \pkg{morewrites} hooks at the lowest level (\TeX{}
+% primitives).
+%
+% Simply add the line |\usepackage{morewrites}| near the beginning of
+% your \LaTeX{} file's preamble: the \enquote{no room for a new
+% \tn{write}} error should vanish. If it does not, please
+% contact me so that I can correct the problem. This can be done by
+% posting a question on the \url{tex.stackexchange.com} question and
+% answers website, logging an issue on GitHub
+% (\url{https://github.com/blefloch/latex-morewrites}), or emailing me a
+% minimal file showing the problem.
+%
+% Notes.
+% \begin{itemize}
+% \item This package loads the \pkg{expl3} package, hence the
+% \pkg{l3kernel} bundle needs to be up to date.
+% \item This package uses an auxiliary file, \meta{job~name}|.mw|,
+% which can safely be deleted. Versions from 2015 and later will only
+% use the auxiliary file if it is originally empty, to avoid
+% destroying data (such as |.mw| files used by Maple). This means
+% that |.mw| files generated by versions before 2015 should be
+% deleted by hand.
+% \item \LuaTeX{} allows $128$ \tn{write} streams, so this package does
+% nothing (with a warning) when used with \LuaTeX{}.
+% \end{itemize}
+%
+% \subsection{Commands defined or altered by \pkg{morewrites}}
+%
+% \begin{function}[added = 2014-07-26]{\morewritessetup}
+% \begin{syntax}
+% \cs{morewritessetup} \Arg{key--value list}
+% \end{syntax}
+% Sets the options described by the \meta{key--value list}.
+% \end{function}
+%
+% \begin{function}[added = 2017-04-10]{allocate}
+% \begin{syntax}
+% \cs{morewritessetup} |{| |allocate| |=| \meta{integer} |}|
+% \end{syntax}
+% Sets to (at least) \meta{integer} the number of \tn{write} streams
+% allocated to the inner workings of \pkg{morewrites}. By default
+% this is zero but increasing this value to 10 (or so) may speed up
+% \pkg{morewrites}.
+% \end{function}
+%
+% \begin{function}[added = 2014-07-26, updated = 2015-08-01]{file}
+% \begin{syntax}
+% \cs{morewritessetup} |{| |file| |=| \meta{file name} |}|
+% \end{syntax}
+% Sets (globally) the name of the file which will be used by internal
+% processes of \pkg{morewrites}. The file name is \tn{jobname}|.mw|
+% by default (technically, \cs{c_sys_jobname_str}|.mw|). Contrarily
+% to earlier versions of \pkg{morewrites} non-empty files will not be
+% overwritten; this design choice may lead to unwanted |.mw| files
+% remaining.
+% \end{function}
+%
+% \begin{function}[updated = 2015-08-01]{\newwrite}
+% This macro is redefined by \pkg{morewrites}. Since \pkg{morewrites}
+% allows more than~$16$ write streams, it removes the corresponding
+% restrictions in \tn{newwrite}.
+% \begin{texnote}
+% The revised \tn{newwrite} allocate stream numbers starting at
+% $19$. This might break some code that expects stream numbers to
+% be less than $16$.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[updated = 2015-08-01]{\immediate}
+% This primitive is altered by \pkg{morewrites}, to detect a following
+% \tn{write} or \tn{openout} or \tn{closeout} and perform the
+% appropriate action.
+% \end{function}
+%
+% \begin{function}[updated = 2017-04-20]{\openout, \write, \closeout}
+% These three primitives are altered by \pkg{morewrites} so that they
+% accept stream numbers outside the normal range $[0,15]$ and
+% open/write/close files as appropriate.
+% \begin{texnote}
+% System calls using \tn{write}|18| are detected and forwarded to the engine.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}{\shipout}
+% This primitive is altered by \pkg{morewrites} to ensure that delayed
+% \tn{openout}, \tn{write} and \tn{closeout} commands are performed at
+% \tn{shipout} time, and in the correct order.
+% \end{function}
+%
+% \subsection{Known deficiencies and open questions}
+%
+% See the bug tracker \url{https://github.com/blefloch/latex-morewrites/issues/}
+% for a list of issues with \pkg{morewrites}.
+%
+% The package code is not good \pkg{expl3} code. \emph{Do not take this
+% package as an example of how to code with \pkg{expl3}; go and see
+% Joseph Wright's \pkg{siunitx} instead.} It uses
+% \cs[no-index]{\ldots{}:D} primitives directly (the |:D| stands for
+% \enquote{do not use}). This is unavoidable in order to hook into the
+% primitives \tn{immediate}, \tn{write}, \emph{etc.\@} and to keep a
+% very strong control on what every command does.
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{morewrites} implementation}
+%
+%<*package>
+% \begin{macrocode}
+\RequirePackage {expl3} [2018/02/21]
+\RequirePackage {primargs} [2018/12/29]
+\ProvidesExplPackage
+ {morewrites} {2018/12/29} {} {Always room for a new write}
+% \end{macrocode}
+%
+% Quit early under \LuaTeX{}.
+% \begin{macrocode}
+\sys_if_engine_luatex:T
+ {
+ \cs_new_protected:Npn \morewritessetup #1 { }
+ \msg_new:nnn { morewrites } { luatex }
+ { The~morewrites~package~is~unnecessary~in~LuaTeX. }
+ \msg_warning:nn { morewrites } { luatex }
+ \tex_endinput:D
+ }%
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=morewrites>
+% \end{macrocode}
+%
+% \subsection{Overview of relevant \TeX{} facts}
+%
+% The aim of the \pkg{morewrites} package is to lift \TeX{}'s
+% restriction of only having $16$ files open for writing at the same
+% time. This requires patching the primitives \tn{immediate},
+% \tn{openout}, \tn{write}, \tn{closeout}, and \tn{shipout}, and the
+% macro \tn{newwrite} present in plain \TeX{} and \LaTeXe{}.
+%
+% Note that doing the same for \tn{read} streams is impossible due to
+% the \tn{ifeof} primitive: that primitive cannot be replaced by a macro
+% without breaking nesting of conditionals.
+%
+% The \pkg{morewrites} package should be loaded as early as possible, so
+% that any package loaded later uses the redefined macros instead of the
+% primitives. However, the format (plain \TeX{} or \LaTeXe{}) and the
+% \pkg{expl3} programming language are always loaded before
+% \pkg{morewrites}, and their interaction must be carefully monitored.
+%
+% Henceforth, ``\TeX{} stream'' will refer to stream numbers in the
+% range $[0,15]$ provided to \TeX{}'s write primitives, while
+% ``user stream'' will denote stream numbers in $[0,15]\cup[19,\infty)$
+% manipulated by the redefined \tn{openout}, \tn{write}, \tn{closeout},
+% and \tn{newwrite}. A user stream in $[0,15]$ (reserved by \LaTeXe{}
+% or allocated by \pkg{expl3}) is mapped to the same \TeX{} stream
+% number, while a user stream in $[19,\infty)$ is mapped to a \TeX{}
+% stream according to the property list (with integer keys and values)
+% \cs{l_@@_write_prop}. Stream numbers $16$,
+% $17$ and $18$ are unused because \tn{write}16 is often used to write
+% to the terminal, and \tn{write}18 sends its argument to a shell.
+%
+% The primitives \tn{openout}, \tn{write}, and \tn{closeout} expect to
+% be followed by an \meta{integer}, normally in the range $[0,15]$, then
+% some further arguments.
+% \begin{quote}
+% \tn{openout} \meta{integer} \meta{equals} \meta{file name} \\
+% \tn{write} \meta{integer} \meta{filler} \meta{general text} \\
+% \tn{closeout} \meta{integer}
+% \end{quote}
+% All of the primitives above perform full expansion of all tokens when
+% looking for their operands.
+% \begin{itemize}
+% \item \meta{integer} denotes an integer in any form that \TeX{}
+% accepts as the right-hand side of a primitive integer assignment of
+% the form \tn{count}|0=|\meta{integer};
+% \item \meta{equals} is an arbitrary (optional) number of explicit or
+% implicit space characters, an optional explicit equal sign of
+% category other, and further (optional) explicit or implicit space
+% characters;
+% \item \meta{file name} is an arbitrary sequence of explicit or implicit
+% characters with arbitrary category codes (except active characters,
+% which are expanded before reaching \TeX{}'s mouth), ending either
+% with a space character (character code $32$, arbitrary non-active
+% category code, explicit or implicit), which is removed, or with a
+% non-expandable token, with some care needed for the case of a
+% \tn{notexpanded:} expandable token;
+% \item \meta{filler} is an arbitrary combination of tokens whose
+% meaning is \tn{relax} or whose category code is $10$;
+% \item \meta{general text} is formed of braced tokens, starting with an
+% explicit or implicit begin-group character, and ending with the
+% matching explicit end-group character (both with any character
+% code), with an equal number of explicit begin-group and end-group
+% characters in between: this is precisely the right-hand side of an
+% assignment of the form \tn{toks}|0=|\meta{general text}.
+% \end{itemize}
+%
+% The \pkg{morewrites} package redefines these three control sequences
+% to expect a user stream number rather than a \TeX{} stream number as
+% the \meta{integer}, then map such a user stream to a \TeX{} stream to
+% call the primitive with the appropriate argument. The primitive
+% \tn{immediate} must also be redefined to detect \tn{openout},
+% \tn{write}, and \tn{closeout} and make them immediate, while still
+% working with other primitives that can be made immediate. Finally,
+% \tn{newwrite} must be patched to allocate stream numbers beyond $15$.
+%
+% A few comments on the behaviour of primitives concerning the
+% \meta{integer} (\TeX{} stream). The \tn{openout} primitive trigger
+% errors if the \meta{integer} is not in $[0,15]$. The primitive
+% \tn{write} outputs to the log if the \meta{integer} is negative, and
+% to the terminal if the \TeX{} stream is closed or greater than $15$,
+% with the exception of \tn{write}|18| which runs code in a shell. The
+% \tn{closeout} primitive triggers an error if the \meta{integer} is not
+% in $[0,15]$ and silently do nothing if the \TeX{} stream is not open,
+% with the exception of \tn{closeout}|18| which causes a segfault at
+% least in some versions.
+%
+% By default, \tn{openout}, \tn{write} and \tn{closeout} are recorded in
+% a whatsit node in the current list, and will be performed when the box
+% containing the whatsit node is sent to the final \texttt{pdf},
+% \emph{i.e.}, at \enquote{shipout} time. In particular, the
+% \meta{general text} for the \tn{write} primitive is expanded at
+% shipout time. This behaviour may be modified by putting
+% \tn{immediate} before any of these three primitives to force \TeX{} to
+% perform the action immediately instead of recording it in a whatsit
+% node.
+%
+% Since the \tn{openout}, \tn{write}, and \tn{closeout} primitives
+% operate at \tn{shipout} time, we will have to hook into this primitive
+% too. It expects to be followed by a box specification, for instance
+% \tn{box}\meta{integer} or \tn{hbox}\Arg{material to typeset}.
+%
+% Finally, the \tn{newwrite} macro expects one token as its argument,
+% and defines this token (with \tn{chardef}) to be an integer
+% corresponding to the first available (\TeX{}) write stream. This must
+% be extended to allocate higher (user) streams.
+%
+% \subsection{Preliminaries}
+%
+% \subsubsection{Copying some commands}
+%
+% \begin{macro}
+% {
+% \@@_tex_immediate:w ,
+% \@@_tex_openout:w ,
+% \@@_tex_write:w ,
+% \@@_tex_closeout:w ,
+% }
+% Aliases for the write-related primitives, to avoid
+% having |:D| throughout the code.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_tex_immediate:w \tex_immediate:D
+\cs_new_eq:NN \@@_tex_openout:w \tex_openout:D
+\cs_new_eq:NN \@@_tex_write:w \tex_write:D
+\cs_new_eq:NN \@@_tex_closeout:w \tex_closeout:D
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_tex_newwrite:N}
+% Copy \tn{newwrite} but making sure that it is
+% not \tn{outer}. This copy will not be affected by redefinitions
+% of \tn{newwrite} later on.
+% \begin{macrocode}
+\exp_args:NNf \cs_new_protected:Npn \@@_tex_newwrite:N
+ { \exp_args:NNc \exp_after:wN \exp_stop_f: { newwrite } }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Variants}
+%
+% \begin{macro}{\prop_gpop:NVNT, \prop_gput:NVx, \tl_gput_right:Nv}
+% We need these variants.
+% \begin{macrocode}
+\cs_generate_variant:Nn \prop_gpop:NnNT { NV }
+\cs_generate_variant:Nn \prop_gput:Nnn { NVx }
+\cs_generate_variant:Nn \tl_gput_right:Nn { Nv }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Variables}
+%
+% \begin{variable}{\l_@@_internal_tl}
+% Used for temporary scratch purposes.
+% \begin{macrocode}
+\tl_new:N \l_@@_internal_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\@@_tmp:w}
+% Used for temporary definitions.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_tmp:w ?
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_later_int}
+% The integer \cs{g_@@_later_int} labels the various
+% non-immediate operations in the order in which they appear in the
+% source. We can never reuse a number because there is no way to know
+% if a whatsit was recorded in a box register, which could be reused
+% in a shipped-out box:
+% \begin{quote}
+% \cs{vbox_set:Nn} \cs{l_my_box} \\
+% ~~|{| \cs{iow_shipout_x:Nn} \cs{c_term_iow} \Arg{text} |}|
+% \tn{shipout} \tn{copy} \cs{l_my_box}
+% \tn{shipout} \tn{copy} \cs{l_my_box}
+% \end{quote}
+% will print \meta{text} to the terminal twice.
+% \begin{macrocode}
+\int_new:N \g_@@_later_int
+% \end{macrocode}
+% \end{variable}
+%
+% ^^A todo: populate \g_@@_write_seq because we don't use \newwrite; this should be done in \morewritessetup
+% \begin{variable}{\g_@@_write_seq}
+% Keep track of \TeX{} stream numbers managed by \pkg{morewrites} that
+% are currently not in use as user streams.
+% \begin{macrocode}
+\seq_new:N \g_@@_write_seq
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_write_prop}
+% Map user streams to \TeX{} streams.
+% \begin{macrocode}
+\prop_new:N \g_@@_write_prop
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_write_file_prop}
+% Map user streams with no associated \TeX{} streams to file names.
+% \begin{macrocode}
+\prop_new:N \g_@@_write_file_prop
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_code_tl}
+% Stores the code to run after finding a user stream, in
+% \cs{@@_get_user:n}.
+% \begin{macrocode}
+\tl_new:N \l_@@_code_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_user_int, \l_@@_tstr_tl}
+% The user stream number following redefined primitives is stored in
+% \cs{l_@@_user_int} (see \cs{@@_get_user:N}). The corresponding
+% \TeX{} stream number is eventually stored in \cs{l_@@_tstr_tl} (a
+% token list).
+% \begin{macrocode}
+\int_new:N \l_@@_user_int
+\tl_new:N \l_@@_tstr_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_tstr_token}
+% This token is given as an argument to \cs{@@_tex_newwrite:N}.
+% \begin{macrocode}
+\cs_new_eq:NN \l_@@_tstr_token ?
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\s_@@}
+% A recognizable version of \cs{scan_stop:}. This is inspired
+% by\footnote{Historically, this might have happened the other way
+% around, since the author of this package is also on the \LaTeX3
+% Team.} scan marks (see the \pkg{l3quark} module of \LaTeX3), but
+% \cs{scan_new:N} is not used directly, since it is has been made
+% available in \LaTeX3 too recently.
+% \begin{macrocode}
+\cs_new_eq:NN \s_@@ \scan_stop:
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\g_@@_iow, \g_@@_ior}
+% The expansion that \tn{write} performs is impossible to emulate (in \XeTeX{} at least) with
+% anything else than \tn{write}. We will write on the stream
+% \cs{g_@@_iow} to the file \cs{g_@@_tmp_file_tl} and read back from
+% it in the stream \cs{g_@@_ior} for things to work properly.
+% Unfortunately, this means that the file is repeatedly opened and
+% closed, leaving a trace of that in the log.
+% \begin{macrocode}
+\newwrite \g_@@_iow
+\newread \g_@@_ior
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_tmp_file_tl, \g_@@_tmp_file_bool}
+% Temporary file used to do the correct expansion for each \tn{write}.
+% Boolean indicating whether we have already checked that the file can
+% be used by \pkg{morewrites}: before using a file, the
+% \pkg{morewrites} package now checks it is empty, so as to avoid
+% clobbering user data.
+% \begin{macrocode}
+\tl_new:N \g_@@_tmp_file_tl
+\bool_new:N \g_@@_tmp_file_bool
+\bool_gset_false:N \g_@@_tmp_file_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_group_level_int}
+% The group level when \tn{shipout} is called: this is used to
+% distinguish between explicit boxes and box registers.
+% \begin{macrocode}
+\int_new:N \g_@@_group_level_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_shipout_box}
+% The page to be shipped out.
+% \begin{macrocode}
+\box_new:N \g_@@_shipout_box
+% \end{macrocode}
+% \end{variable}
+%
+% \subsubsection{Helpers for auxiliary file}
+%
+% \begin{macro}{\@@_set_file:n}
+% Sets \cs{g_@@_tmp_file_tl} to the given value (initially
+% \cs{c_sys_jobname_str}|.mw|). Mark that the file has not been
+% checked.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_set_file:n #1
+ {
+ \bool_gset_false:N \g_@@_tmp_file_bool
+ \tl_gset:Nn \g_@@_tmp_file_tl {#1}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_empty_file:n}
+% Empties the file \cs{g_@@_tmp_file_tl} by opening it and closing it
+% right away. This is used when performing \tn{immediate}
+% \tn{openout}. It is also used to ensure the file used by
+% \pkg{morewrites} is left empty. We do this every time the auxiliary
+% file is used, in case that run ends with an error mid-document.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_empty_file:n #1
+ {
+ \@@_tex_immediate:w \@@_tex_openout:w
+ \g_@@_iow = #1 \scan_stop:
+ \@@_tex_immediate:w \@@_tex_closeout:w
+ \g_@@_iow
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[TF]{\@@_if_file_trivial:n}
+% True if the file does not exist, or if it is empty.
+% Only the \texttt{TF} variant is defined. We set
+% \cs{@@_tmp:w} to \cs{prg_return_true:} or \cs{prg_return_false:}
+% within the group and use it after cleaning up. The first
+% \texttt{eof} test is \texttt{true} if the file does not exist.
+% Then we read one line, the second \texttt{eof} test is
+% \texttt{true} if the file was empty (it is \texttt{false} if
+% the file contained anything, even a single space).
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_if_file_trivial:n #1 { TF }
+ {
+ \group_begin:
+ \tex_openin:D \g_@@_ior = #1 \scan_stop:
+ \if_eof:w \g_@@_ior
+ \cs_gset_eq:NN \@@_tmp:w \prg_return_true:
+ \else:
+ \int_set:Nn \tex_endlinechar:D { -1 }
+ \tex_readline:D \g_@@_ior to \l_@@_internal_tl
+ \if_eof:w \g_@@_ior
+ \cs_gset_eq:NN \@@_tmp:w \prg_return_true:
+ \else:
+ \cs_gset_eq:NN \@@_tmp:w \prg_return_false:
+ \fi:
+ \fi:
+ \tex_closein:D \g_@@_ior
+ \group_end:
+ \@@_tmp:w
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_chk_file:}
+% Check that the file \cs{g_@@_tmp_file_tl} does not exist or is blank.
+% If not, try the file name obtained by adding |.mw|.
+% This avoids clobbering files that the user would not want to lose.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_chk_file:
+ {
+ \@@_if_file_trivial:nTF { \g_@@_tmp_file_tl }
+ { \bool_gset_true:N \g_@@_tmp_file_bool }
+ {
+ \msg_warning:nnxx { morewrites } { file-exists }
+ { \g_@@_tmp_file_tl }
+ { \g_@@_tmp_file_tl .mw }
+ \tl_gput_right:Nn \g_@@_tmp_file_tl { .mw }
+ \@@_chk_file:
+ }
+ }
+\msg_new:nnnn { morewrites } { file-exists }
+ { File~'#1'~exists,~using~'#2'~instead. }
+ {
+ The~file~`#1'~exists~and~was~not~created~by~this~version~of~the~
+ `morewrites'~package.~Please~move~or~delete~that~file,~or~provide~
+ another~file~name~by~adding
+ \\ \\
+ \iow_indent:n { \iow_char:N\\morewritessetup~{~file~=~other-name~} }
+ \\ \\
+ to~your~source~file.~In~the~meantime,~the~file~`#2'~will~be~used.
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Parsing and other helpers}
+%
+% \begin{macro}{\@@_equals_file:N}
+% Most of the parsing for primitive arguments is done using
+% \pkg{primargs}, except for one case we care about: after its
+% \meta{number} argument, the \tn{openout} primitive expects an
+% \meta{equals} (optional spaces and |=|) and a \meta{file name}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_equals_file:N #1
+ {
+ \group_begin:
+ \tex_aftergroup:D \primargs_get_file_name:N
+ \tex_aftergroup:D #1
+ \primargs_remove_equals:N \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_get_user:n}
+% \pkg{primargs} commands only take \texttt{N}-type arguments, but we
+% often need to find an integer, save it in \cs{l_@@_user_int}, and
+% run some code |#1|. This is analogous to \cs{primargs_get_number:N}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_get_user:n #1
+ {
+ \tl_set:Nn \l_@@_code_tl {#1}
+ \tex_afterassignment:D \l_@@_code_tl
+ \l_@@_user_int =
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_user_to_tstr:NTF}
+% The goal is to go from a user stream \cs{l_@@_user_int} to a \TeX{}
+% stream \cs{l_@@_tstr_tl} (it defaults to the user stream). Streams
+% less than $19$ are not managed by \pkg{morewrites}: actual \TeX{}
+% streams in $[0,15]$; negative for writing to \texttt{log}; $16$,
+% $17$ for writing to terminal; $18$ for shell escape. Larger stream
+% numbers are looked up in the property list |#1|, namely
+% \cs{g_@@_write_prop}. If present, use the corresponding value as
+% the \TeX{} stream, otherwise run the \texttt{false} branch.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_user_to_tstr:NTF #1
+ {
+ \tl_set:NV \l_@@_tstr_tl \l_@@_user_int
+ \int_compare:nNnTF { \l_@@_user_int } < { 19 }
+ { \use_i:nn }
+ { \prop_get:NVNTF #1 \l_@@_user_int \l_@@_tstr_tl }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\l_@@_collect_next_int}
+% \begin{macro}
+% {
+% \@@_collect:x, \@@_collect_aux:Nn,
+% \@@_collect_aux:cf, \@@_collect_gput_right:N, \@@_collect_gput_right:c
+% }
+% When encountering very large \tn{write} statements we may need to
+% collect many lines. This can easily become an $O(n^2)$ task, and
+% here we make sure that it remains around $O(n\log n)$, with a large
+% constant unfortunately. Each of the token lists
+% \cs[no-index]{l_@@_$k$_tl} is empty or contains $2^k$ lines. As
+% lines accumulate, they move to token lists with larger values
+% of~$k$, and eventually all are combined. The integer
+% \cs{l_@@_collect_next_int} is (one plus) the maximal $k$ among
+% non-empty token lists.
+% \begin{macrocode}
+\int_new:N \l_@@_collect_next_int
+\cs_new_protected:Npn \@@_collect:x #1
+ {
+ \tl_set:Nx \l_@@_internal_tl {#1}
+ \@@_collect_aux:cf { l_@@_0_tl } { 1 }
+ }
+\cs_new_protected:Npn \@@_collect_aux:Nn #1#2
+ {
+ \int_compare:nNnT {#2} > \l_@@_collect_next_int
+ {
+ \tl_clear_new:N #1
+ \int_set:Nn \l_@@_collect_next_int {#2}
+ }
+ \tl_if_empty:NTF #1
+ { \tl_set_eq:NN #1 \l_@@_internal_tl }
+ {
+ \tl_put_left:No \l_@@_internal_tl {#1}
+ \tl_clear:N #1
+ \@@_collect_aux:cf { l_@@_#2_tl }
+ { \int_eval:n { #2 + 1 } }
+ }
+ }
+\cs_generate_variant:Nn \@@_collect_aux:Nn { cf }
+\cs_new_protected:Npn \@@_collect_gput_right:N #1
+ {
+ \int_compare:nNnF \l_@@_collect_next_int = 0
+ {
+ \int_decr:N \l_@@_collect_next_int
+ \tl_gput_right:Nv #1
+ {
+ l_@@_
+ \int_use:N \l_@@_collect_next_int
+ _tl
+ }
+ \@@_collect_gput_right:N #1
+ }
+ }
+\cs_generate_variant:Nn \@@_collect_gput_right:N { c }
+% \end{macrocode}
+% \end{macro}
+% \end{variable}
+%
+% \begin{macro}[EXP]{\@@_user_tl_name:n}
+% The name of a global token list variable holding the text of a given
+% user stream.
+% \begin{macrocode}
+\cs_new:Npn \@@_user_tl_name:n #1
+ { g_@@_iow_ \int_eval:n {#1} _tl }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Writing}
+%
+% We can hold on to material while a file is being written and only
+% write it in one go once the file closes, to avoid using a stream
+% throughout.
+%
+% At any given time, each user stream may point to an open \TeX{}
+% stream, given in \cs{g_@@_write_prop}, or may point to a token list
+% that will eventually be written to a file whose file name is stored in
+% \cs{g_@@_write_file_prop}, or may be closed.
+%
+% When a user stream points to a token list rather than a \TeX{} stream,
+% any material to be written must be written to our temporary file and
+% read back in to apply the same expansion as \tn{write} does.
+%
+% Another difficulty is that users may mix immediate and non-immediate
+% operations. The biggest difficulty comes from the possibility of
+% copying boxes containing delayed actions. If we ever produced a
+% whatsit \tn{write}\meta{number}\Arg{text} then the \TeX{} stream
+% \meta{number} would have to be reserved forever, as as copies of the
+% box containing this delayed actions may be shipped out at any later
+% point in the document.
+%
+% Each delayed action is thus saved in a separate numbered token list
+% and \tn{write}\cs{g_@@_iow}\Arg{number} is inserted instead of the
+% delayed action. At each \tn{shipout}, the stream \cs{g_@@_iow} is
+% opened, to catch the \meta{number} of each action that should be
+% performed at this \tn{shipout}.
+%
+% \subsubsection{Redefining \tn{immediate}}
+%
+% To accomodate the \tn{immediate} primitive, our versions of
+% \tn{openout}, \tn{write} and \tn{closeout} will take the form
+% \begin{quote}
+% \cs{s_@@} \cs{use_i:nn}
+% \quad \Arg{code for delayed action} \\
+% \quad \Arg{code for immediate action} \\
+% \meta{further code}
+% \end{quote}
+% The leading \cs{s_@@} allows the redefined \tn{immediate} to detect
+% these redefined primitives, and to run the \meta{code for immediate
+% action} instead of the \meta{code for delayed action} which is run by
+% default. In both cases, any \meta{further code} is run.
+%
+% \begin{macro}[updated = 2012-12-05]{\@@_immediate:w}
+% \begin{macro}{\@@_immediate_auxii:, \@@_immediate_auxiii:N}
+% \TeX{}'s \tn{immediate} primitive raises a flag which is cancelled
+% after \TeX{} sees a non-expandable token. We use
+% \cs{primargs_read_x_token:N} to find the next non-expandable token
+% then test for \tn{openout}, \tn{write}, and \tn{closeout}. More
+% precisely we test for the marker \cs{s_@@} and run the appropriate
+% code as described above. Otherwise we call the primitive, for cases
+% where the next token is \tn{pdfobj} or similar. This code performs
+% too much expansion for some nonsensical uses of \tn{noexpand} after
+% \tn{immediate}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_immediate:w
+ { \primargs_read_x_token:N \@@_immediate_auxii: }
+\cs_new_protected:Npn \@@_immediate_auxii:
+ {
+ \token_if_eq_meaning:NNTF \g_primargs_token \s_@@
+ { \@@_immediate_auxiii:N }
+ { \@@_tex_immediate:w }
+ }
+\cs_new_protected:Npn \@@_immediate_auxiii:N #1
+ { \str_if_eq:nnTF { #1 } { \s_@@ } { \use_iii:nnn } { #1 } }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsubsection{Immediate actions}
+%
+% The \tn{openout}, \tn{write}, and \tn{closeout} primitive can be
+% either delayed or immediate. In all cases they begin by looking for a
+% user stream. Here, we implement the immediate versions only.
+%
+% \begin{macro}{\@@_closeout:w, \@@_closeout_now:, \@@_closeout_now:nn}
+% In the immediate case \cs{@@_closeout_now:}, there are three cases.
+% The stream may point to a \TeX{} stream, in which case it is closed,
+% removed from \cs{g_@@_write_prop}, and put back in the list of
+% usable streams. The stream may point to a token list, in which case
+% that token list should be written to the appropriate file. The
+% stream may be closed, in which case nothing happens.
+% The auxiliary \cs{@@_closeout_now:nn} writes the material collected
+% so far for a given user stream |#1| to the file |#2|. This uses the
+% \TeX{} stream \cs{g_@@_iow}. The token list consists of multiple
+% \tn{immediate} \tn{write} \cs{g_@@_iow} \Arg{text} statements
+% because that is the only safe way to obtain new lines. We do not
+% remove the stream/file pair from \cs{g_@@_write_file_prop}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_closeout:w
+ {
+ \s_@@
+ \use_i:nn
+ { \@@_get_user:n { \@@_closeout_later: } }
+ { \@@_get_user:n { \@@_closeout_now: } }
+ }
+\cs_new_protected:Npn \@@_closeout_now:
+ {
+ \@@_user_to_tstr:NTF \g_@@_write_prop
+ {
+ \@@_tex_immediate:w \@@_tex_closeout:w \l_@@_tstr_tl \exp_stop_f:
+ \int_compare:nNnF { \l_@@_tstr_tl } = { \l_@@_user_int }
+ {
+ \prop_gremove:NV \g_@@_write_prop \l_@@_user_int
+ \seq_gput_left:NV \g_@@_write_seq \l_@@_tstr_tl
+ }
+ }
+ {
+ \prop_gpop:NVNT \g_@@_write_file_prop \l_@@_user_int \l_@@_internal_tl
+ { \@@_closeout_now:nn { \l_@@_user_int } { \l_@@_internal_tl } }
+ }
+ }
+\cs_new_protected:Npn \@@_closeout_now:nn #1#2
+ {
+ \@@_tex_immediate:w \@@_tex_openout:w \g_@@_iow = #2 \scan_stop:
+ \group_begin:
+ \int_set:Nn \tex_newlinechar:D { -1 }
+ \tl_use:c { \@@_user_tl_name:n {#1} }
+ \tl_gclear:c { \@@_user_tl_name:n {#1} }
+ \group_end:
+ \@@_tex_immediate:w \@@_tex_closeout:w \g_@@_iow
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_openout:w, \@@_openout_now:n}
+% In the immediate case find a file name, then allocate a \TeX{}
+% stream if possible, and otherwise point the user stream to a token
+% list. In all cases, close the stream to avoid losing any material
+% that \TeX{} would have written, and empty the file by opening and
+% closing it (actually that's done automatically by the primitive).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_openout:w
+ {
+ \s_@@
+ \use_i:nn
+ { \@@_get_user:n { \@@_openout_later:w } }
+ { \@@_get_user:n { \@@_equals_file:N \@@_openout_now:n } }
+ }
+\cs_new_protected:Npn \@@_openout_now:n #1
+ {
+ \@@_closeout_now:
+ \int_compare:nNnTF { \l_@@_user_int } < { 19 }
+ {
+ \@@_tex_immediate:w \@@_tex_openout:w \l_@@_user_int
+ = \tl_to_str:n {#1} \scan_stop:
+ }
+ {
+ \seq_gpop:NNTF \g_@@_write_seq \l_@@_tstr_tl
+ {
+ \prop_gput:NVV \g_@@_write_prop \l_@@_user_int \l_@@_tstr_tl
+ \@@_tex_immediate:w \@@_tex_openout:w \l_@@_tstr_tl \exp_stop_f:
+ = \tl_to_str:n {#1} \scan_stop:
+ }
+ {
+ \@@_empty_file:n {#1}
+ \prop_gput:NVx \g_@@_write_file_prop \l_@@_user_int
+ { \tl_to_str:n {#1} }
+ \tl_gclear_new:c { \@@_user_tl_name:n { \l_@@_user_int } }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_write:w, \@@_write_now:w, \@@_write_now:n}
+% In the immediate case we use \cs{@@_write_now_open:n} if the stream
+% points to a token list, and otherwise use the primitive, with the
+% dummy stream $16$ if closed (the text is then written to the
+% terminal).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_write:w
+ {
+ \s_@@
+ \use_i:nn
+ { \@@_get_user:n { \@@_write_later:w } }
+ { \@@_get_user:n { \@@_write_now:w } }
+ }
+\cs_new_protected:Npn \@@_write_now:w
+ {
+ \@@_user_to_tstr:NTF \g_@@_write_prop
+ { \@@_tex_immediate:w \@@_tex_write:w \l_@@_tstr_tl \exp_stop_f: }
+ { \primargs_get_general_text:N \@@_write_now:n }
+ }
+\cs_new_protected:Npn \@@_write_now:n
+ {
+ \prop_get:NVNTF \g_@@_write_file_prop \l_@@_user_int \l_@@_internal_tl
+ { \@@_write_now_open:n }
+ { \@@_tex_immediate:w \@@_tex_write:w 16 }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_write_now_open:n}
+% \begin{macro}{\@@_write_now_loop:}
+% Only \tn{write} itself can emulate how \tn{write} expands tokens,
+% because |#| don't have to be doubled, and because the
+% \tn{newlinechar} has to be changed to new lines. Hence, we start by
+% writing |#1| to a file (after making sure we are allowed to alter
+% it), yielding some lines. The lines are then read one at a time
+% using \eTeX{}'s \tn{readline} with \tn{endlinechar} set to $-1$ to
+% avoid spurious characters. Each line becomes a \tn{immediate}
+% \tn{write} statement added to a token list whose name is constructed
+% using \cs{@@_user_tl_name:n}. This token list will be called when
+% it is time to actually write to the file. At that time,
+% \tn{newlinechar} will be $-1$, so that writing each line will
+% produce no extra line.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_write_now_open:n #1
+ {
+ \bool_if:NF \g_@@_tmp_file_bool { \@@_chk_file: }
+ \@@_tex_immediate:w \@@_tex_openout:w
+ \g_@@_iow = \g_@@_tmp_file_tl \scan_stop:
+ \@@_tex_immediate:w \@@_tex_write:w
+ \g_@@_iow {#1}
+ \@@_tex_immediate:w \@@_tex_closeout:w
+ \g_@@_iow
+ \group_begin:
+ \int_set:Nn \tex_endlinechar:D { -1 }
+ \tex_openin:D \g_@@_ior = \g_@@_tmp_file_tl \scan_stop:
+ \@@_write_now_loop:
+ \tex_closein:D \g_@@_ior
+ \@@_collect_gput_right:c
+ { \@@_user_tl_name:n { \l_@@_user_int } }
+ \group_end:
+ \@@_empty_file:n { \g_@@_tmp_file_tl }
+ }
+\cs_new_protected:Npn \@@_write_now_loop:
+ {
+ \tex_readline:D \g_@@_ior to \l_@@_internal_tl
+ \ior_if_eof:NF \g_@@_ior
+ {
+ \@@_collect:x
+ {
+ \@@_tex_immediate:w \@@_tex_write:w
+ \g_@@_iow { \l_@@_internal_tl }
+ }
+ \@@_write_now_loop:
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+%
+% \subsubsection{Delayed actions}
+%
+% \begin{macro}{\@@_later:n, \@@_later_do:n}
+% Store the action to be done at shipout in a token list, and
+% non-immediately write the label \cs{g_@@_later_int} of the
+% output operation to the temporary file.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_later:n #1
+ {
+ \int_gincr:N \g_@@_later_int
+ \tl_const:cx
+ {
+ c_@@_later_
+ \int_use:N \g_@@_later_int
+ _tl
+ }
+ {
+ \int_set:Nn \exp_not:N \l_@@_user_int
+ { \exp_not:V \l_@@_user_int }
+ \exp_not:n {#1}
+ }
+ \exp_args:NNx \@@_tex_write:w \g_@@_iow
+ { `( \int_use:N \g_@@_later_int ) }
+ }
+\cs_new_protected:Npn \@@_later_do:n #1
+ { \tl_use:c { c_@@_later_ \int_eval:n {#1} _tl } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_closeout_later:}
+% If the user stream is a \TeX{} stream, use the primitive, otherwise
+% save \cs{@@_closeout_now:} for later.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_closeout_later:
+ {
+ \int_compare:nNnTF \l_@@_user_int < { 19 }
+ { \@@_tex_closeout:w \l_@@_user_int }
+ { \@@_later:n { \@@_closeout_now: } }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_openout_later:w, \@@_openout_later:n}
+% If the user stream is a \TeX{} stream use the primitive, otherwise
+% find a file name and call \cs{@@_openout_now:n} later.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_openout_later:w
+ {
+ \int_compare:nNnTF \l_@@_user_int < { 19 }
+ { \@@_tex_openout:w \l_@@_user_int }
+ { \@@_equals_file:N \@@_openout_later:n }
+ }
+\cs_new_protected:Npn \@@_openout_later:n #1
+ { \@@_later:n { \@@_openout_now:n {#1} } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_write_later:w, \@@_write_later:n, \@@_write_later_aux:n}
+% For \TeX{} streams use the primitive, otherwise find a general text
+% and save it for later; the auxiliary is very similar to
+% \cs{@@_write_now:w}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_write_later:w
+ {
+ \int_compare:nNnTF \l_@@_user_int < { 19 }
+ { \@@_tex_write:w \l_@@_user_int }
+ { \primargs_get_general_text:N \@@_write_later:n }
+ }
+\cs_new_protected:Npn \@@_write_later:n #1
+ { \@@_later:n { \@@_write_later_aux:n {#1} } }
+\cs_new_protected:Npn \@@_write_later_aux:n
+ {
+ \@@_user_to_tstr:NTF \g_@@_write_prop
+ { \@@_tex_immediate:w \@@_tex_write:w \l_@@_tstr_tl \exp_stop_f: }
+ { \@@_write_now:n }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Shipout business}
+%
+% In this section, we hook into the \tn{shipout} primitive, and redefine
+% it to first build a box with the material to ship out, then perform
+% \begin{quote}
+% \cs{@@_before_shipout:} \\
+% \meta{primitive shipout} \meta{collected box} \\
+% \cs{@@_after_shipout:}
+% \end{quote}
+%
+% Each delayed output operation has been replaced by \tn{write}
+% \cs{g_@@_iow} |{`(|\meta{operation number}|)}|. The delimiters we
+% chose to put around numbers must be at least two distinct characters
+% on the left (then \cs{tex_newlinechar:D} cannot be equal to the
+% delimiter), and at least one non-digit character on the right.
+%
+% \begin{macro}{\@@_before_shipout:}
+% Immediately before the shipout, we must open the writing stream
+% \cs{g_@@_iow} (after making sure we are allowed to alter the
+% auxiliary file).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_before_shipout:
+ {
+ \bool_if:NF \g_@@_tmp_file_bool { \@@_chk_file: }
+ \@@_tex_immediate:w \@@_tex_openout:w
+ \g_@@_iow = \g_@@_tmp_file_tl \scan_stop:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_after_shipout:}
+% \begin{macro}[rEXP]{\@@_after_shipout_loop:ww}
+% Immediately after all the \tn{write}s are performed, close the file,
+% then read the file with \tn{endlinechar} set to
+% \tn{newlinechar}\footnote{Note that the \tn{newlinechar} used by
+% \tn{write}s at \tn{shipout} time are those in effect when the page
+% is shipped out, \emph{i.e.}, just after the closing brace of the
+% \tn{shipout} construction, which is exactly where we have added
+% this hook.} to get exactly the original characters that have been
+% written, possibly with extra characters between |`(|\ldots{}|)|
+% groups. The file is then read with all the appropriate category
+% codes set up (no other character can appear in the file). The
+% looping auxiliary \cs{@@_after_shipout_loop:ww} extracts the
+% \meta{operation} numbers from the file, and makes a token list out
+% of those. This token list is then used in a mapping function to
+% perform the appropriate \tn{write} operations. Note that those
+% operations may reuse the file, so we have to fully parse the file
+% before moving on.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_after_shipout:
+ {
+ \@@_tex_immediate:w \@@_tex_closeout:w
+ \g_@@_iow
+ \group_begin:
+ \int_set_eq:NN \tex_endlinechar:D \tex_newlinechar:D
+ \char_set_catcode_other:n { \tex_endlinechar:D }
+ \tl_map_inline:nn { `(0123456789) }
+ { \char_set_catcode_other:n {`##1} }
+ \tex_everyeof:D { `() \exp_not:N }
+ \tl_set:Nx \l_@@_internal_tl
+ {
+ \exp_after:wN \@@_after_shipout_loop:ww
+ \tex_input:D \g_@@_tmp_file_tl \c_space_tl
+ }
+ \@@_empty_file:n { \g_@@_tmp_file_tl }
+ \exp_args:NNo
+ \group_end:
+ \tl_map_function:nN { \l_@@_internal_tl } \@@_later_do:n
+ }
+\cs_new:Npn \@@_after_shipout_loop:ww #1 `( #2 )
+ {
+ \tl_if_empty:nF {#2}
+ {
+ {#2}
+ \@@_after_shipout_loop:ww
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_shipout:w}
+% \begin{macro}{\@@_shipout_i:, \@@_shipout_ii:}
+% Grab the shipped out box using \tn{setbox} and regain control using
+% \tn{afterassignment}. There are two cases: either the box is given
+% as \tn{box} or \tn{copy} followed by a number, in which case
+% \cs{@@_shipout_i:} is inserted afterwards at the same group level,
+% or the box is given as \tn{hbox} (or \tn{vtop} and so on) and an
+% additional \tn{aftergroup} is needed to reach a point where we can
+% use the box saved in \cs{g_@@_shipout_box}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_shipout:w
+ {
+ \int_gset_eq:NN \g_@@_group_level_int \tex_currentgrouplevel:D
+ \tex_afterassignment:D \@@_shipout_i:
+ \tex_global:D \tex_setbox:D \g_@@_shipout_box
+ }
+\cs_new_protected:Npn \@@_shipout_i:
+ {
+ \int_compare:nNnTF { \g_@@_group_level_int }
+ = { \tex_currentgrouplevel:D }
+ { \@@_shipout_ii: }
+ { \tex_aftergroup:D \@@_shipout_ii: }
+ }
+\cs_new_protected:Npn \@@_shipout_ii:
+ {
+ \@@_before_shipout:
+ \@@_tex_shipout:w \tex_box:D \g_@@_shipout_box
+ \@@_after_shipout:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\shipout, \@@_tex_shipout:w}
+% The task is now to locate the shipout primitive, which may have been
+% renamed and hooked into by many different packages loaded before
+% \pkg{morewrites}. Any of those control sequences which are equal to
+% the primitive are redefined to do \cs{@@_shipout:w} instead. If the
+% primitive is not located at all, the fallback is to hook into the
+% control sequence \tn{shipout}.
+% \begin{macrocode}
+\cs_gset_protected:Npn \@@_tmp:w #1
+ {
+ \cs_if_exist:NF \@@_tex_shipout:w
+ { \cs_new_eq:NN \@@_tex_shipout:w #1 }
+ \cs_gset_eq:NN #1 \@@_shipout:w
+ }
+\tl_map_inline:nn
+ {
+ \xyrealshipout@
+ \org@shipout
+ \PDFSYNCship@ut@ld
+ \CROP@shipout
+ \@soORI
+ \tex_shipout:D
+ \zwpl@Hship
+ \o@shipout@TP
+ \LL@shipout
+ \Shipout
+ \GXTorg@shipout
+ \AtBegShi@OrgShipout
+ \AtBeginShipoutOriginalShipout
+ \minidocument@orig@shipout
+ \shipout
+ }
+ {
+ \str_if_eq:eeT
+ { \cs_meaning:N #1 }
+ { \token_to_str:N \shipout }
+ { \@@_tmp:w #1 }
+ }
+\cs_if_exist:NF \@@_tex_shipout:w
+ {
+ \cs_new_eq:NN \@@_tex_shipout:w \shipout
+ \cs_gset_eq:NN \shipout \@@_shipout:w
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Hook at the very end}
+%
+% \begin{macro}{\@@_close_all:}
+% At the end of the document, close all the files.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_close_all:
+ {
+ \prop_map_inline:Nn \g_@@_write_prop
+ { \@@_tex_immediate:w \@@_tex_closeout:w ##2 \scan_stop: }
+ \prop_gclear:N \g_@@_write_prop
+ \prop_map_function:NN \g_@@_write_file_prop
+ \@@_closeout_now:nn
+ \prop_gclear:N \g_@@_write_file_prop
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_close_all_at_end:nw}
+% At the end of the run, we try very hard to put some material at the
+% \tn{@@end}, just in case some other very late code writes to files
+% that are not yet closed. This is tried at most $5$~times, to avoid
+% infinite loops in case two packages compete for that last place.
+% The four |@| become two after \pkg{l3docstrip}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_close_all_at_end:nw #1#2 \@@@@end
+ {
+ \int_compare:nNnTF {#1} > 0
+ { #2 \@@_close_all_at_end:nw { #1 - 1 } }
+ { \@@_close_all: #2 }
+ \@@@@end
+ }
+\AtEndDocument { \@@_close_all_at_end:nw { 5 } }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Redefining commands}
+%
+% \subsubsection{Modified \tn{newwrite}}
+%
+% \begin{variable}{\g_@@_alloc_write_int}
+% Counter to allocate user streams. Initialized to $18$ so that the
+% first user stream allocated by \pkg{morewrites} is $19$. Indeed,
+% $18$ is reserved for shell commands and packages may expect $16$ or
+% $17$ to write to the terminal.
+% \begin{macrocode}
+\int_new:N \g_@@_alloc_write_int
+\int_gset:Nn \g_@@_alloc_write_int { 18 }
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_newwrite:N}
+% Reimplementation of \tn{newwrite} but protected and using a counter
+% \cs{g_@@_alloc_write_int} instead of what \TeX{}/\LaTeXe{} use.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_newwrite:N #1
+ {
+ \int_gincr:N \g_@@_alloc_write_int
+ \int_set_eq:NN \allocationnumber \g_@@_alloc_write_int
+ \cs_undefine:N #1
+ \int_const:Nn #1 { \allocationnumber }
+ \wlog
+ {
+ \token_to_str:N #1
+ = \token_to_str:N \write \int_use:N \allocationnumber
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_allocate:n}
+% Raise to |#1| the number of \tn{write} streams allocated to \pkg{morewrites}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_allocate:n #1
+ {
+ \prg_replicate:nn
+ {
+ \int_max:nn { 0 }
+ {
+ (#1) - \seq_count:N \g_@@_write_seq
+ - \prop_count:N \g_@@_write_prop
+ }
+ }
+ {
+ \@@_tex_newwrite:N \l_@@_tstr_token
+ \seq_gput_right:NV \g_@@_write_seq \l_@@_tstr_token
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{User commands and keys}
+%
+% \begin{macro}[added = 2014-07-26]{\morewritessetup}
+% Set whatever keys the user passes to \cs{morewritessetup}.
+% \begin{macrocode}
+\cs_new_protected:Npn \morewritessetup #1
+ { \keys_set:nn { @@ } {#1} }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[added = 2014-07-26]{file}
+% Because of our use of |.initial:n|, this code must appear after
+% \cs{@@_set_file:n} is defined.
+% \begin{macrocode}
+\keys_define:nn { @@ }
+ {
+ allocate .code:n = \@@_allocate:n {#1} ,
+ file .code:n = \@@_set_file:n {#1} ,
+ file .initial:n = \c_sys_jobname_str .mw
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[updated = 2015-08-01]
+% {\immediate, \openout, \write, \closeout, \newwrite}
+% \begin{macrocode}
+\cs_gset_eq:NN \immediate \@@_immediate:w
+\cs_gset_eq:NN \openout \@@_openout:w
+\cs_gset_eq:NN \write \@@_write:w
+\cs_gset_eq:NN \closeout \@@_closeout:w
+\cs_gset_eq:NN \newwrite \@@_newwrite:N
+% \end{macrocode}
+% \end{macro}
+%
+%</package>
+%
+% \end{implementation}
+%
+% \clearpage
+% \PrintIndex