% \iffalse meta-comment % %% File: l3file.dtx Copyright (C) 1990-2015 The LaTeX3 Project %% %% 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 file is part of the "l3kernel bundle" (The Work in LPPL) %% and all files in that bundle must be distributed together. %% %% The released version of this bundle is available from CTAN. %% %% ----------------------------------------------------------------------- %% %% The development version of the bundle can be found at %% %% http://www.latex-project.org/svnroot/experimental/trunk/ %% %% for those people who are interested. %% %%%%%%%%%%% %% NOTE: %% %%%%%%%%%%% %% %% Snapshots taken from the repository represent work in progress and may %% not work or may contain conflicting material! We therefore ask %% people _not_ to put them into distributions, archives, etc. without %% prior consultation with the LaTeX3 Project. %% %% ----------------------------------------------------------------------- % %<*driver> \documentclass[full]{l3doc} % %<*driver|package> \GetIdInfo$Id: l3file.dtx 5627 2015-06-07 09:26:26Z bruno $ {L3 File and I/O operations} % %<*driver> \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \pkg{l3file} package\\ File and I/O operations^^A % \thanks{This file describes v\ExplFileVersion, % last revised \ExplFileDate.}^^A % } % % \author{^^A % The \LaTeX3 Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Released \ExplFileDate} % % \maketitle % % \begin{documentation} % % This module provides functions for working with external files. Some of these % functions apply to an entire file, and have prefix \cs{file_\ldots}, while % others are used to work with files on a line by line basis and have prefix % \cs{ior_\ldots} (reading) or \cs{iow_\ldots} (writing). % % It is important to remember that when reading external files \TeX{} will % attempt to locate them both the operating system path and entries in the % \TeX{} file database (most \TeX{} systems use such a database). Thus the % \enquote{current path} for \TeX{} is somewhat broader than that for other % programs. % % For functions which expect a \meta{file name} argument, this argument % may contain both literal items and expandable content, which should on % full expansion be the desired file name. Any active characters (as % declared in \cs{l_char_active_seq}) will \emph{not} be expanded, % allowing the direct use of these in file names. File names will be quoted % using |"| tokens if they contain spaces: as a result, |"| tokens are % \emph{not} permitted in file names. % % \section{File operation functions} % % \begin{variable}{\g_file_current_name_tl} % Contains the name of the current \LaTeX{} file. This variable % should not be modified: it is intended for information only. It % will be equal to \cs{c_job_name_tl} at the start of a \LaTeX{} % run and will be modified each time a file is read using % \cs{file_input:n}. % \end{variable} % % \begin{function}[TF, updated = 2012-02-10]{\file_if_exist:n} % \begin{syntax} % \cs{file_if_exist:nTF} \Arg{file name} \Arg{true code} \Arg{false code} % \end{syntax} % Searches for \meta{file name} using the current \TeX{} search % path and the additional paths controlled by % \cs{file_path_include:n}). % \end{function} % % \begin{function}[updated = 2012-02-10]{\file_add_path:nN} % \begin{syntax} % \cs{file_add_path:nN} \Arg{file name} \meta{tl var} % \end{syntax} % Searches for \meta{file name} in the path as detailed for % \cs{file_if_exist:nTF}, and if found sets the \meta{tl var} the % fully-qualified name of the file, \emph{i.e.}~the path and file name. % If the file is not found then the \meta{tl var} will contain the % marker \cs{q_no_value}. % \end{function} % % \begin{function}[updated = 2012-02-17]{\file_input:n} % \begin{syntax} % \cs{file_input:n} \Arg{file name} % \end{syntax} % Searches for \meta{file name} in the path as detailed for % \cs{file_if_exist:nTF}, and if found reads in the file as % additional \LaTeX{} source. All files read are recorded % for information and the file name stack is updated by this % function. An error will be raised if the file is not found. % \end{function} % % \begin{function}[updated = 2012-07-04]{\file_path_include:n} % \begin{syntax} % \cs{file_path_include:n} \Arg{path} % \end{syntax} % Adds \meta{path} to the list of those used to search when reading % files. The assignment is local. % The \meta{path} is processed in the same way as a \meta{file name}, % \emph{i.e.}, with \texttt{x}-type expansion except active % characters. Spaces are not allowed in the \meta{path}. % \end{function} % % \begin{function}[updated = 2012-07-04]{\file_path_remove:n} % \begin{syntax} % \cs{file_path_remove:n} \Arg{path} % \end{syntax} % Removes \meta{path} from the list of those used to search when reading % files. The assignment is local. % The \meta{path} is processed in the same way as a \meta{file name}, % \emph{i.e.}, with \texttt{x}-type expansion except active % characters. Spaces are not allowed in the \meta{path}. % \end{function} % % \begin{function}{\file_list:} % \begin{syntax} % \cs{file_list:} % \end{syntax} % This function will list all files loaded using \cs{file_input:n} % in the log file. % \end{function} % % \subsection{Input--output stream management} % % As \TeX{} is limited to $16$ input streams and $16$ output streams, direct % use of the streams by the programmer is not supported in \LaTeX3. Instead, an % internal pool of streams is maintained, and these are allocated and % deallocated as needed by other modules. As a result, the programmer should % close streams when they are no longer needed, to release them for other % processes. % % Note that I/O operations are global: streams should all be declared % with global names and treated accordingly. % % \begin{function}[added = 2011-09-26, updated = 2011-12-27] % {\ior_new:N, \ior_new:c, \iow_new:N, \iow_new:c} % \begin{syntax} % \cs{ior_new:N} \meta{stream} % \cs{iow_new:N} \meta{stream} % \end{syntax} % Globally reserves the name of the \meta{stream}, either for reading % or for writing as appropriate. The \meta{stream} is not opened until % the appropriate \cs{\ldots_open:Nn} function is used. Attempting to % use a \meta{stream} which has not been opened is an error, and the % \meta{stream} will behave as the corresponding \cs{c_term_\ldots}. % \end{function} % % \begin{function}[updated = 2012-02-10]{\ior_open:Nn, \ior_open:cn} % \begin{syntax} % \cs{ior_open:Nn} \meta{stream} \Arg{file name} % \end{syntax} % Opens \meta{file name} for reading using \meta{stream} as the % control sequence for file access. If the \meta{stream} was already % open it is closed before the new operation begins. The % \meta{stream} is available for access immediately and will remain % allocated to \meta{file name} until a \cs{ior_close:N} instruction % is given or the \TeX{} run ends. % \end{function} % % \begin{function}[added = 2013-01-12, TF]{\ior_open:Nn, \ior_open:cn} % \begin{syntax} % \cs{ior_open:NnTF} \meta{stream} \Arg{file name} \Arg{true code} \Arg{false code} % \end{syntax} % Opens \meta{file name} for reading using \meta{stream} as the % control sequence for file access. If the \meta{stream} was already % open it is closed before the new operation begins. The % \meta{stream} is available for access immediately and will remain % allocated to \meta{file name} until a \cs{ior_close:N} instruction % is given or the \TeX{} run ends. The \meta{true code} is then inserted % into the input stream. If the file is not found, no error is raised and % the \meta{false code} is inserted into the input stream. % \end{function} % % \begin{function}[updated = 2012-02-09]{\iow_open:Nn, \iow_open:cn} % \begin{syntax} % \cs{iow_open:Nn} \meta{stream} \Arg{file name} % \end{syntax} % Opens \meta{file name} for writing using \meta{stream} as the % control sequence for file access. If the \meta{stream} was already % open it is closed before the new operation begins. The % \meta{stream} is available for access immediately and will remain % allocated to \meta{file name} until a \cs{iow_close:N} instruction % is given or the \TeX{} run ends. Opening a file for writing will clear % any existing content in the file (\emph{i.e.}~writing is \emph{not} % additive). % \end{function} % % \begin{function}[updated = 2012-07-31] % {\ior_close:N, \ior_close:c, \iow_close:N, \iow_close:c} % \begin{syntax} % \cs{ior_close:N} \meta{stream} % \cs{iow_close:N} \meta{stream} % \end{syntax} % Closes the \meta{stream}. Streams should always be closed when % they are finished with as this ensures that they remain available % to other programmers. % \end{function} % % \begin{function}[updated = 2012-09-09]{\ior_list_streams:, \iow_list_streams:} % \begin{syntax} % \cs{ior_list_streams:} % \cs{iow_list_streams:} % \end{syntax} % Displays a list of the file names associated with each open % stream: intended for tracking down problems. % \end{function} % % \subsection{Reading from files} % % \begin{function}[added = 2012-06-24]{\ior_get:NN} % \begin{syntax} % \cs{ior_get:NN} \meta{stream} \meta{token list variable} % \end{syntax} % Function that reads one or more lines (until an equal number of left % and right braces are found) from the input \meta{stream} and stores % the result locally in the \meta{token list} variable. If the % \meta{stream} is not open, input is requested from the terminal. % The material read from the \meta{stream} will be tokenized by % \TeX{} according to the category codes in force when the function % is used. Note that any blank lines will be converted to the token % \cs{par}. Therefore, if skipping blank lines is requires a test such as % \begin{verbatim} % \ior_get:NN \l_my_stream \l_tmpa_tl % \tl_set:Nn \l_tmpb_tl { \par } % \tl_if_eq:NNF \l_tmpa_tl \l_tmpb_tl % ... % \end{verbatim} % may be used. Also notice that if multiple lines are read to match braces % then the resulting token list will contain \cs{par} tokens. As normal % \TeX{} tokenization is in force, any lines which do not end in a comment % character (usually |%|) will have the line ending converted to a space, % so for example input % \begin{verbatim} % a b c % \end{verbatim} % will result in a token list |a b c |. % \begin{texnote} % This protected macro expands to the \TeX{} primitive \tn{read} % along with the |to| keyword. % \end{texnote} % \end{function} % % \begin{function}[added = 2012-06-24, updated = 2012-07-31]{\ior_get_str:NN} % \begin{syntax} % \cs{ior_get_str:NN} \meta{stream} \meta{token list variable} % \end{syntax} % Function that reads one line from the input \meta{stream} and stores % the result locally in the \meta{token list} variable. If the % \meta{stream} is not open, input is requested from the terminal. % The material is read from the \meta{stream} as a series of tokens with % category code $12$ (other), with the exception of space % characters which are given category code $10$ (space). % Multiple whitespace characters are retained by this process. It will % always only read one line and any blank lines in the input % will result in the \meta{token list variable} being empty. Unlike % \cs{ior_get:NN}, line ends do not receive any special treatment. Thus % input % \begin{verbatim} % a b c % \end{verbatim} % will result in a token list |a b c| with the letters |a|, |b|, and |c| % having category code~12. % \begin{texnote} % This protected macro is a wrapper around the \eTeX{} primitive % \tn{readline}. However, the end-line character normally added by % this primitive is not included in the result of % \cs{ior_get_str:NN}. % \end{texnote} % \end{function} % %\begin{function}[updated = 2012-02-10, EXP, pTF]{\ior_if_eof:N} % \begin{syntax} % \cs{ior_if_eof_p:N} \meta{stream} \\ % \cs{ior_if_eof:NTF} \meta{stream} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the end of a \meta{stream} has been reached during a reading % operation. The test will also return a \texttt{true} value if % the \meta{stream} is not open. %\end{function} % % \section{Writing to files} % % \begin{function}[updated = 2012-06-05]{\iow_now:Nn, \iow_now:Nx, \iow_now:cn, \iow_now:cx} % \begin{syntax} % \cs{iow_now:Nn} \meta{stream} \Arg{tokens} % \end{syntax} % This functions writes \meta{tokens} to the specified % \meta{stream} immediately (\emph{i.e.}~the write operation is called % on expansion of \cs{iow_now:Nn}). % \end{function} % % \begin{function}{\iow_log:n, \iow_log:x} % \begin{syntax} % \cs{iow_log:n} \Arg{tokens} % \end{syntax} % This function writes the given \meta{tokens} to the log (transcript) % file immediately: it is a dedicated version of \cs{iow_now:Nn}. % \end{function} % % \begin{function}{\iow_term:n, \iow_term:x} % \begin{syntax} % \cs{iow_term:n} \Arg{tokens} % \end{syntax} % This function writes the given \meta{tokens} to the terminal % file immediately: it is a dedicated version of \cs{iow_now:Nn}. % \end{function} % % \begin{function}{\iow_shipout:Nn, \iow_shipout:Nx, \iow_shipout:cn, \iow_shipout:cx} % \begin{syntax} % \cs{iow_shipout:Nn} \meta{stream} \Arg{tokens} % \end{syntax} % This functions writes \meta{tokens} to the specified % \meta{stream} when the current page is finalised (\emph{i.e.}~at % shipout). The \texttt{x}-type variants expand the \meta{tokens} % at the point where the function is used but \emph{not} when the % resulting tokens are written to the \meta{stream} % (\emph{cf.}~\cs{iow_shipout_x:Nn}). % \begin{texnote} % When using \pkg{expl3} with a format other than \LaTeX{}, new line % characters inserted using \cs{iow_newline:} or using the % line-wrapping code \cs{iow_wrap:nnnN} will not be recognized in % the argument of \cs{iow_shipout:Nn}. This may lead to the % insertion of additionnal unwanted line-breaks. % \end{texnote} % \end{function} % % \begin{function}[updated = 2012-09-08]{\iow_shipout_x:Nn, \iow_shipout_x:Nx, \iow_shipout_x:cn, \iow_shipout_x:cx} % \begin{syntax} % \cs{iow_shipout_x:Nn} \meta{stream} \Arg{tokens} % \end{syntax} % This functions writes \meta{tokens} to the specified % \meta{stream} when the current page is finalised (\emph{i.e.}~at % shipout). The \meta{tokens} are expanded at the time of writing % in addition to any expansion when the function is used. This makes % these functions suitable for including material finalised during % the page building process (such as the page number integer). % \begin{texnote} % This is a wrapper around the \TeX{} primitive \tn{write}. % When using \pkg{expl3} with a format other than \LaTeX{}, new line % characters inserted using \cs{iow_newline:} or using the % line-wrapping code \cs{iow_wrap:nnnN} will not be recognized in % the argument of \cs{iow_shipout:Nn}. This may lead to the % insertion of additionnal unwanted line-breaks. % \end{texnote} % \end{function} % % \begin{function}[EXP]{\iow_char:N} % \begin{syntax} % \cs{iow_char:N} |\|\meta{char} % \end{syntax} % Inserts \meta{char} into the output stream. Useful when trying to % write difficult characters such as |%|, |{|, |}|, % \emph{etc.}~in messages, for example: % \begin{verbatim} % \iow_now:Nx \g_my_iow { \iow_char:N \{ text \iow_char:N \} } % \end{verbatim} % The function has no effect if writing is taking place without % expansion (\emph{e.g.}~in the second argument of \cs{iow_now:Nn}). % \end{function} % % \begin{function}[EXP]{\iow_newline:} % \begin{syntax} % \cs{iow_newline:} % \end{syntax} % Function to add a new line within the \meta{tokens} written to a % file. The function has no effect if writing is taking place without % expansion (\emph{e.g.}~in the second argument of \cs{iow_now:Nn}). % \begin{texnote} % When using \pkg{expl3} with a format other than \LaTeX{}, the % character inserted by \cs{iow_newline:} will not be recognized by % \TeX{}, which may lead to the insertion of additionnal unwanted % line-breaks. This issue only affects \cs{iow_shipout:Nn}, % \cs{iow_shipout_x:Nn} and direct uses of primitive operations. % \end{texnote} % \end{function} % % \subsection{Wrapping lines in output} % % \begin{function}[added = 2012-06-28]{\iow_wrap:nnnN} % \begin{syntax} % \cs{iow_wrap:nnnN} \Arg{text} \Arg{run-on text} \Arg{set up} \meta{function} % \end{syntax} % This function will wrap the \meta{text} to a fixed number of % characters per line. At the start of each line which is wrapped, % the \meta{run-on text} will be inserted. The line character count % targeted will be the value of \cs{l_iow_line_count_int} minus the % number of characters in the \meta{run-on text}. The \meta{text} and % \meta{run-on text} are exhaustively expanded by the function, with the % following substitutions: % \begin{itemize} % \item |\\| may be used to force a new line, % \item \verb*|\ | may be used to represent a forced space % (for example after a control sequence), % \item |\#|, |\%|, |\{|, |\}|, |\~| may be used to represent % the corresponding character, % \item \cs{iow_indent:n} may be used to indent a part of the % \meta{text} (not the \meta{run-on text}). % \end{itemize} % Additional functions may be added to the wrapping by using the % \meta{set up}, which is executed before the wrapping takes place: this % may include overriding the substitutions listed. % % Any expandable material in the \meta{text} which is not to be expanded % on wrapping should be converted to a string using \cs{token_to_str:N}, % \cs{tl_to_str:n}, \cs{tl_to_str:N}, \emph{etc.} % % The result of the wrapping operation is passed as a braced argument to the % \meta{function}, which will typically be a wrapper around a write % operation. The output of \cs{iow_wrap:nnnN} (\emph{i.e.}~the argument % passed to the \meta{function}) will consist of characters of category % \enquote{other} (category code~12), with the exception of spaces which % will have category \enquote{space} (category code~10). This means that the % output will \emph{not} expand further when written to a file. % % \begin{texnote} % Internally, \cs{iow_wrap:nnnN} carries out an \texttt{x}-type expansion % on the \meta{text} to expand it. This is done in such a way that % \cs{exp_not:N} or \cs{exp_not:n} \emph{could} be used to prevent % expansion of material. However, this is less conceptually clear than % conversion to a string, which is therefore the supported method for % handling expandable material in the \meta{text}. % \end{texnote} % \end{function} % % \begin{function}[added = 2011-09-21]{\iow_indent:n} % \begin{syntax} % \cs{iow_indent:n} \Arg{text} % \end{syntax} % In the first argument of \cs{iow_wrap:nnnN} (for instance in messages), % indents \meta{text} by four spaces. This function will not cause % a line break, and only affects lines which start within the scope % of the \meta{text}. In case the indented \meta{text} should appear % on separate lines from the surrounding text, use |\\| to force % line breaks. % \end{function} % % \begin{variable}[added = 2012-06-24]{\l_iow_line_count_int} % The maximum number of characters in a line to be written % by the \cs{iow_wrap:nnnN} % function. This value depends on the \TeX{} system in use: the standard % value is $78$, which is typically correct for unmodified \TeX{}live % and MiK\TeX{} systems. % \end{variable} % % \begin{variable}[added = 2011-09-05]{\c_catcode_other_space_tl} % Token list containing one character with category code $12$, % (\enquote{other}), and character code $32$ (space). % \end{variable} % % \subsection{Constant input--output streams} % % \begin{variable}{\c_term_ior} % Constant input stream for reading from the terminal. Reading from this % stream using \cs{ior_get:NN} or similar will result in a prompt from % \TeX{} of the form % \begin{verbatim} % = % \end{verbatim} % \end{variable} % % \begin{variable}{\c_log_iow, \c_term_iow} % Constant output streams for writing to the log and to the terminal % (plus the log), respectively. % \end{variable} % % \subsection{Primitive conditionals} % % \begin{function}[EXP]{\if_eof:w} % \begin{syntax} % \cs{if_eof:w} \meta{stream} % ~~\meta{true code} % \cs{else:} % ~~\meta{false code} % \cs{fi:} % \end{syntax} % Tests if the \meta{stream} returns \enquote{end of file}, which is true % for non-existent files. The \cs{else:} branch is optional. % \begin{texnote} % This is the \TeX{} primitive \tn{ifeof}. % \end{texnote} % \end{function} % % \subsection{Internal file functions and variables} % % \begin{variable}{\g__file_internal_ior} % Used to test for the existence of files when opening. % \end{variable} % % \begin{variable}{\l__file_internal_name_tl} % Used to return the full name of a file for internal use. This is % set by \cs{file_if_exist:n(TF)} and \cs{__file_if_exist:nT}, and % the value may then be used to load a file directly provided no % further operations intervene. % \end{variable} % % \begin{function}[added = 2012-02-09]{\__file_name_sanitize:nn} % \begin{syntax} % \cs{__file_name_sanitize:nn} \Arg{name} \Arg{tokens} % \end{syntax} % Exhaustively-expands the \meta{name} with the exception of any % category \meta{active} (catcode~$13$) tokens, which are not expanded. % The list of \meta{active} tokens is taken from \cs{l_char_active_seq}. % The \meta{sanitized name} is then inserted (in braces) after the % \meta{tokens}, which should further process the file name. If any % spaces are found in the name after expansion, an error is raised. % \end{function} % % \subsection{Internal input--output functions} % % \begin{function}[added = 2012-01-23]{\__ior_open:Nn, \__ior_open:No} % \begin{syntax} % \cs{__ior_open:Nn} \meta{stream} \Arg{file name} % \end{syntax} % This function has identical syntax to the public version. However, % is does not take precautions against active characters in the % \meta{file name}, and it does not attempt to add a \meta{path} to % the \meta{file name}: it is therefore intended to be used by % higher-level % functions which have already fully expanded the \meta{file name} and which % need to perform multiple open or close operations. See for example the % implementation of \cs{file_add_path:nN}, % \end{function} % % \begin{function}[added = 2014-08-23]{\__iow_with:Nnn} % \begin{syntax} % \cs{__iow_with:Nnn} \meta{integer} \Arg{value} \Arg{code} % \end{syntax} % If the \meta{integer} is equal to the \meta{value} then this % function simply runs the \meta{code}. Otherwise it saves the % current value of the \meta{integer}, sets it to the \meta{value}, % runs the \meta{code}, and restores the \meta{integer} to its former % value. This is used to ensure that the \tn{newlinechar} is~$10$ % when writing to a stream, which lets \cs{iow_newline:} work, and % that \tn{errorcontextlines} is $-1$ when displaying a message. % \end{function} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3file} implementation} % % \TestFiles{m3file001} % % \begin{macrocode} %<*initex|package> % \end{macrocode} % % \begin{macrocode} %<@@=file> % \end{macrocode} % % \subsection{File operations} % % \begin{variable}{\g_file_current_name_tl} % The name of the current file should be available at all times. % For the format the file name needs to be picked up at the start of the % file. In package mode the current file name is collected from \LaTeXe{}. % \begin{macrocode} \tl_new:N \g_file_current_name_tl %<*initex> \tex_everyjob:D \exp_after:wN { \tex_the:D \tex_everyjob:D \tl_gset:Nx \g_file_current_name_tl { \tex_jobname:D } } % %<*package> \tl_gset_eq:NN \g_file_current_name_tl \@currname % % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_stack_seq} % The input list of files is stored as a sequence stack. % \begin{macrocode} \seq_new:N \g_@@_stack_seq % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_record_seq} % The total list of files used is recorded separately from the current % file stack, as nothing is ever popped from this list. The current % file name should be included in the file list! In format mode, this % is done at the very start of the \TeX{} run. In package mode we % will eventually copy the contents of \cs{@filelist}. % \begin{macrocode} \seq_new:N \g_@@_record_seq %<*initex> \tex_everyjob:D \exp_after:wN { \tex_the:D \tex_everyjob:D \seq_gput_right:NV \g_@@_record_seq \g_file_current_name_tl } % % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_internal_tl} % Used as a short-term scratch variable. It may be possible to reuse % \cs{l_@@_internal_name_tl} there. % \begin{macrocode} \tl_new:N \l_@@_internal_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_internal_name_tl} % Used to return the fully-qualified name of a file. % \begin{macrocode} \tl_new:N \l_@@_internal_name_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_search_path_seq} % The current search path. % \begin{macrocode} \seq_new:N \l_@@_search_path_seq % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_saved_search_path_seq} % The current search path has to be saved for package use. % \begin{macrocode} %<*package> \seq_new:N \l_@@_saved_search_path_seq % % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_internal_seq} % Scratch space for comma list conversion in package mode. % \begin{macrocode} %<*package> \seq_new:N \l_@@_internal_seq % % \end{macrocode} % \end{variable} % % \begin{macro}[int]{\@@_name_sanitize:nn} % \begin{macro}[int, aux]{\@@_name_sanitize_aux:n} % For converting a token list to a string where active characters are treated % as strings from the start. The logic to the quoting normalisation is the % same as used by \texttt{lualatexquotejobname}: check for balanced |"|, and % assuming they balance strip all of them out before quoting the entire name % if it contains spaces. % \begin{macrocode} \cs_new_protected:Npn \@@_name_sanitize:nn #1#2 { \group_begin: \seq_map_inline:Nn \l_char_active_seq { \cs_set_nopar:Npx ##1 { \token_to_str:N ##1 } } \tl_set:Nx \l_@@_internal_name_tl {#1} \tl_set:Nx \l_@@_internal_name_tl { \tl_to_str:N \l_@@_internal_name_tl } \int_compare:nNnTF { \int_mod:nn { 0 \tl_map_function:NN \l_@@_internal_name_tl \@@_name_sanitize_aux:n } \c_two } = \c_zero { \tl_remove_all:Nn \l_@@_internal_name_tl { " } \tl_if_in:NnT \l_@@_internal_name_tl { ~ } { \tl_set:Nx \l_@@_internal_name_tl { " \exp_not:V \l_@@_internal_name_tl " } } } { \__msg_kernel_error:nnx { kernel } { unbalanced-quote-in-filename } { \l_@@_internal_name_tl } } \use:x { \group_end: \exp_not:n {#2} { \l_@@_internal_name_tl } } } \cs_new:Npn \@@_name_sanitize_aux:n #1 { \token_if_eq_charcode:NNT #1 " { + \c_one } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\file_add_path:nN} % \begin{macro}[aux]{\@@_add_path:nN, \@@_add_path_search:nN} % The way to test if a file exists is to try to open it: if it does not % exist then \TeX{} will report end-of-file. For files which are in the % current directory, this is straight-forward. For other locations, a % search has to be made looking at each potential path in turn. The first % location is of course treated as the correct one. If nothing is found, % |#2| is returned empty. % \begin{macrocode} \cs_new_protected:Npn \file_add_path:nN #1 { \@@_name_sanitize:nn {#1} { \@@_add_path:nN } } \cs_new_protected:Npn \@@_add_path:nN #1#2 { \__ior_open:Nn \g_@@_internal_ior {#1} \ior_if_eof:NTF \g_@@_internal_ior { \@@_add_path_search:nN {#1} #2 } { \tl_set:Nn #2 {#1} } \ior_close:N \g_@@_internal_ior } \cs_new_protected:Npn \@@_add_path_search:nN #1#2 { \tl_set:Nn #2 { \q_no_value } %<*package> \cs_if_exist:NT \input@path { \seq_set_eq:NN \l_@@_saved_search_path_seq \l_@@_search_path_seq \seq_set_split:NnV \l_@@_internal_seq { , } \input@path \seq_concat:NNN \l_@@_search_path_seq \l_@@_search_path_seq \l_@@_internal_seq } % \seq_map_inline:Nn \l_@@_search_path_seq { \__ior_open:Nn \g_@@_internal_ior { ##1 #1 } \ior_if_eof:NF \g_@@_internal_ior { \tl_set:Nx #2 { ##1 #1 } \seq_map_break: } } %<*package> \cs_if_exist:NT \input@path { \seq_set_eq:NN \l_@@_search_path_seq \l_@@_saved_search_path_seq } % } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[TF]{\file_if_exist:n} % The test for the existence of a file is a wrapper around the function to % add a path to a file. If the file was found, the path will contain % something, whereas if the file was not located then the return value % will be \cs{q_no_value}. % \begin{macrocode} \prg_new_protected_conditional:Npnn \file_if_exist:n #1 { T , F , TF } { \file_add_path:nN {#1} \l_@@_internal_name_tl \quark_if_no_value:NTF \l_@@_internal_name_tl { \prg_return_false: } { \prg_return_true: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\file_input:n} % \begin{macro}[int]{\@@_if_exist:nT} % \begin{macro}[aux]{\@@_input:n \@@_input:V} % \begin{macro}[aux]{\@@_input_aux:n, \@@_input_aux:o} % Loading a file is done in a safe way, checking first that the file % exists and loading only if it does. Push the file name on the % \cs{g_@@_stack_seq}, and add it to the file list, either % \cs{g_@@_record_seq}, or \cs{@filelist} in package mode. % \begin{macrocode} \cs_new_protected:Npn \file_input:n #1 { \@@_if_exist:nT {#1} { \@@_input:V \l_@@_internal_name_tl } } % \end{macrocode} % This code is spun out as a separate function to encapsulate the % error message into a easy-to-reuse form. % \begin{macrocode} \cs_new_protected:Npn \@@_if_exist:nT #1#2 { \file_if_exist:nTF {#1} {#2} { \@@_name_sanitize:nn {#1} { \__msg_kernel_error:nnx { kernel } { file-not-found } } } } \cs_new_protected:Npn \@@_input:n #1 { \tl_if_in:nnTF {#1} { . } { \@@_input_aux:n {#1} } { \@@_input_aux:o { \tl_to_str:n { #1 . tex } } } } \cs_generate_variant:Nn \@@_input:n { V } \cs_new_protected:Npn \@@_input_aux:n #1 { %<*initex> \seq_gput_right:Nn \g_@@_record_seq {#1} % %<*package> \clist_if_exist:NTF \@filelist { \@addtofilelist {#1} } { \seq_gput_right:Nn \g_@@_record_seq {#1} } % \seq_gpush:No \g_@@_stack_seq \g_file_current_name_tl \tl_gset:Nn \g_file_current_name_tl {#1} \tex_input:D #1 \c_space_tl \seq_gpop:NN \g_@@_stack_seq \l_@@_internal_tl \tl_gset_eq:NN \g_file_current_name_tl \l_@@_internal_tl } \cs_generate_variant:Nn \@@_input_aux:n { o } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\file_path_include:n} % \begin{macro}{\file_path_remove:n} % \begin{macro}[aux]{\@@_path_include:n} % Wrapper functions to manage the search path. % \begin{macrocode} \cs_new_protected:Npn \file_path_include:n #1 { \@@_name_sanitize:nn {#1} { \@@_path_include:n } } \cs_new_protected:Npn \@@_path_include:n #1 { \seq_if_in:NnF \l_@@_search_path_seq {#1} { \seq_put_right:Nn \l_@@_search_path_seq {#1} } } \cs_new_protected:Npn \file_path_remove:n #1 { \@@_name_sanitize:nn {#1} { \seq_remove_all:Nn \l_@@_search_path_seq } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\file_list:} % A function to list all files used to the log, without duplicates. % In package mode, if \cs{@filelist} is still defined, we need to take % this list of file names into account % (we capture it \cs{AtBeginDocument} into % \cs{g_@@_record_seq}), turning each file name into a string. % \begin{macrocode} \cs_new_protected_nopar:Npn \file_list: { \seq_set_eq:NN \l_@@_internal_seq \g_@@_record_seq %<*package> \clist_if_exist:NT \@filelist { \clist_map_inline:Nn \@filelist { \seq_put_right:No \l_@@_internal_seq { \tl_to_str:n {##1} } } } % \seq_remove_duplicates:N \l_@@_internal_seq \iow_log:n { *~File~List~* } \seq_map_inline:Nn \l_@@_internal_seq { \iow_log:n {##1} } \iow_log:n { ************* } } % \end{macrocode} % \end{macro} % % When used as a package, there is a need to hold onto the standard file % list as well as the new one here. File names recorded in % \cs{@filelist} must be turned to strings before being added to % \cs{g_@@_record_seq}. % \begin{macrocode} %<*package> \AtBeginDocument { \clist_map_inline:Nn \@filelist { \seq_gput_right:No \g_@@_record_seq { \tl_to_str:n {#1} } } } % % \end{macrocode} % % \subsection{Input operations} % % \begin{macrocode} %<@@=ior> % \end{macrocode} % % \subsubsection{Variables and constants} % % \begin{variable}{\c_term_ior} % Reading from the terminal (with a prompt) is done using a positive % but non-existent stream number. Unlike writing, there is no concept % of reading from the log. % \begin{macrocode} \cs_new_eq:NN \c_term_ior \c_sixteen % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_streams_seq} % A list of the currently-available input streams to be used as a % stack. In format mode, all streams (from $0$ to~$15$) are % available, while the package requests streams to \LaTeXe{} as they % are needed (initially none are needed), so the starting point % varies! % \begin{macrocode} \seq_new:N \g_@@_streams_seq %<*initex> \seq_gset_split:Nnn \g_@@_streams_seq { , } { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 } % % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_stream_tl} % Used to recover the raw stream number from the stack. % \begin{macrocode} \tl_new:N \l_@@_stream_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_streams_prop} % The name of the file attached to each stream is tracked in a property % list. To get the correct number of reserved streams in package mode the % underlying mechanism needs to be queried. For \LaTeXe{} and plain \TeX{} % this data is stored in |\count16|: with the \pkg{etex} package loaded % we need to subtract $1$ as the register holds the number of the next % stream to use. In Con\TeX{}t, we need to look at |\count38| but there % is no subtraction: like the original plain \TeX{}/\LaTeXe{} mechanism % it holds the value of the \emph{last} stream allocated. % \begin{macrocode} \prop_new:N \g_@@_streams_prop %<*package> \int_step_inline:nnnn { \c_zero } { \c_one } { \cs_if_exist:NTF \normalend { \tex_count:D 38 \scan_stop: } { \tex_count:D 16 \scan_stop: \cs_if_exist:NT \loccount { - \c_one } } } { \prop_gput:Nnn \g_@@_streams_prop {#1} { Reserved~by~format } } % % \end{macrocode} % \end{variable} % % \subsubsection{Stream management} % % \begin{macro}{\ior_new:N, \ior_new:c} % Reserving a new stream is done by defining the name as equal to using the % terminal. % \begin{macrocode} \cs_new_protected:Npn \ior_new:N #1 { \cs_new_eq:NN #1 \c_term_ior } \cs_generate_variant:Nn \ior_new:N { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\ior_open:Nn, \ior_open:cn} % \begin{macro}[aux]{\@@_open_aux:Nn} % Opening an input stream requires a bit of pre-processing. The file name % is sanitized to deal with active characters, before an auxiliary adds a % path and checks that the file really exists. If those two tests pass, then % pass the information on to the lower-level function which deals with % streams. % \begin{macrocode} \cs_new_protected:Npn \ior_open:Nn #1#2 { \__file_name_sanitize:nn {#2} { \@@_open_aux:Nn #1 } } \cs_generate_variant:Nn \ior_open:Nn { c } \cs_new_protected:Npn \@@_open_aux:Nn #1#2 { \file_add_path:nN {#2} \l__file_internal_name_tl \quark_if_no_value:NTF \l__file_internal_name_tl { \__msg_kernel_error:nnx { kernel } { file-not-found } {#2} } { \@@_open:No #1 \l__file_internal_name_tl } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[TF]{\ior_open:Nn, \ior_open:cn} % \begin{macro}[aux]{\@@_open_aux:NnTF} % Much the same idea for opening a read with a conditional, except the % auxiliary function does not issue an error if the file is not found. % \begin{macrocode} \prg_new_protected_conditional:Npnn \ior_open:Nn #1#2 { T , F , TF } { \__file_name_sanitize:nn {#2} { \@@_open_aux:NnTF #1 } } \cs_generate_variant:Nn \ior_open:NnT { c } \cs_generate_variant:Nn \ior_open:NnF { c } \cs_generate_variant:Nn \ior_open:NnTF { c } \cs_new_protected:Npn \@@_open_aux:NnTF #1#2 { \file_add_path:nN {#2} \l__file_internal_name_tl \quark_if_no_value:NTF \l__file_internal_name_tl { \prg_return_false: } { \@@_open:No #1 \l__file_internal_name_tl \prg_return_true: } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int]{\@@_open:Nn, \@@_open:No} % \begin{macro}[aux]{\@@_open_stream:Nn} % The stream allocation itself uses the fact that there is a list of all of % those available, so allocation is simply a question of using the number at % the top of the list. In package mode, life gets more complex as it's % important to keep things in sync. That is done using a two-part approach: % any streams that have already been taken up by \pkg{ior} but are now free % are tracked, so we first try those. If that fails, ask \LaTeXe{} for a new % stream and use that number (after a bit of conversion). % \begin{macrocode} \cs_new_protected:Npn \@@_open:Nn #1#2 { \ior_close:N #1 \seq_gpop:NNTF \g_@@_streams_seq \l_@@_stream_tl { \@@_open_stream:Nn #1 {#2} } %<*initex> { \__msg_kernel_fatal:nn { kernel } { input-streams-exhausted } } % %<*package> { \cs:w newread \cs_end: #1 \tl_set:Nx \l_@@_stream_tl { \int_eval:n {#1} } \@@_open_stream:Nn #1 {#2} } % } \cs_generate_variant:Nn \@@_open:Nn { No } \cs_new_protected:Npn \@@_open_stream:Nn #1#2 { \tex_global:D \tex_chardef:D #1 = \l_@@_stream_tl \scan_stop: \prop_gput:NVn \g_@@_streams_prop #1 {#2} \tex_openin:D #1 #2 \scan_stop: } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\ior_close:N, \ior_close:c} % Closing a stream means getting rid of it at the \TeX{} level and % removing from the various data structures. Unless the name passed % is an invalid stream number (outside the range $[0,15]$), it can be % closed. On the other hand, it only gets added to the stack if it % was not already there, to avoid duplicates building up. % \begin{macrocode} \cs_new_protected:Npn \ior_close:N #1 { \int_compare:nT { \c_minus_one < #1 < \c_sixteen } { \tex_closein:D #1 \prop_gremove:NV \g_@@_streams_prop #1 \seq_if_in:NVF \g_@@_streams_seq #1 { \seq_gpush:NV \g_@@_streams_seq #1 } \cs_gset_eq:NN #1 \c_term_ior } } \cs_generate_variant:Nn \ior_close:N { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\ior_list_streams:} % \begin{macro}[aux]{\@@_list_streams:Nn} % Show the property lists, but with some \enquote{pretty printing}. % See the \pkg{l3msg} module. If there are no open read streams, % issue the message \texttt{show-no-stream}, and show an empty % token list. If there are open read streams, format them with % \cs{__msg_show_item_unbraced:nn}, and with the message % \texttt{show-open-streams}. % \begin{macrocode} \cs_new_protected_nopar:Npn \ior_list_streams: { \@@_list_streams:Nn \g_@@_streams_prop { input } } \cs_new_protected:Npn \@@_list_streams:Nn #1#2 { \__msg_term:nnn { LaTeX / kernel } { \prop_if_empty:NTF #1 { show-no-stream } { show-open-streams } } {#2} \__msg_show_variable:n { \prop_map_function:NN #1 \__msg_show_item_unbraced:nn } } % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Reading input} % % \begin{macro}[int]{\if_eof:w} % The primitive conditional % \begin{macrocode} \cs_new_eq:NN \if_eof:w \tex_ifeof:D % \end{macrocode} % \end{macro} % % \begin{macro}[pTF]{\ior_if_eof:N} % To test if some particular input stream is exhausted the following % conditional is provided. % \begin{macrocode} \prg_new_conditional:Nnn \ior_if_eof:N { p , T , F , TF } { \cs_if_exist:NTF #1 { \if_int_compare:w #1 = \c_sixteen \prg_return_true: \else: \if_eof:w #1 \prg_return_true: \else: \prg_return_false: \fi: \fi: } { \prg_return_true: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\ior_get:NN} % And here we read from files. % \begin{macrocode} \cs_new_protected:Npn \ior_get:NN #1#2 { \tex_read:D #1 to #2 } % \end{macrocode} % \end{macro} % % \begin{macro}{\ior_get_str:NN} % Reading as strings is a more complicated wrapper, as we wish to % remove the endline character. % \begin{macrocode} \cs_new_protected:Npn \ior_get_str:NN #1#2 { \use:x { \int_set_eq:NN \tex_endlinechar:D \c_minus_one \exp_not:n { \etex_readline:D #1 to #2 } \int_set:Nn \tex_endlinechar:D { \int_use:N \tex_endlinechar:D } } } % \end{macrocode} % \end{macro} % % \begin{variable}{\g__file_internal_ior} % Needed by the higher-level code, but cannot be created until here. % \begin{macrocode} \ior_new:N \g__file_internal_ior % \end{macrocode} % \end{variable} % % \subsection{Output operations} % % \begin{macrocode} %<@@=iow> % \end{macrocode} % % There is a lot of similarity here to the input operations, at least for % many of the basics. Thus quite a bit is copied from the earlier material % with minor alterations. % % \subsubsection{Variables and constants} % % \begin{variable}{\c_log_iow, \c_term_iow} % Here we allocate two output streams for writing to the transcript % file only (\cs{c_log_iow}) and to both the terminal and % transcript file (\cs{c_term_iow}). % \begin{macrocode} \cs_new_eq:NN \c_log_iow \c_minus_one \cs_new_eq:NN \c_term_iow \c_sixteen % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_streams_seq} % A list of the currently-available input streams to be used as a stack. % Things are done differently in format and package mode, so the starting % point varies! % \begin{macrocode} \seq_new:N \g_@@_streams_seq %<*initex> \seq_gset_eq:NN \g_@@_streams_seq \g__ior_streams_seq % % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_stream_tl} % Used to recover the raw stream number from the stack. % \begin{macrocode} \tl_new:N \l_@@_stream_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_streams_prop} % As for reads with the appropriate adjustment of the register numbers to % check on. % \begin{macrocode} \prop_new:N \g_@@_streams_prop %<*package> \int_step_inline:nnnn { \c_zero } { \c_one } { \cs_if_exist:NTF \normalend { \tex_count:D 39 \scan_stop: } { \tex_count:D 17 \scan_stop: \cs_if_exist:NT \loccount { - \c_one } } } { \prop_gput:Nnn \g_@@_streams_prop {#1} { Reserved~by~format } } % % \end{macrocode} % \end{variable} % % \subsection{Stream management} % % \begin{macro}{\iow_new:N, \iow_new:c} % Reserving a new stream is done by defining the name as equal to writing % to the terminal: odd but at least consistent. % \begin{macrocode} \cs_new_protected:Npn \iow_new:N #1 { \cs_new_eq:NN #1 \c_term_iow } \cs_generate_variant:Nn \iow_new:N { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\iow_open:Nn, \iow_open:cn} % \begin{macro}[int]{\@@_open:Nn} % \begin{macro}[aux]{\@@_open_stream:Nn} % The same idea as for reading, but without the path and without the need % to allow for a conditional version. % \begin{macrocode} \cs_new_protected:Npn \iow_open:Nn #1#2 { \__file_name_sanitize:nn {#2} { \@@_open:Nn #1 } } \cs_generate_variant:Nn \iow_open:Nn { c } \cs_new_protected:Npn \@@_open:Nn #1#2 { \iow_close:N #1 \seq_gpop:NNTF \g_@@_streams_seq \l_@@_stream_tl { \@@_open_stream:Nn #1 {#2} } %<*initex> { \__msg_kernel_fatal:nn { kernel } { output-streams-exhausted } } % %<*package> { \cs:w newwrite \cs_end: #1 \tl_set:Nx \l_@@_stream_tl { \int_eval:n {#1} } \@@_open_stream:Nn #1 {#2} } % } \cs_generate_variant:Nn \@@_open:Nn { No } \cs_new_protected:Npn \@@_open_stream:Nn #1#2 { \tex_global:D \tex_chardef:D #1 = \l_@@_stream_tl \scan_stop: \prop_gput:NVn \g_@@_streams_prop #1 {#2} \tex_immediate:D \tex_openout:D #1 #2 \scan_stop: } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\iow_close:N, \iow_close:c} % Closing a stream is not quite the reverse of opening one. First, % the close operation is easier than the open one, and second as the % stream is actually a number we can use it directly to show that the % slot has been freed up. % \begin{macrocode} \cs_new_protected:Npn \iow_close:N #1 { \int_compare:nT { \c_minus_one < #1 < \c_sixteen } { \tex_immediate:D \tex_closeout:D #1 \prop_gremove:NV \g_@@_streams_prop #1 \seq_if_in:NVF \g_@@_streams_seq #1 { \seq_gpush:NV \g_@@_streams_seq #1 } \cs_gset_eq:NN #1 \c_term_ior } } \cs_generate_variant:Nn \iow_close:N { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\iow_list_streams:} % \begin{macro}{\@@_list_streams:Nn} % Done as for input, but with a copy of the auxiliary so the name is correct. % \begin{macrocode} \cs_new_protected_nopar:Npn \iow_list_streams: { \@@_list_streams:Nn \g_@@_streams_prop { output } } \cs_new_eq:NN \@@_list_streams:Nn \__ior_list_streams:Nn % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Deferred writing} % % \begin{macro}{\iow_shipout_x:Nn, \iow_shipout_x:Nx, \iow_shipout_x:cn, \iow_shipout_x:cx} % First the easy part, this is the primitive, which expects its % argument to be braced. % \begin{macrocode} \cs_new_protected:Npn \iow_shipout_x:Nn #1#2 { \tex_write:D #1 {#2} } \cs_generate_variant:Nn \iow_shipout_x:Nn { c, Nx, cx } % \end{macrocode} % \end{macro} % % \begin{macro}{\iow_shipout:Nn, \iow_shipout:Nx, \iow_shipout:cn, \iow_shipout:cx} % With \eTeX{} available deferred writing without expansion is easy. % \begin{macrocode} \cs_new_protected:Npn \iow_shipout:Nn #1#2 { \tex_write:D #1 { \exp_not:n {#2} } } \cs_generate_variant:Nn \iow_shipout:Nn { c, Nx, cx } % \end{macrocode} % \end{macro} % % \subsubsection{Immediate writing} % % \begin{macro}[aux]{\@@_with:Nnn, \@@_with_aux:nNnn} % If the integer~|#1| is equal to~|#2|, just leave~|#3| in the input % stream. Otherwise, pass the old value to an auxiliary, which sets % the integer to the new value, runs the code, and restores the % integer. % \begin{macrocode} \cs_new_protected:Npn \@@_with:Nnn #1#2 { \int_compare:nNnTF {#1} = {#2} { \use:n } { \exp_args:No \@@_with_aux:nNnn { \int_use:N #1 } #1 {#2} } } \cs_new_protected:Npn \@@_with_aux:nNnn #1#2#3#4 { \int_set:Nn #2 {#3} #4 \int_set:Nn #2 {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\iow_now:Nn,\iow_now:Nx,\iow_now:cn,\iow_now:cx} % This routine writes the second argument onto the output stream without % expansion. If this stream isn't open, the output goes to the terminal % instead. If the first argument is no output stream at all, we get an % internal error. We don't use the expansion done by \tn{write} to % get the |Nx| variant, because it differs in subtle ways from % \texttt{x}-expansion, namely, macro parameter characters would not % need to be doubled. We set the \tn{newlinechar} to~$10$ using % \cs{@@_with:Nnn} to support formats such as plain \TeX{}: otherwise, % \cs{iow_newline:} would not work. We do not do this for % \cs{iow_shipout:Nn} or \cs{iow_shipout_x:Nn}, as \TeX{} looks at the % value of the \tn{newlinechar} at shipout time in those cases. % \begin{macrocode} \cs_new_protected:Npn \iow_now:Nn #1#2 { \@@_with:Nnn \tex_newlinechar:D { `\^^J } { \tex_immediate:D \tex_write:D #1 { \exp_not:n {#2} } } } \cs_generate_variant:Nn \iow_now:Nn { c, Nx, cx } % \end{macrocode} % \end{macro} % % \begin{macro}{\iow_log:n, \iow_log:x} % \begin{macro}{\iow_term:n, \iow_term:x} % Writing to the log and the terminal directly are relatively easy. % \begin{macrocode} \cs_set_protected_nopar:Npn \iow_log:x { \iow_now:Nx \c_log_iow } \cs_new_protected_nopar:Npn \iow_log:n { \iow_now:Nn \c_log_iow } \cs_set_protected_nopar:Npn \iow_term:x { \iow_now:Nx \c_term_iow } \cs_new_protected_nopar:Npn \iow_term:n { \iow_now:Nn \c_term_iow } % \end{macrocode} %\end{macro} %\end{macro} % % \subsubsection{Special characters for writing} % % \begin{macro}{\iow_newline:} % Global variable holding the character that forces a new line when % something is written to an output stream. % \begin{macrocode} \cs_new_nopar:Npn \iow_newline: { ^^J } % \end{macrocode} % \end{macro} % % \begin{macro}{\iow_char:N} % Function to write any escaped char to an output stream. % \begin{macrocode} \cs_new_eq:NN \iow_char:N \cs_to_str:N % \end{macrocode} % \end{macro} % % \subsubsection{Hard-wrapping lines to a character count} % % The code here implements a generic hard-wrapping function. This is % used by the messaging system, but is designed such that it is % available for other uses. % % \begin{macro}[var, added = 2012-06-24]{\l_iow_line_count_int} % This is the \enquote{raw} number of characters in a line which % can be written to the terminal. % The standard value is the line length typically used by % \TeX{}Live and Mik\TeX{}. % \begin{macrocode} \int_new:N \l_iow_line_count_int \int_set:Nn \l_iow_line_count_int { 78 } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\l_@@_target_count_int} % This stores the target line count: the full number of characters % in a line, minus any part for a leader at the start of each line. % \begin{macrocode} \int_new:N \l_@@_target_count_int % \end{macrocode} % \end{macro} % % \begin{macro}[aux] % { % \l_@@_current_line_int, % \l_@@_current_word_int, % \l_@@_current_indentation_int % } % These store the number of characters in the line and word currently % being constructed, and the current indentation, respectively. % \begin{macrocode} \int_new:N \l_@@_current_line_int \int_new:N \l_@@_current_word_int \int_new:N \l_@@_current_indentation_int % \end{macrocode} % \end{macro} % % \begin{macro}[aux] % { % \l_@@_current_line_tl, % \l_@@_current_word_tl, % \l_@@_current_indentation_tl % } % These hold the current line of text and current word, % and a number of spaces for indentation, respectively. % \begin{macrocode} \tl_new:N \l_@@_current_line_tl \tl_new:N \l_@@_current_word_tl \tl_new:N \l_@@_current_indentation_tl % \end{macrocode} %\end{macro} % % \begin{macro}[aux]{\l_@@_wrap_tl} % Used for the expansion step before detokenizing, and for the output % from wrapping text: fully expanded and with lines which are not % overly long. % \begin{macrocode} \tl_new:N \l_@@_wrap_tl % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\l_@@_newline_tl} % The token list inserted to produce the new line, with the % \meta{run-on text}. % \begin{macrocode} \tl_new:N \l_@@_newline_tl % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\l_@@_line_start_bool} % Boolean to avoid adding a space at the beginning of forced newlines, % and to know when to add the indentation. % \begin{macrocode} \bool_new:N \l_@@_line_start_bool % \end{macrocode} % \end{macro} % % \begin{macro}{\c_catcode_other_space_tl} % Lowercase a character with category code $12$ to produce an % \enquote{other} space. We can do everything within the group, % because \cs{tl_const:Nn} defines its argument globally. % \begin{macrocode} \group_begin: \char_set_catcode_other:N \* \char_set_lccode:nn {`\*} {`\ } \tl_to_lowercase:n { \tl_const:Nn \c_catcode_other_space_tl { * } } \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\c_@@_wrap_marker_tl} % \begin{macro}[aux] % { % \c_@@_wrap_end_marker_tl, % \c_@@_wrap_newline_marker_tl, % \c_@@_wrap_indent_marker_tl, % \c_@@_wrap_unindent_marker_tl % } % Every special action of the wrapping code is preceded by % the same recognizable string, \cs{c_@@_wrap_marker_tl}. % Upon seeing that \enquote{word}, the wrapping code reads % one space-delimited argument to know what operation to % perform. The setting of \tn{escapechar} here is not % very important, but makes \cs{c_@@_wrap_marker_tl} look % nicer. % \begin{macrocode} \group_begin: \int_set_eq:NN \tex_escapechar:D \c_minus_one \tl_const:Nx \c_@@_wrap_marker_tl { \tl_to_str:n { \^^I \^^O \^^W \^^_ \^^W \^^R \^^A \^^P } } \group_end: \tl_map_inline:nn { { end } { newline } { indent } { unindent } } { \tl_const:cx { c_@@_wrap_ #1 _marker_tl } { \c_catcode_other_space_tl \c_@@_wrap_marker_tl \c_catcode_other_space_tl #1 \c_catcode_other_space_tl } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\iow_indent:n} % \begin{macro}[aux]{\@@_indent:n, \@@_indent_error:n} % We give a (protected) error definition to \cs{iow_indent:n} % when outside messages. Within wrapped message, it places % the instruction for increasing the indentation before its % argument, and the instruction for unindenting afterwards. % Note that there will be no forced line-break, so the indentation % only changes when the next line is started. % \begin{macrocode} \cs_new:Npx \@@_indent:n #1 { \c_@@_wrap_indent_marker_tl #1 \c_@@_wrap_unindent_marker_tl } \cs_new:Npn \@@_indent_error:n #1 { \__msg_kernel_expandable_error:nn { kernel } { indent-outside-wrapping-code } #1 } \cs_new_protected_nopar:Npn \iow_indent:n { \@@_indent_error:n } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\iow_wrap:nnnN} % \begin{macro}[aux]{\@@_wrap_set:Nx} % The main wrapping function works as follows. First give |\\|, % \verb*|\ | and other formatting commands the correct definition for % messages, before fully-expanding the input. In package mode, the % expansion uses \LaTeXe{}'s \cs{protect} mechanism. Afterwards, set % the newline marker (two assignments to fully expand, then convert % to a string) and its length, and initialize some registers. % There is then a loop over each word in the input, which will do the % actual wrapping. After the loop, the resulting text is passed on to % the function which has been given as a post-processor. % The argument |#4| is available for additional set up steps for % the output. The definition of |\\| and \verb*|\ | use an % \enquote{other} space rather than a normal space, because the latter % might be absorbed by \TeX{} to end a number or other \texttt{f}-type % expansions. The \cs{tl_to_str:N} step converts the \enquote{other} % space back to a normal space. % \begin{macrocode} \cs_new_protected:Npn \iow_wrap:nnnN #1#2#3#4 { \group_begin: \int_set_eq:NN \tex_escapechar:D \c_minus_one \cs_set_nopar:Npx \{ { \token_to_str:N \{ } \cs_set_nopar:Npx \# { \token_to_str:N \# } \cs_set_nopar:Npx \} { \token_to_str:N \} } \cs_set_nopar:Npx \% { \token_to_str:N \% } \cs_set_nopar:Npx \~ { \token_to_str:N \~ } \int_set:Nn \tex_escapechar:D { 92 } \cs_set_eq:NN \\ \c_@@_wrap_newline_marker_tl \cs_set_eq:NN \ \c_catcode_other_space_tl \cs_set_eq:NN \iow_indent:n \@@_indent:n #3 %<*initex> \tl_set:Nx \l_@@_wrap_tl {#1} % %<*package> \@@_wrap_set:Nx \l_@@_wrap_tl {#1} % % \end{macrocode} % To warn users that \cs{iow_indent:n} only works in the first argument % of \cs{iow_wrap:nnnN} reset \cs{iow_indent:n} to its error definition. % This is a bit of a hack to measure the string length of the run on text % without the \pkg{l3str} module (which is still experimental). This should % be replaced once the string module is finalised with something a little % cleaner. % \begin{macrocode} \cs_set_eq:NN \iow_indent:n \@@_indent_error:n \tl_set:Nx \l_@@_newline_tl { \iow_newline: #2 } \tl_set:Nx \l_@@_newline_tl { \tl_to_str:N \l_@@_newline_tl } \tl_replace_all:Nnn \l_@@_newline_tl { ~ } { \c_space_tl } \int_set:Nn \l_@@_target_count_int { \l_iow_line_count_int - \tl_count:N \l_@@_newline_tl + \c_one } \int_zero:N \l_@@_current_indentation_int \tl_clear:N \l_@@_current_indentation_tl \int_zero:N \l_@@_current_line_int \tl_set:Nn \l_@@_current_line_tl { \use_none:n } \bool_set_true:N \l_@@_line_start_bool % \end{macrocode} % After some setup above (in particular the odd setting of the current line % to \cs{use_none:n}), a loop goes through space-delimited words in the % message, recognizing special markers. To make sure that the first line % behaves identically to others, start with a newline marker: the % \cs{use_none:n} above avoids actually getting a new line in the output. % \begin{macrocode} \use:x { \exp_not:n { \tl_clear:N \l_@@_wrap_tl } \@@_wrap_loop:w \tl_to_str:N \c_@@_wrap_newline_marker_tl \tl_to_str:N \l_@@_wrap_tl \tl_to_str:N \c_@@_wrap_end_marker_tl \c_space_tl \c_space_tl \exp_not:N \q_stop } \exp_args:NNo \group_end: #4 \l_@@_wrap_tl } % \end{macrocode} % As using the generic loader will mean that \cs{protected@edef} is % not available, it's not placed directly in the wrap function but is set % up as an auxiliary. In the generic loader this can then be redefined. % \begin{macrocode} %<*package> \cs_new_eq:NN \@@_wrap_set:Nx \protected@edef % % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[aux]{\@@_wrap_loop:w} % The loop grabs one word in the input, and checks whether it is % the special marker, or a normal word. % \begin{macrocode} \cs_new_protected:Npn \@@_wrap_loop:w #1 ~ % { \tl_set:Nn \l_@@_current_word_tl {#1} \tl_if_eq:NNTF \l_@@_current_word_tl \c_@@_wrap_marker_tl { \@@_wrap_special:w } { \@@_wrap_word: } } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_wrap_word:} % \begin{macro}[aux]{\@@_wrap_word_fits:} % \begin{macro}[aux]{\@@_wrap_word_newline:} % For a normal word, update the line count, then test if the current % word would fit in the current line, and call the appropriate function. % If the word fits in the current line, add it to the line, preceded by % a space unless it is the first word of the line. % Otherwise, the current line is added to the result, with the run-on text. % The current word (and its character count) are then put in the new line. % \begin{macrocode} \cs_new_protected_nopar:Npn \@@_wrap_word: { \int_set:Nn \l_@@_current_word_int { \__str_count_ignore_spaces:N \l_@@_current_word_tl } \int_add:Nn \l_@@_current_line_int { \l_@@_current_word_int } \int_compare:nNnTF \l_@@_current_line_int < \l_@@_target_count_int { \@@_wrap_word_fits: } { \@@_wrap_word_newline: } \@@_wrap_loop:w } \cs_new_protected_nopar:Npn \@@_wrap_word_fits: { \bool_if:NTF \l_@@_line_start_bool { \bool_set_false:N \l_@@_line_start_bool \tl_put_right:Nx \l_@@_current_line_tl { \l_@@_current_indentation_tl \l_@@_current_word_tl } \int_add:Nn \l_@@_current_line_int { \l_@@_current_indentation_int } } { \tl_put_right:Nx \l_@@_current_line_tl { ~ \l_@@_current_word_tl } \int_incr:N \l_@@_current_line_int } } \cs_new_protected_nopar:Npn \@@_wrap_word_newline: { \tl_put_right:Nx \l_@@_wrap_tl { \l_@@_current_line_tl \l_@@_newline_tl } \int_set:Nn \l_@@_current_line_int { \l_@@_current_word_int + \l_@@_current_indentation_int } \tl_set:Nx \l_@@_current_line_tl { \l_@@_current_indentation_tl \l_@@_current_word_tl } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[aux]{\@@_wrap_special:w} % \begin{macro}[aux]{\@@_wrap_newline:w} % \begin{macro}[aux]{\@@_wrap_indent:w} % \begin{macro}[aux]{\@@_wrap_unindent:w} % \begin{macro}[aux]{\@@_wrap_end:w} % When the \enquote{special} marker is encountered, % read what operation to perform, as a space-delimited % argument, perform it, and remember to loop. % In fact, to avoid spurious spaces when two special % actions follow each other, we look ahead for another % copy of the marker. % Forced newlines are almost identical to those caused by overflow, % except that here the word is empty. % To indent more, add four spaces to the start of the indentation % token list. To reduce indentation, rebuild the indentation token % list using \cs{prg_replicate:nn}. % At the end, we simply save the last line (without the run-on text), % and prevent the loop. % \begin{macrocode} \cs_new_protected:Npn \@@_wrap_special:w #1 ~ #2 ~ #3 ~ % { \use:c { @@_wrap_#1: } \str_if_eq_x:nnTF { #2~#3 } { ~ \c_@@_wrap_marker_tl } { \@@_wrap_special:w } { \@@_wrap_loop:w #2 ~ #3 ~ } } \cs_new_protected_nopar:Npn \@@_wrap_newline: { \tl_put_right:Nx \l_@@_wrap_tl { \l_@@_current_line_tl \l_@@_newline_tl } \int_zero:N \l_@@_current_line_int \tl_clear:N \l_@@_current_line_tl \bool_set_true:N \l_@@_line_start_bool } \cs_new_protected_nopar:Npx \@@_wrap_indent: { \int_add:Nn \l_@@_current_indentation_int \c_four \tl_put_right:Nx \exp_not:N \l_@@_current_indentation_tl { \c_space_tl \c_space_tl \c_space_tl \c_space_tl } } \cs_new_protected_nopar:Npn \@@_wrap_unindent: { \int_sub:Nn \l_@@_current_indentation_int \c_four \tl_set:Nx \l_@@_current_indentation_tl { \prg_replicate:nn \l_@@_current_indentation_int { ~ } } } \cs_new_protected_nopar:Npn \@@_wrap_end: { \tl_put_right:Nx \l_@@_wrap_tl { \l_@@_current_line_tl } \use_none_delimit_by_q_stop:w } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[aux]{\__str_count_ignore_spaces:N} % \begin{macro}[aux]{\__str_count_ignore_spaces:n} % \begin{macro}[aux]{\__str_count_loop:NNNNNNNNN} % The wrapping code requires to measure the number of character % in each word. This could be done with \cs{tl_count:n}, but % it is ten times faster (literally) to use the code below. % \begin{macrocode} \cs_new_nopar:Npn \__str_count_ignore_spaces:N { \exp_args:No \__str_count_ignore_spaces:n } \cs_new:Npn \__str_count_ignore_spaces:n #1 { \__int_value:w \__int_eval:w \exp_after:wN \__str_count_loop:NNNNNNNNN \tl_to_str:n {#1} { X8 } { X7 } { X6 } { X5 } { X4 } { X3 } { X2 } { X1 } { X0 } \q_stop \__int_eval_end: } \cs_new:Npn \__str_count_loop:NNNNNNNNN #1#2#3#4#5#6#7#8#9 { \if_catcode:w X #9 \exp_after:wN \use_none_delimit_by_q_stop:w \else: 9 + \exp_after:wN \__str_count_loop:NNNNNNNNN \fi: } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Messages} % % \begin{macrocode} \__msg_kernel_new:nnnn { kernel } { file-not-found } { File~'#1'~not~found. } { The~requested~file~could~not~be~found~in~the~current~directory,~ in~the~TeX~search~path~or~in~the~LaTeX~search~path. } \__msg_kernel_new:nnnn { kernel } { input-streams-exhausted } { Input~streams~exhausted } { TeX~can~only~open~up~to~16~input~streams~at~one~time.\\ All~16~are~currently~in~use,~and~something~wanted~to~open~ another~one. } \__msg_kernel_new:nnnn { kernel } { output-streams-exhausted } { Output~streams~exhausted } { TeX~can~only~open~up~to~16~output~streams~at~one~time.\\ All~16~are~currently~in~use,~and~something~wanted~to~open~ another~one. } \__msg_kernel_new:nnnn { kernel } { unbalanced-quote-in-filename } { Unbalanced~quotes~in~file~name~'#1'. } { File~names~must~contain~balanced~numbers~of~quotes~("). } \__msg_kernel_new:nnn { kernel } { indent-outside-wrapping-code } { Only~\iow_wrap:nnnN~(arg~1)~allows~\iow_indent:n } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex