% \iffalse meta-comment % %% File: l3file.dtx % % Copyright (C) 1990-2019 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 % % https://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 development version of the bundle can be found at % % https://github.com/latex3/latex3 % % for those people who are interested. % %<*driver> \documentclass[full,kernel]{l3doc} \begin{document} \DocInput{\jobname.dtx} \PrintIndex \end{document} % % \fi % % \title{^^A % The \pkg{l3file} package\\ File and I/O operations^^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 2019-05-28} % % \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[no-index]{file_\ldots}, while % others are used to work with files on a line by line basis and have prefix % \cs[no-index]{ior_\ldots} (reading) or \cs[no-index]{iow_\ldots} (writing). % % It is important to remember that when reading external files \TeX{} % attempts to locate them using 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. Active characters (as % declared in \cs{l_char_active_seq}) are \emph{not} expanded, % allowing the direct use of these in file names. File names are quoted % using |"| tokens if they contain spaces: as a result, |"| tokens are % \emph{not} permitted in file names. % % \section{Input--output stream management} % % As \TeX{} engines have a limited number of input and 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[no-index]{\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[no-index]{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. % If the file is not found, an error is raised. % \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 clears % 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}[added = 2017-06-27] % { % \ior_show_list:, \ior_log_list:, % \iow_show_list:, \iow_log_list: % } % \begin{syntax} % \cs{ior_show_list:} % \cs{ior_log_list:} % \cs{iow_show_list:} % \cs{iow_log_list:} % \end{syntax} % Display (to the terminal or log file) a list of the file names % associated with each open (read or write) stream. This is intended % for tracking down problems. % \end{function} % % \subsection{Reading from files} % % Reading from files and reading from the terminal are separate processes in % \pkg{expl3}. The functions \cs{ior_get:NN} and \cs{ior_str_get:NN}, and their % branching equivalents, are designed to work with files. % % \begin{function}[noTF, added = 2012-06-24, updated = 2019-03-23]{\ior_get:NN} % \begin{syntax} % \cs{ior_get:NN} \meta{stream} \meta{token list variable} % \cs{ior_get:NNTF} \meta{stream} \meta{token list variable} \meta{true code} \meta{false code} % \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. % The material read from the \meta{stream} is tokenized by \TeX{} % according to the category codes and \tn{endlinechar} in force when % the function is used. Assuming normal settings, any lines which do % not end in a comment character~|%| have the line ending % converted to a space, so for example input % \begin{verbatim} % a b c % \end{verbatim} % results in a token list \verb*|a b c |. Any blank line is % converted to the token \cs{par}. Therefore, blank lines can be % skipped by using 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} % Also notice that if multiple lines are read to match braces % then the resulting token list can contain \cs{par} tokens. % In the non-branching version, where the \meta{stream} is not open % the \meta{tl var} is set to \cs{q_no_value}. % \begin{texnote} % This protected macro is a wrapper around the \TeX{} primitive % \tn{read}. Regardless of settings, \TeX{} replaces trailing space % and tab characters (character codes 32 and~9) in each line by an % end-of-line character (character code \tn{endlinechar}, omitted if % \tn{endlinechar} is negative or too large) before turning % characters into tokens according to current category codes. With % default settings, spaces appearing at the beginning of lines are % also ignored. % \end{texnote} % \end{function} % % \begin{function}[noTF, added = 2016-12-04, updated = 2019-03-23] % {\ior_str_get:NN} % \begin{syntax} % \cs{ior_str_get:NN} \meta{stream} \meta{token list variable} % \cs{ior_str_get:NNTF} \meta{stream} \meta{token list variable} \meta{true code} \meta{false code} % \end{syntax} % Function that reads one line from the input \meta{stream} and stores % the result locally in the \meta{token list} variable. % 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 % always only reads one line and any blank lines in the input % 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} % results in a token list |a b c| with the letters |a|, |b|, and |c| % having category code~12. % In the non-branching version, where the\meta{stream} is not open % the \meta{tl var} is set to \cs{q_no_value}. % \begin{texnote} % This protected macro is a wrapper around the \eTeX{} primitive % \tn{readline}. Regardless of settings, \TeX{} removes trailing % space and tab characters (character codes 32 and~9). However, the % end-line character normally added by this primitive is not % included in the result of \cs{ior_str_get:NN}. % \end{texnote} % \end{function} % % \begin{function}[added = 2012-02-11]{\ior_map_inline:Nn} % \begin{syntax} % \cs{ior_map_inline:Nn} \meta{stream} \Arg{inline function} % \end{syntax} % Applies the \meta{inline function} to each set of \meta{lines} % obtained by calling \cs{ior_get:NN} until reaching the end of the % file. \TeX{} ignores any trailing new-line marker from the file it % reads. The \meta{inline function} should consist of code which % receives the \meta{line} as |#1|. % \end{function} % % \begin{function}[added = 2012-02-11]{\ior_str_map_inline:Nn} % \begin{syntax} % \cs{ior_str_map_inline:Nn} \meta{stream} \Arg{inline function} % \end{syntax} % Applies the \meta{inline function} to every \meta{line} % in the \meta{stream}. 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). The \meta{inline function} should consist of code which % receives the \meta{line} as |#1|. % Note that \TeX{} removes trailing space and tab characters % (character codes 32 and 9) from every line upon input. \TeX{} also % ignores any trailing new-line marker from the file it reads. % \end{function} % % \begin{function}[added = 2012-06-29]{\ior_map_break:} % \begin{syntax} % \cs{ior_map_break:} % \end{syntax} % Used to terminate a \cs[no-index]{ior_map_\ldots} function before all % lines from the \meta{stream} have been processed. This % normally takes place within a conditional statement, for example % \begin{verbatim} % \ior_map_inline:Nn \l_my_ior % { % \str_if_eq:nnTF { #1 } { bingo } % { \ior_map_break: } % { % % Do something useful % } % } % \end{verbatim} % Use outside of a \cs[no-index]{ior_map_\ldots} scenario leads to low % level \TeX{} errors. % \begin{texnote} % When the mapping is broken, additional tokens may be inserted % before further items are taken % from the input stream. This depends on the design of the mapping % function. % \end{texnote} % \end{function} % % \begin{function}[added = 2012-06-29]{\ior_map_break:n} % \begin{syntax} % \cs{ior_map_break:n} \Arg{code} % \end{syntax} % Used to terminate a \cs[no-index]{ior_map_\ldots} function before all % lines in the \meta{stream} have been processed, inserting % the \meta{code} after the mapping has ended. This % normally takes place within a conditional statement, for example % \begin{verbatim} % \ior_map_inline:Nn \l_my_ior % { % \str_if_eq:nnTF { #1 } { bingo } % { \ior_map_break:n { } } % { % % Do something useful % } % } % \end{verbatim} % Use outside of a \cs[no-index]{ior_map_\ldots} scenario leads to low % level \TeX{} errors. % \begin{texnote} % When the mapping is broken, additional tokens may be inserted % before the \meta{code} is % inserted into the input stream. % This depends on the design of the mapping function. % \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 also returns a \texttt{true} value if % the \meta{stream} is not open. % \end{function} % % \subsection{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} are not recognized in % the argument of \cs{iow_shipout:Nn}. This may lead to the % insertion of additional 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} are not recognized in % the argument of \cs{iow_shipout:Nn}. This may lead to the % insertion of additional 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:} is not recognized by % \TeX{}, which may lead to the insertion of additional 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, updated = 2017-12-04] % {\iow_wrap:nnnN, \iow_wrap:nxnN} % \begin{syntax} % \cs{iow_wrap:nnnN} \Arg{text} \Arg{run-on text} \Arg{set up} \meta{function} % \end{syntax} % This function wraps 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} is inserted. The line character count % targeted is the value of \cs{l_iow_line_count_int} minus the % number of characters in the \meta{run-on text} for all lines except % the first, for which the target number of characters is simply % \cs{l_iow_line_count_int} since there is no run-on text. The % \meta{text} and \meta{run-on text} are exhaustively expanded by the % function, with the following substitutions: % \begin{itemize} % \item |\\| or \cs{iow_newline:} 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_allow_break:} may be used to allow a line-break % without inserting a space (this is experimental), % \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 is typically a wrapper around a write % operation. The output of \cs{iow_wrap:nnnN} (\emph{i.e.}~the argument % passed to the \meta{function}) consists of characters of category % \enquote{other} (category code~12), with the exception of spaces which % have category \enquote{space} (category code~10). This means that the % output does \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 does 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 \hologo{MiKTeX} systems. % \end{variable} % % \subsection{Constant input--output streams, and variables} % % \begin{variable}{\c_term_ior} % Constant input stream for reading from the terminal. Reading from this % stream using \cs{ior_get:NN} or similar results in a prompt from % \TeX{} of the form % \begin{verbatim} % = % \end{verbatim} % \end{variable} % % \begin{variable}[added = 2017-12-11]{\g_tmpa_ior, \g_tmpb_ior} % Scratch input stream for global use. These are never used by % the kernel code, and so are safe for use with any \LaTeX3-defined % function. However, they may be overwritten by other non-kernel % code and so should only be used for short-term storage. % \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} % % \begin{variable}[added = 2017-12-11]{\g_tmpa_iow, \g_tmpb_iow} % Scratch output stream for global use. These are never used by % the kernel code, and so are safe for use with any \LaTeX3-defined % function. However, they may be overwritten by other non-kernel % code and so should only be used for short-term storage. % \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} % % \section{File operation functions} % % \begin{variable}[added = 2017-06-21] % { % \g_file_curr_dir_str, % \g_file_curr_name_str, % \g_file_curr_ext_str % } % Contain the directory, name and extension of the current file. The % directory is empty if the file was loaded without an explicit % path (\emph{i.e.}~if it is in the \TeX{} search path), and does % \emph{not} end in |/| other than the case that it is exactly equal % to the root directory. The \meta{name} and \meta{ext} parts together % make up the file name, thus the \meta{name} part may be thought of % as the \enquote{job name} for the current file. Note that \TeX{} does % not provide information on the \meta{ext} part for the main (top % level) file and that this file always has an empty \meta{dir} component. % Also, the \meta{name} here will be equal to \cs{c_sys_jobname_str}, % which may be different from the real file name (if set using % |--jobname|, for example). % \end{variable} % % \begin{variable}[added = 2017-06-18]{\l_file_search_path_seq} % Each entry is the path to a directory which should be searched when % seeking a file. Each path can be relative or absolute, and should % not include the trailing slash. The entries are not expanded when % used so may contain active characters but should not feature any % variable content. Spaces need not be quoted. % % \begin{texnote} % When working as a package in \LaTeXe{}, \pkg{expl3} will % automatically append the current \tn{input@path} to the % set of values from \cs{l_file_search_path_seq}. % \end{texnote} % \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{l_file_search_path_seq}. % \end{function} % % \begin{function}[noTF, added = 2019-01-16, updated = 2019-02-16]{\file_get:nnN} % \begin{syntax} % \cs{file_get:nnN} \Arg{filename} \Arg{setup} \meta{tl} % \cs{file_get:nnNTF} \Arg{filename} \Arg{setup} \meta{tl} \Arg{true code} \Arg{false code} % \end{syntax} % Defines \meta{tl} to the contents of \meta{filename}. % Category codes may need to be set appropriately via the \meta{setup} % argument. % The non-branching version sets the \meta{tl} to \cs{q_no_value} if the file is % not found. The branching version runs the \meta{true code} after the % assignment to \meta{tl} if the file is found, and \meta{false code} % otherwise. % \end{function} % % \begin{function}[noTF, updated = 2019-02-16] % {\file_get_full_name:nN, \file_get_full_name:VN} % \begin{syntax} % \cs{file_get_full_name:nN} \Arg{file name} \meta{tl} % \cs{file_get_full_name:nNTF} \Arg{file name} \meta{tl} \Arg{true code} \Arg{false code} % \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. % This includes an extension |.tex| when the given \meta{file name} % has no extension but the file found has that extension. % In the non-branching version, the \meta{tl var} will be set to % \cs{q_no_value} in the case that the file does not exist. % \end{function} % % \begin{function}[added = 2017-06-23, updated = 2017-06-26] % {\file_parse_full_name:nNNN, \file_parse_full_name:VNNN} % \begin{syntax} % \cs{file_parse_full_name:nNNN} \Arg{full name} \meta{dir} \meta{name} \meta{ext} % \end{syntax} % Parses the \meta{full name} and splits it into three parts, each of % which is returned by setting the appropriate local string variable: % \begin{itemize} % \item The \meta{dir}: everything up to the last |/| (path separator) % in the \meta{file path}. As with system \texttt{PATH} variables % and related functions, the \meta{dir} does \emph{not} include the % trailing |/| unless it points to the root directory. If there is no path (only % a file name), \meta{dir} is empty. % \item The \meta{name}: everything after the last |/| up to the last |.|, % where both of those characters are optional. The \meta{name} may % contain multiple |.| characters. It is empty if \meta{full name} % consists only of a directory name. % \item The \meta{ext}: everything after the last |.| (including the dot). % The \meta{ext} is empty if there is no |.| after the last |/|. % \end{itemize} % This function does not expand the \meta{full name} before turning it % to a string. It assume that the \meta{full name} either contains no % quote (|"|) characters or is surrounded by a pair of quotes. % \end{function} % % \begin{function}[noTF, added = 2017-07-11, updated = 2019-02-16] % {\file_get_mdfive_hash:nN} % \begin{syntax} % \cs{file_get_mdfive_hash:nN} \Arg{file name} \meta{tl var} % \end{syntax} % Searches for \meta{file name} using the current \TeX{} search % path and the additional paths controlled by \cs{l_file_search_path_seq}. % If found, sets the \meta{tl var} to the MD5 sum generated from the % content of the file. The file is read as bytes, which means that in % contrast to most \TeX{} behaviour there will be a difference in result % depending on the line endings used in text files. The same file will % produce the same result between different engines: the algorithm used % is the same in all cases. % Where the file is not found, the \meta{tl var} will set to % \cs{q_no_value}. % \end{function} % % \begin{function}[noTF, added = 2017-07-09, updated = 2019-02-16] % {\file_get_size:nN} % \begin{syntax} % \cs{file_get_size:nN} \Arg{file name} \meta{tl var} % \end{syntax} % Searches for \meta{file name} using the current \TeX{} search % path and the additional paths controlled by \cs{l_file_search_path_seq}. % If found, sets the \meta{tl var} to the size of the file in bytes. % Where the file is not found, the \meta{tl var} will set to \cs{q_no_value}. % This is not available in older versions of \XeTeX{}. % \end{function} % % \begin{function}[noTF, added = 2017-07-09, updated = 2019-02-16] % {\file_get_timestamp:nN} % \begin{syntax} % \cs{file_get_timestamp:nN} \Arg{file name} \meta{tl var} % \end{syntax} % Searches for \meta{file name} using the current \TeX{} search % path and the additional paths controlled by \cs{l_file_search_path_seq}. % If found, sets the \meta{tl var} to the modification timestamp of % the file in the form |D:|\meta{year}\meta{month}\meta{day}\meta{hour}^^A % \meta{minute}\meta{second}\meta{offset}, where the latter may be |Z| % (UTC) or \meta{plus-minus}\meta{hours}|'|\meta{minutes}|'|. % Where the file is not found, the \meta{tl var} will be set to % \cs{q_no_value}. % This is not available in older versions of \XeTeX{}. % \end{function} % % \begin{function}[updated = 2017-06-26]{\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 is raised if the file is not found. % \end{function} % % \begin{function}[added = 2014-07-02]{\file_if_exist_input:n, \file_if_exist_input:nF} % \begin{syntax} % \cs{file_if_exist_input:n} \Arg{file name} % \cs{file_if_exist_input:nF} \Arg{file name} \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}. If found then % reads in the file as additional \LaTeX{} source as described for % \cs{file_input:n}, otherwise inserts the \meta{false code}. % Note that these functions do not raise % an error if the file is not found, in contrast to \cs{file_input:n}. % \end{function} % % \begin{function}[added = 2017-07-07]{\file_input_stop:} % \begin{syntax} % \cs{file_input_stop:} % \end{syntax} % Ends the reading of a file started by \cs{file_input:n} or similar before % the end of the file is reached. Where the file reading is being terminated % due to an error, \cs[index = msg_critical:nn]{msg_critical:nn(nn)} % should be preferred. % \begin{texnote} % This function must be used on a line on its own: \TeX{} reads files % line-by-line and so any additional tokens in the \enquote{current} line % will still be read. % % This is also true if the function is hidden inside another function % (which will be the normal case), i.e., all tokens on the same line % in the source file are still processed. Putting it on a line by itself % in the definition doesn't help as it is the line where it is used that % counts! % \end{texnote} % \end{function} % % \begin{function}{\file_show_list:, \file_log_list:} % \begin{syntax} % \cs{file_show_list:} % \cs{file_log_list:} % \end{syntax} % These functions list all files loaded by \LaTeXe{} commands that % populate \tn{@filelist} or by \cs{file_input:n}. While % \cs{file_show_list:} displays the list in the terminal, % \cs{file_log_list:} outputs it to the log file only. % \end{function} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3file} implementation} % % \TestFiles{m3file001} % % \begin{macrocode} %<*initex|package> % \end{macrocode} % % \subsection{Input operations} % % \begin{macrocode} %<@@=ior> % \end{macrocode} % % \subsubsection{Variables and constants} % % \begin{variable}{\l_@@_internal_tl} % Used as a short-term scratch variable. % \begin{macrocode} \tl_new:N \l_@@_internal_tl % \end{macrocode} % \end{variable} % % \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} \int_const:Nn \c_term_ior { 16 } % \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 \ConTeXt{}, 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:nnn { 0 } { \cs_if_exist:NTF \normalend { \tex_count:D 38 ~ } { \tex_count:D 16 ~ % \cs_if_exist:NT \loccount { - 1 } } } { \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{variable}{\g_tmpa_ior, \g_tmpb_ior} % The usual scratch space. % \begin{macrocode} \ior_new:N \g_tmpa_ior \ior_new:N \g_tmpb_ior % \end{macrocode} % \end{variable} % % \begin{macro}{\ior_open:Nn, \ior_open:cn} % Use the conditional version, with an error if the file is not found. % \begin{macrocode} \cs_new_protected:Npn \ior_open:Nn #1#2 { \ior_open:NnF #1 {#2} { \__kernel_file_missing:n {#2} } } \cs_generate_variant:Nn \ior_open:Nn { c } % \end{macrocode} % \end{macro} % % \begin{variable}{\l_@@_file_name_tl} % Data storage. % \begin{macrocode} \tl_new:N \l_@@_file_name_tl % \end{macrocode} % \end{variable} % % \begin{macro}[TF]{\ior_open:Nn, \ior_open:cn} % An auxiliary searches for the file in the \TeX{}, \LaTeXe{} and % \LaTeX3 paths. Then pass the file found to the lower-level function % which deals with streams. The |full_name| is empty when the file is % not found. % \begin{macrocode} \prg_new_protected_conditional:Npnn \ior_open:Nn #1#2 { T , F , TF } { \file_get_full_name:nNTF {#2} \l_@@_file_name_tl { \__kernel_ior_open:No #1 \l_@@_file_name_tl \prg_return_true: } { \prg_return_false: } } \prg_generate_conditional_variant:Nnn \ior_open:Nn { c } { T , F , TF } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_new:N} % In package mode, streams are reserved using \tn{newread} before they % can be managed by \pkg{ior}. To prevent \pkg{ior} from being % affected by redefinitions of \tn{newread} (such as done by the % third-party package \pkg{morewrites}), this macro is saved here % under a private name. The complicated code ensures that % \cs{@@_new:N} is not \tn{outer} despite plain \TeX{}'s \tn{newread} % being \tn{outer}. For \ConTeXt{}, we have to deal with the fact % that \tn{newread} works like our own: it actually checks before % altering definition. % \begin{macrocode} %<*package> \exp_args:NNf \cs_new_protected:Npn \@@_new:N { \exp_args:NNc \exp_after:wN \exp_stop_f: { newread } } \cs_if_exist:NT \normalend { \cs_new_eq:NN \@@_new_aux:N \@@_new:N \cs_set_protected:Npn \@@_new:N #1 { \cs_undefine:N #1 \@@_new_aux:N #1 } } % % \end{macrocode} % \end{macro} % % \begin{macro}{\__kernel_ior_open:Nn, \__kernel_ior_open:No} % \begin{macro}{\@@_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 plain \TeX{} or \LaTeXe{} % for a new stream and use that number (after a bit of conversion). % \begin{macrocode} \cs_new_protected:Npn \__kernel_ior_open:Nn #1#2 { \ior_close:N #1 \seq_gpop:NNTF \g_@@_streams_seq \l_@@_stream_tl { \@@_open_stream:Nn #1 {#2} } %<*initex> { \__kernel_msg_fatal:nn { kernel } { input-streams-exhausted } } % %<*package> { \@@_new:N #1 \tl_set:Nx \l_@@_stream_tl { \int_eval:n {#1} } \@@_open_stream:Nn #1 {#2} } % } \cs_generate_variant:Nn \__kernel_ior_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 { -1 < #1 < \c_term_ior } { \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_show_list:, \ior_log_list:} % \begin{macro}{\@@_list:N} % Show the property lists, but with some \enquote{pretty printing}. % See the \pkg{l3msg} module. The first argument of the message is % |ior| (as opposed to |iow|) and the second is empty if no read % stream is open and non-empty (the list of streams formatted using % \cs{msg_show_item_unbraced:nn}) otherwise. The code of the message % \texttt{show-streams} takes care of translating |ior|/|iow| to % English. % \begin{macrocode} \cs_new_protected:Npn \ior_show_list: { \@@_list:N \msg_show:nnxxxx } \cs_new_protected:Npn \ior_log_list: { \@@_list:N \msg_log:nnxxxx } \cs_new_protected:Npn \@@_list:N #1 { #1 { LaTeX / kernel } { show-streams } { ior } { \prop_map_function:NN \g_@@_streams_prop \msg_show_item_unbraced:nn } { } { } } % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Reading input} % % \begin{macro}{\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. The primitive test can only deal with % numbers in the range $[0,15]$ so we catch outliers (they are % exhausted). % \begin{macrocode} \prg_new_conditional:Npnn \ior_if_eof:N #1 { p , T , F , TF } { \cs_if_exist:NTF #1 { \int_compare:nTF { -1 < #1 < \c_term_ior } { \if_eof:w #1 \prg_return_true: \else: \prg_return_false: \fi: } { \prg_return_true: } } { \prg_return_true: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\ior_get:NN, \@@_get:NN} % \begin{macro}[TF]{\ior_get:NN} % And here we read from files. % \begin{macrocode} \cs_new_protected:Npn \ior_get:NN #1#2 { \ior_get:NNF #1 #2 { \tl_set:Nn #2 { \q_no_value } } } \cs_new_protected:Npn \@@_get:NN #1#2 { \tex_read:D #1 to #2 } \prg_new_protected_conditional:Npnn \ior_get:NN #1#2 { T , F , TF } { \ior_if_eof:NTF #1 { \prg_return_false: } { \@@_get:NN #1 #2 \prg_return_true: } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\ior_str_get:NN, \@@_str_get:NN} % \begin{macro}[TF]{\ior_str_get:NN} % Reading as strings is a more complicated wrapper, as we wish to % remove the endline character and restore it afterwards. % \begin{macrocode} \cs_new_protected:Npn \ior_str_get:NN #1#2 { \ior_str_get:NNF #1 #2 { \tl_set:Nn #2 { \q_no_value } } } \cs_new_protected:Npn \@@_str_get:NN #1#2 { \exp_args:Nno \use:n { \int_set:Nn \tex_endlinechar:D { -1 } \tex_readline:D #1 to #2 \int_set:Nn \tex_endlinechar:D } { \int_use:N \tex_endlinechar:D } } \prg_new_protected_conditional:Npnn \ior_str_get:NN #1#2 { T , F , TF } { \ior_if_eof:NTF #1 { \prg_return_false: } { \@@_str_get:NN #1 #2 \prg_return_true: } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{variable}{\c_@@_term_ior} % For reading without a prompt. % \begin{macrocode} \int_const:Nn \c_@@_term_ior { -1 } % \end{macrocode} % \end{variable} % % \begin{macro}{\ior_get_term:nN, \ior_str_get_term:nN} % \begin{macro}{\@@_get_term:NnN} % Getting from the terminal is better with pretty-printing. % \begin{macrocode} \cs_new_protected:Npn \ior_get_term:nN #1#2 { \@@_get_term:NnN \@@_get:NN {#1} #2 } \cs_new_protected:Npn \ior_str_get_term:nN #1#2 { \@@_get_term:NnN \@@_str_get:NN {#1} #2 } \cs_new_protected:Npn \@@_get_term:NnN #1#2#3 { \group_begin: \tex_escapechar:D = -1 \scan_stop: \tl_if_blank:nTF {#2} { \exp_args:NNc #1 \c_@@_term_ior } { \exp_args:NNc #1 \c_term_ior } {#2} \exp_args:NNNv \group_end: \tl_set:Nn #3 {#2} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\ior_map_break:, \ior_map_break:n} % Usual map breaking functions. % \begin{macrocode} \cs_new:Npn \ior_map_break: { \prg_map_break:Nn \ior_map_break: { } } \cs_new:Npn \ior_map_break:n { \prg_map_break:Nn \ior_map_break: } % \end{macrocode} % \end{macro} % % \begin{macro}{\ior_map_inline:Nn, \ior_str_map_inline:Nn} % \begin{macro}{\@@_map_inline:NNn} % \begin{macro}{\@@_map_inline:NNNn} % \begin{macro}{\@@_map_inline_loop:NNN} % Mapping to an input stream can be done on either a token or a string % basis, hence the set up. Within that, there is a check to avoid reading % past the end of a file, hence the two applications of \cs{ior_if_eof:N} % and its lower-level analogue \cs{if_eof:w}. % This mapping cannot be nested with twice the same stream, as the % stream has only one \enquote{current line}. % \begin{macrocode} \cs_new_protected:Npn \ior_map_inline:Nn { \@@_map_inline:NNn \@@_get:NN } \cs_new_protected:Npn \ior_str_map_inline:Nn { \@@_map_inline:NNn \@@_str_get:NN } \cs_new_protected:Npn \@@_map_inline:NNn { \int_gincr:N \g__kernel_prg_map_int \exp_args:Nc \@@_map_inline:NNNn { @@_map_ \int_use:N \g__kernel_prg_map_int :n } } \cs_new_protected:Npn \@@_map_inline:NNNn #1#2#3#4 { \cs_gset_protected:Npn #1 ##1 {#4} \ior_if_eof:NF #3 { \@@_map_inline_loop:NNN #1#2#3 } \prg_break_point:Nn \ior_map_break: { \int_gdecr:N \g__kernel_prg_map_int } } \cs_new_protected:Npn \@@_map_inline_loop:NNN #1#2#3 { #2 #3 \l_@@_internal_tl \if_eof:w #3 \exp_after:wN \ior_map_break: \fi: \exp_args:No #1 \l_@@_internal_tl \@@_map_inline_loop:NNN #1#2#3 } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\ior_map_variable:NNn, \ior_str_map_variable:NNn} % \begin{macro}{\@@_map_variable:NNNn} % \begin{macro}{\@@_map_variable_loop:NNNn} % Since the \TeX{} primitive (\tn{read} or \tn{readline}) assigns the % tokens read in the same way as a token list assignment, we simply % call the appropriate primitive. The end-of-loop is checked using % the primitive conditional for speed. % \begin{macrocode} \cs_new_protected:Npn \ior_map_variable:NNn { \@@_map_variable:NNNn \ior_get:NN } \cs_new_protected:Npn \ior_str_map_variable:NNn { \@@_map_variable:NNNn \ior_str_get:NN } \cs_new_protected:Npn \@@_map_variable:NNNn #1#2#3#4 { \ior_if_eof:NF #2 { \@@_map_variable_loop:NNNn #1#2#3 {#4} } \prg_break_point:Nn \ior_map_break: { } } \cs_new_protected:Npn \@@_map_variable_loop:NNNn #1#2#3#4 { #1 #2 #3 \if_eof:w #2 \exp_after:wN \ior_map_break: \fi: #4 \@@_map_variable_loop:NNNn #1#2#3 {#4} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \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}). Recent \LuaTeX{} provide $128$ write % streams; we also use \cs{c_term_iow} as the first non-allowed write % stream so its value depends on the engine. % \begin{macrocode} \int_const:Nn \c_log_iow { -1 } \int_const:Nn \c_term_iow { \bool_lazy_and:nnTF { \sys_if_engine_luatex_p: } { \int_compare_p:nNn \tex_luatexversion:D > { 80 } } { 128 } { 16 } } % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_streams_seq} % A list of the currently-available output streams to be used as a % stack. The stream $18$ is special, as \tn{write}18 is used to % denote commands to be sent to the OS\@. % \begin{macrocode} \seq_new:N \g_@@_streams_seq %<*initex> \exp_args:Nnx \use:n { \seq_gset_split:Nnn \g_@@_streams_seq { } } { \int_step_function:nnN { 0 } { \c_term_iow } \prg_do_nothing: } \int_compare:nNnF \c_term_iow < { 18 } { \seq_gremove_all:Nn \g_@@_streams_seq { 18 } } % % \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:nnn { 0 } { \cs_if_exist:NTF \normalend { \tex_count:D 39 ~ } { \tex_count:D 17 ~ \cs_if_exist:NT \loccount { - 1 } } } { \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{variable}{\g_tmpa_iow, \g_tmpb_iow} % The usual scratch space. % \begin{macrocode} \iow_new:N \g_tmpa_iow \iow_new:N \g_tmpb_iow % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_new:N} % As for read streams, copy \tn{newwrite} in package mode, making sure % that it is not \tn{outer}. % \begin{macrocode} %<*package> \exp_args:NNf \cs_new_protected:Npn \@@_new:N { \exp_args:NNc \exp_after:wN \exp_stop_f: { newwrite } } % % \end{macrocode} % \end{macro} % % \begin{variable}{\l_@@_file_name_tl} % Data storage. % \begin{macrocode} \tl_new:N \l_@@_file_name_tl % \end{macrocode} % \end{variable} % % \begin{macro}{\iow_open:Nn, \iow_open:cn} % \begin{macro}{\@@_open_stream:Nn, \@@_open_stream:NV} % 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 { \__kernel_file_name_sanitize:nN {#2} \l_@@_file_name_tl \iow_close:N #1 \seq_gpop:NNTF \g_@@_streams_seq \l_@@_stream_tl { \@@_open_stream:NV #1 \l_@@_file_name_tl } %<*initex> { \__kernel_msg_fatal:nn { kernel } { output-streams-exhausted } } % %<*package> { \@@_new:N #1 \tl_set:Nx \l_@@_stream_tl { \int_eval:n {#1} } \@@_open_stream:NV #1 \l_@@_file_name_tl } % } \cs_generate_variant:Nn \iow_open:Nn { c } \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: } \cs_generate_variant:Nn \@@_open_stream:Nn { NV } % \end{macrocode} % \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_log_iow < #1 < \c_term_iow } { \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_iow } } \cs_generate_variant:Nn \iow_close:N { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\iow_show_list:, \iow_log_list:} % \begin{macro}{\@@_list:N} % Done as for input, but with a copy of the auxiliary so the name is correct. % \begin{macrocode} \cs_new_protected:Npn \iow_show_list: { \@@_list:N \msg_show:nnxxxx } \cs_new_protected:Npn \iow_log_list: { \@@_list:N \msg_log:nnxxxx } \cs_new_protected:Npn \@@_list:N #1 { #1 { LaTeX / kernel } { show-streams } { iow } { \prop_map_function:NN \g_@@_streams_prop \msg_show_item_unbraced: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}{\__kernel_iow_with:Nnn} % \begin{macro}{\@@_with: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 \__kernel_iow_with:Nnn #1#2 { \int_compare:nNnTF {#1} = {#2} { \use:n } { \exp_args:No \@@_with:nNnn { \int_use:N #1 } #1 {#2} } } \cs_new_protected:Npn \@@_with:nNnn #1#2#3#4 { \int_set:Nn #2 {#3} #4 \int_set:Nn #2 {#1} } % \end{macrocode} % \end{macro} % \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{__kernel_iow_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 { \__kernel_iow_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:Npn \iow_log:x { \iow_now:Nx \c_log_iow } \cs_new_protected:Npn \iow_log:n { \iow_now:Nn \c_log_iow } \cs_set_protected:Npn \iow_term:x { \iow_now:Nx \c_term_iow } \cs_new_protected: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: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{variable}{\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 \hologo{MiKTeX}. % \begin{macrocode} \int_new:N \l_iow_line_count_int \int_set:Nn \l_iow_line_count_int { 78 } % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_newline_tl} % The token list inserted to produce a new line, with the % \meta{run-on text}. % \begin{macrocode} \tl_new:N \l_@@_newline_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_line_target_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_@@_line_target_int % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_set_indent:n} % \begin{macro}{\@@_unindent:w} % \begin{variable}{\l_@@_one_indent_tl, \l_@@_one_indent_int} % The \texttt{one_indent} variables hold one indentation marker and % its length. The \cs{@@_unindent:w} auxiliary removes one % indentation. The function \cs{@@_set_indent:n} (that could possibly % be public) sets the indentation in a consistent way. We set it to % four spaces by default. % \begin{macrocode} \tl_new:N \l_@@_one_indent_tl \int_new:N \l_@@_one_indent_int \cs_new:Npn \@@_unindent:w { } \cs_new_protected:Npn \@@_set_indent:n #1 { \tl_set:Nx \l_@@_one_indent_tl { \exp_args:No \__kernel_str_to_other_fast:n { \tl_to_str:n {#1} } } \int_set:Nn \l_@@_one_indent_int { \str_count:N \l_@@_one_indent_tl } \exp_last_unbraced:NNo \cs_set:Npn \@@_unindent:w \l_@@_one_indent_tl { } } \exp_args:Nx \@@_set_indent:n { \prg_replicate:nn { 4 } { ~ } } % \end{macrocode} % \end{variable} % \end{macro} % \end{macro} % % \begin{variable}{\l_@@_indent_tl, \l_@@_indent_int} % The current indentation (some copies of \cs{l_@@_one_indent_tl}) and % its number of characters. % \begin{macrocode} \tl_new:N \l_@@_indent_tl \int_new:N \l_@@_indent_int % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_line_tl, \l_@@_line_part_tl} % These hold the current line of text and a partial line to be added % to it, respectively. % \begin{macrocode} \tl_new:N \l_@@_line_tl \tl_new:N \l_@@_line_part_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_line_break_bool} % Indicates whether the line was broken precisely at a chunk boundary. % \begin{macrocode} \bool_new:N \l_@@_line_break_bool % \end{macrocode} % \end{variable} % % \begin{variable}{\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{variable} % % \begin{variable}{\c_@@_wrap_marker_tl} % \begin{variable} % { % \c_@@_wrap_end_marker_tl, % \c_@@_wrap_newline_marker_tl, % \c_@@_wrap_allow_break_marker_tl, % \c_@@_wrap_indent_marker_tl, % \c_@@_wrap_unindent_marker_tl % } % Every special action of the wrapping code is starts with % 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 % marginally nicer. % \begin{macrocode} \group_begin: \int_set:Nn \tex_escapechar:D { -1 } \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 } { allow_break } { indent } { unindent } } { \tl_const:cx { c_@@_wrap_ #1 _marker_tl } { \c_@@_wrap_marker_tl #1 \c_catcode_other_space_tl } } % \end{macrocode} % \end{variable} % \end{variable} % % \begin{macro}{\iow_allow_break:} % \begin{macro}[EXP]{\@@_allow_break:} % \begin{macro}[EXP]{\@@_allow_break_error:} % We set \cs{iow_allow_break:n} to produce an error when outside % messages. Within wrapped message, it is set to \cs{@@_allow_break:} % when valid and otherwise to \cs{@@_allow_break_error:}. The second % produces an error expandably. % \begin{macrocode} \cs_new_protected:Npn \iow_allow_break: { \__kernel_msg_error:nnnn { kernel } { iow-indent } { \iow_wrap:nnnN } { \iow_allow_break: } } \cs_new:Npx \@@_allow_break: { \c_@@_wrap_allow_break_marker_tl } \cs_new:Npn \@@_allow_break_error: { \__kernel_msg_expandable_error:nnnn { kernel } { iow-indent } { \iow_wrap:nnnN } { \iow_allow_break: } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\iow_indent:n} % \begin{macro}[EXP]{\@@_indent:n} % \begin{macro}[EXP]{\@@_indent_error:n} % We set \cs{iow_indent:n} to produce an error when outside % messages. Within wrapped message, it is set to \cs{@@_indent:n} when % valid and otherwise to \cs{@@_indent_error:n}. The first places the % instruction for increasing the indentation before its argument, and % the instruction for unindenting afterwards. The second produces an % error expandably. Note that there are no forced line-break, so % the indentation only changes when the next line is started. % \begin{macrocode} \cs_new_protected:Npn \iow_indent:n #1 { \__kernel_msg_error:nnnnn { kernel } { iow-indent } { \iow_wrap:nnnN } { \iow_indent:n } {#1} #1 } \cs_new:Npx \@@_indent:n #1 { \c_@@_wrap_indent_marker_tl #1 \c_@@_wrap_unindent_marker_tl } \cs_new:Npn \@@_indent_error:n #1 { \__kernel_msg_expandable_error:nnnnn { kernel } { iow-indent } { \iow_wrap:nnnN } { \iow_indent:n } {#1} #1 } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\iow_wrap:nnnN, \iow_wrap:nxnN} % The main wrapping function works as follows. First give |\\|, % \verb*|\ | and other formatting commands the correct definition for % messages and perform the given setup~|#3|. The definition of % \verb*|\ | uses 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. % Use \cs{conditionally@traceoff} if defined; it is introduced by the % \pkg{trace} package and suppresses uninteresting tracing of the % wrapping code. % \begin{macrocode} \cs_new_protected:Npn \iow_wrap:nnnN #1#2#3#4 { \group_begin: % \use:c { conditionally@traceoff } \int_set:Nn \tex_escapechar:D { -1 } \cs_set:Npx \{ { \token_to_str:N \{ } \cs_set:Npx \# { \token_to_str:N \# } \cs_set:Npx \} { \token_to_str:N \} } \cs_set:Npx \% { \token_to_str:N \% } \cs_set:Npx \~ { \token_to_str:N \~ } \int_set:Nn \tex_escapechar:D { 92 } \cs_set_eq:NN \\ \iow_newline: \cs_set_eq:NN \ \c_catcode_other_space_tl \cs_set_eq:NN \iow_allow_break: \@@_allow_break: \cs_set_eq:NN \iow_indent:n \@@_indent:n #3 % \end{macrocode} % Then fully-expand the input: in package mode, the expansion uses % \LaTeXe{}'s \tn{protect} mechanism in the same way as \tn{typeout}. % In generic mode this setting is useless but harmless. As soon as % the expansion is done, reset \cs{iow_indent:n} to its error % definition: it only works in the first argument of % \cs{iow_wrap:nnnN}. % \begin{macrocode} % \cs_set_eq:NN \protect \token_to_str:N \tl_set:Nx \l_@@_wrap_tl {#1} \cs_set_eq:NN \iow_allow_break: \@@_allow_break_error: \cs_set_eq:NN \iow_indent:n \@@_indent_error:n % \end{macrocode} % Afterwards, set the newline marker (two assignments to fully expand, % then convert to a string) and initialize the target count for lines % (the first line has target count \cs{l_iow_line_count_int} instead). % \begin{macrocode} \tl_set:Nx \l_@@_newline_tl { \iow_newline: #2 } \tl_set:Nx \l_@@_newline_tl { \tl_to_str:N \l_@@_newline_tl } \int_set:Nn \l_@@_line_target_int { \l_iow_line_count_int - \str_count:N \l_@@_newline_tl + 1 } % \end{macrocode} % Sanity check. % \begin{macrocode} \int_compare:nNnT { \l_@@_line_target_int } < 0 { \tl_set:Nn \l_@@_newline_tl { \iow_newline: } \int_set:Nn \l_@@_line_target_int { \l_iow_line_count_int + 1 } } % \end{macrocode} % There is then a loop over the input, which stores the wrapped % result in \cs{l_@@_wrap_tl}. After the loop, the resulting text is % passed on to the function which has been given as a post-processor. % The \cs{tl_to_str:N} step converts the \enquote{other} spaces back % to normal spaces. The \texttt{f}-expansion removes a leading space % from \cs{l_@@_wrap_tl}. % \begin{macrocode} \@@_wrap_do: \exp_args:NNf \group_end: #4 { \tl_to_str:N \l_@@_wrap_tl } } \cs_generate_variant:Nn \iow_wrap:nnnN { nx } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_wrap_do:, \@@_wrap_fix_newline:w, \@@_wrap_start:w} % Escape spaces and change newlines to \cs{c_@@_wrap_newline_marker_tl}. % Set up a few variables, in particular the initial % value of \cs{l_@@_wrap_tl}: the space stops the % \texttt{f}-expansion of the main wrapping function and % \cs{use_none:n} removes a newline marker inserted by later code. % The main loop consists of repeatedly calling the \texttt{chunk} % auxiliary to wrap chunks delimited by (newline or indentation) % markers. % \begin{macrocode} \cs_new_protected:Npn \@@_wrap_do: { \tl_set:Nx \l_@@_wrap_tl { \exp_args:No \__kernel_str_to_other_fast:n \l_@@_wrap_tl \c_@@_wrap_end_marker_tl } \tl_set:Nx \l_@@_wrap_tl { \exp_after:wN \@@_wrap_fix_newline:w \l_@@_wrap_tl ^^J \q_nil ^^J \q_stop } \exp_after:wN \@@_wrap_start:w \l_@@_wrap_tl } \cs_new:Npn \@@_wrap_fix_newline:w #1 ^^J #2 ^^J { #1 \if_meaning:w \q_nil #2 \use_i_delimit_by_q_stop:nw \fi: \c_@@_wrap_newline_marker_tl \@@_wrap_fix_newline:w #2 ^^J } \cs_new_protected:Npn \@@_wrap_start:w { \bool_set_false:N \l_@@_line_break_bool \tl_clear:N \l_@@_line_tl \tl_clear:N \l_@@_line_part_tl \tl_set:Nn \l_@@_wrap_tl { ~ \use_none:n } \int_zero:N \l_@@_indent_int \tl_clear:N \l_@@_indent_tl \@@_wrap_chunk:nw { \l_iow_line_count_int } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_wrap_chunk:nw, \@@_wrap_next:nw} % The \texttt{chunk} and \texttt{next} auxiliaries are defined % indirectly to obtain the expansions of \cs{c_catcode_other_space_tl} % and \cs{c_@@_wrap_marker_tl} in their definition. The \texttt{next} % auxiliary calls a function corresponding to the type of marker (its % |##2|), which can be \texttt{newline} or \texttt{indent} or % \texttt{unindent} or \texttt{end}. The first argument of the % \texttt{chunk} auxiliary is a target number of characters and the % second is some string to wrap. If the chunk is empty simply call % \texttt{next}. Otherwise, set up a call to \cs{@@_wrap_line:nw}, % including the indentation if the current line is empty, and % including a trailing space (|#1|) before the % \cs{@@_wrap_end_chunk:w} auxiliary. % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:w #1#2 { \cs_new_protected:Npn \@@_wrap_chunk:nw ##1##2 #2 { \tl_if_empty:nTF {##2} { \tl_clear:N \l_@@_line_part_tl \@@_wrap_next:nw {##1} } { \tl_if_empty:NTF \l_@@_line_tl { \@@_wrap_line:nw { \l_@@_indent_tl } ##1 - \l_@@_indent_int ; } { \@@_wrap_line:nw { } ##1 ; } ##2 #1 \@@_wrap_end_chunk:w 7 6 5 4 3 2 1 0 \q_stop } } \cs_new_protected:Npn \@@_wrap_next:nw ##1##2 #1 { \use:c { @@_wrap_##2:n } {##1} } } \exp_args:NVV \@@_tmp:w \c_catcode_other_space_tl \c_@@_wrap_marker_tl % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_wrap_line:nw} % \begin{macro}[EXP] % { % \@@_wrap_line_loop:w, % \@@_wrap_line_aux:Nw, % \@@_wrap_line_seven:nnnnnnn, % \@@_wrap_line_end:NnnnnnnnN, % \@@_wrap_line_end:nw, % \@@_wrap_end_chunk:w % } % This is followed by \Arg{string} \meta{intexpr} |;|. It stores the % \meta{string} and up to \meta{intexpr} characters from the current % chunk into \cs{l_@@_line_part_tl}. Characters are grabbed 8~at a % time and left in \cs{l_@@_line_part_tl} by the \texttt{line_loop} % auxiliary. When $k<8$ remain to be found, the \texttt{line_aux} % auxiliary calls the \texttt{line_end} auxiliary followed by (the % single digit) $k$, then $7-k$ empty brace groups, then the chunk's % remaining characters. The \texttt{line_end} auxiliary leaves % $k$~characters from the chunk in the line part, then ends the % assignment. Ignore the \cs{use_none:nnnnn} line for now. If the % next character is a space the line can be broken there: % store what we found into the result and get the next line. % Otherwise some work is needed to find a break-point. So far we have % ignored what happens if the chunk is shorter than the requested % number of characters: this is dealt with by the \texttt{end_chunk} % auxiliary, which gets treated like a character by the rest of the % code. It ends up being called either as one of the arguments % |#2|--|#9| of the \texttt{line_loop} auxiliary or as one of the % arguments |#2|--|#8| of the \texttt{line_end} auxiliary. In both % cases stop the assignment and work out how many characters are still % needed. Notice that when we have exactly seven arguments to clean up, % a \cs{exp_stop_f:} has to be inserted to stop the \cs{exp:w}. % The weird \cs{use_none:nnnnn} ensures that the required % data is in the right place. % \begin{macrocode} \cs_new_protected:Npn \@@_wrap_line:nw #1 { \tex_edef:D \l_@@_line_part_tl { \if_false: } \fi: #1 \exp_after:wN \@@_wrap_line_loop:w \int_value:w \int_eval:w } \cs_new:Npn \@@_wrap_line_loop:w #1 ; #2#3#4#5#6#7#8#9 { \if_int_compare:w #1 < 8 \exp_stop_f: \@@_wrap_line_aux:Nw #1 \fi: #2 #3 #4 #5 #6 #7 #8 #9 \exp_after:wN \@@_wrap_line_loop:w \int_value:w \int_eval:w #1 - 8 ; } \cs_new:Npn \@@_wrap_line_aux:Nw #1#2#3 \exp_after:wN #4 ; { #2 \exp_after:wN \@@_wrap_line_end:NnnnnnnnN \exp_after:wN #1 \exp:w \exp_end_continue_f:w \exp_after:wN \exp_after:wN \if_case:w #1 \exp_stop_f: \prg_do_nothing: \or: \use_none:n \or: \use_none:nn \or: \use_none:nnn \or: \use_none:nnnn \or: \use_none:nnnnn \or: \use_none:nnnnnn \or: \@@_wrap_line_seven:nnnnnnn \fi: { } { } { } { } { } { } { } #3 } \cs_new:Npn \@@_wrap_line_seven:nnnnnnn #1#2#3#4#5#6#7 { \exp_stop_f: } \cs_new:Npn \@@_wrap_line_end:NnnnnnnnN #1#2#3#4#5#6#7#8#9 { #2 #3 #4 #5 #6 #7 #8 \use_none:nnnnn \int_eval:w 8 - ; #9 \token_if_eq_charcode:NNTF \c_space_token #9 { \@@_wrap_line_end:nw { } } { \if_false: { \fi: } \@@_wrap_break:w #9 } } \cs_new:Npn \@@_wrap_line_end:nw #1 { \if_false: { \fi: } \@@_wrap_store_do:n {#1} \@@_wrap_next_line:w } \cs_new:Npn \@@_wrap_end_chunk:w #1 \int_eval:w #2 - #3 ; #4#5 \q_stop { \if_false: { \fi: } \exp_args:Nf \@@_wrap_next:nw { \int_eval:n { #2 - #4 } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_wrap_break:w} % \begin{macro}[EXP] % { % \@@_wrap_break_first:w, % \@@_wrap_break_none:w, % \@@_wrap_break_loop:w, % \@@_wrap_break_end:w, % } % Functions here are defined indirectly: \cs{@@_tmp:w} is eventually % called with an \enquote{other} space as its argument. The goal is % to remove from \cs{l_@@_line_part_tl} the part after the last space. % In most cases this is done by repeatedly calling the % \texttt{break_loop} auxiliary, which leaves \enquote{words} % (delimited by spaces) until it hits the trailing space: then its % argument |##3| is |?| \cs{@@_wrap_break_end:w} instead of a single % token, and that \texttt{break_end} auxiliary leaves in the % assignment the line until the last space, then calls % \cs{@@_wrap_line_end:nw} to finish up the line and move on to the % next. If there is no space in \cs{l_@@_line_part_tl} then the % \texttt{break_first} auxiliary calls the \texttt{break_none} % auxiliary. In that case, if the current line is empty, the complete % word (including |##4|, characters beyond what we had grabbed) is % added to the line, making it over-long. Otherwise, the word is % used for the following line (and the last space of the line so far % is removed because it was inserted due to the presence of a marker). % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:w #1 { \cs_new:Npn \@@_wrap_break:w { \tex_edef:D \l_@@_line_part_tl { \if_false: } \fi: \exp_after:wN \@@_wrap_break_first:w \l_@@_line_part_tl #1 { ? \@@_wrap_break_end:w } \q_mark } \cs_new:Npn \@@_wrap_break_first:w ##1 #1 ##2 { \use_none:nn ##2 \@@_wrap_break_none:w \@@_wrap_break_loop:w ##1 #1 ##2 } \cs_new:Npn \@@_wrap_break_none:w ##1##2 #1 ##3 \q_mark ##4 #1 { \tl_if_empty:NTF \l_@@_line_tl { ##2 ##4 \@@_wrap_line_end:nw { } } { \@@_wrap_line_end:nw { \@@_wrap_trim:N } ##2 ##4 #1 } } \cs_new:Npn \@@_wrap_break_loop:w ##1 #1 ##2 #1 ##3 { \use_none:n ##3 ##1 #1 \@@_wrap_break_loop:w ##2 #1 ##3 } \cs_new:Npn \@@_wrap_break_end:w ##1 #1 ##2 ##3 #1 ##4 \q_mark { ##1 \@@_wrap_line_end:nw { } ##3 } } \exp_args:NV \@@_tmp:w \c_catcode_other_space_tl % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_wrap_next_line:w} % The special case where the end of a line coincides with the end of a % chunk is detected here, to avoid a spurious empty line. Otherwise, % call \cs{@@_wrap_line:nw} to find characters for the next line % (remembering to account for the indentation). % \begin{macrocode} \cs_new_protected:Npn \@@_wrap_next_line:w #1#2 \q_stop { \tl_clear:N \l_@@_line_tl \token_if_eq_meaning:NNTF #1 \@@_wrap_end_chunk:w { \tl_clear:N \l_@@_line_part_tl \bool_set_true:N \l_@@_line_break_bool \@@_wrap_next:nw { \l_@@_line_target_int } } { \@@_wrap_line:nw { \l_@@_indent_tl } \l_@@_line_target_int - \l_@@_indent_int ; #1 #2 \q_stop } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_wrap_allow_break:n} % This is called after a chunk has been wrapped. The % \cs{l_@@_line_part_tl} typically ends with a space (except at the % beginning of a line?), which we remove since the % \texttt{allow\_break} marker should not insert a space. Then move % on with the next chunk, making sure to adjust the target number of % characters for the line in case we did remove a space. % \begin{macrocode} \cs_new_protected:Npn \@@_wrap_allow_break:n #1 { \tl_set:Nx \l_@@_line_tl { \l_@@_line_tl \@@_wrap_trim:N \l_@@_line_part_tl } \bool_set_false:N \l_@@_line_break_bool \tl_if_empty:NTF \l_@@_line_part_tl { \@@_wrap_chunk:nw {#1} } { \exp_args:Nf \@@_wrap_chunk:nw { \int_eval:n { #1 + 1 } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_wrap_indent:n, \@@_wrap_unindent:n} % These functions are called after a chunk has been wrapped, when % encountering \texttt{indent}/\texttt{unindent} markers. Add the % line part (last line part of the previous chunk) to the line so far % and reset a boolean denoting the presence of a line-break. Most % importantly, add or remove one indent from the current indent (both % the integer and the token list). Finally, continue wrapping. % \begin{macrocode} \cs_new_protected:Npn \@@_wrap_indent:n #1 { \tl_put_right:Nx \l_@@_line_tl { \l_@@_line_part_tl } \bool_set_false:N \l_@@_line_break_bool \int_add:Nn \l_@@_indent_int { \l_@@_one_indent_int } \tl_put_right:No \l_@@_indent_tl { \l_@@_one_indent_tl } \@@_wrap_chunk:nw {#1} } \cs_new_protected:Npn \@@_wrap_unindent:n #1 { \tl_put_right:Nx \l_@@_line_tl { \l_@@_line_part_tl } \bool_set_false:N \l_@@_line_break_bool \int_sub:Nn \l_@@_indent_int { \l_@@_one_indent_int } \tl_set:Nx \l_@@_indent_tl { \exp_after:wN \@@_unindent:w \l_@@_indent_tl } \@@_wrap_chunk:nw {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_wrap_newline:n, \@@_wrap_end:n} % These functions are called after a chunk has been line-wrapped, when % encountering a \texttt{newline}/\texttt{end} marker. Unless we just % took a line-break, store the line part and the line so far into the % whole \cs{l_@@_wrap_tl}, trimming a trailing space. In the % \texttt{newline} case look for a new line (of length % \cs{l_@@_line_target_int}) in a new chunk. % \begin{macrocode} \cs_new_protected:Npn \@@_wrap_newline:n #1 { \bool_if:NF \l_@@_line_break_bool { \@@_wrap_store_do:n { \@@_wrap_trim:N } } \bool_set_false:N \l_@@_line_break_bool \@@_wrap_chunk:nw { \l_@@_line_target_int } } \cs_new_protected:Npn \@@_wrap_end:n #1 { \bool_if:NF \l_@@_line_break_bool { \@@_wrap_store_do:n { \@@_wrap_trim:N } } \bool_set_false:N \l_@@_line_break_bool } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_wrap_store_do:n} % First add the last line part to the line, then append it to % \cs{l_@@_wrap_tl} with the appropriate new line (with % \enquote{run-on} text), possibly with its last space removed (|#1| % is empty or \cs{@@_wrap_trim:N}). % \begin{macrocode} \cs_new_protected:Npn \@@_wrap_store_do:n #1 { \tl_set:Nx \l_@@_line_tl { \l_@@_line_tl \l_@@_line_part_tl } \tl_set:Nx \l_@@_wrap_tl { \l_@@_wrap_tl \l_@@_newline_tl #1 \l_@@_line_tl } \tl_clear:N \l_@@_line_tl } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_wrap_trim:N, \@@_wrap_trim:w, \@@_wrap_trim_aux:w} % Remove one trailing \enquote{other} space from the argument if present. % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:w #1 { \cs_new:Npn \@@_wrap_trim:N ##1 { \exp_after:wN \@@_wrap_trim:w ##1 \q_mark #1 \q_mark \q_stop } \cs_new:Npn \@@_wrap_trim:w ##1 #1 \q_mark { \@@_wrap_trim_aux:w ##1 \q_mark } \cs_new:Npn \@@_wrap_trim_aux:w ##1 \q_mark ##2 \q_stop {##1} } \exp_args:NV \@@_tmp:w \c_catcode_other_space_tl % \end{macrocode} % \end{macro} % % \begin{macrocode} %<@@=file> % \end{macrocode} % % \subsection{File operations} % % \begin{variable}{\l_@@_internal_tl} % Used as a short-term scratch variable. % \begin{macrocode} \tl_new:N \l_@@_internal_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_internal_ior} % A reserved stream to test for file existence. % \begin{macrocode} \ior_new:N \g_@@_internal_ior % \end{macrocode} % \end{variable} % % \begin{variable} % { % \g_file_curr_dir_str , % \g_file_curr_ext_str , % \g_file_curr_name_str % } % 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 % run. In \LaTeXe{} package mode the current file name is collected from % \tn{@currname}. % \begin{macrocode} \str_new:N \g_file_curr_dir_str \str_new:N \g_file_curr_ext_str \str_new:N \g_file_curr_name_str %<*initex> \tex_everyjob:D \exp_after:wN { \tex_the:D \tex_everyjob:D \str_gset:Nx \g_file_curr_name_str { \tex_jobname:D } } % %<*package> \cs_if_exist:NT \@currname { \str_gset_eq:NN \g_file_curr_name_str \@currname } % % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_stack_seq} % The input list of files is stored as a sequence stack. In package % mode we can recover the information from the details held by % \LaTeXe{} (we must be in the preamble and loaded using \tn{usepackage} % or \tn{RequirePackage}). As \LaTeXe{} doesn't store directory and % name separately, we stick to the same convention here. % \begin{macrocode} \seq_new:N \g_@@_stack_seq %<*package> \group_begin: \cs_set_protected:Npn \@@_tmp:w #1#2#3 { \tl_if_blank:nTF {#1} { \cs_set:Npn \@@_tmp:w ##1 " ##2 " ##3 \q_stop { { } {##2} { } } \seq_gput_right:Nx \g_@@_stack_seq { \exp_after:wN \@@_tmp:w \tex_jobname:D " \tex_jobname:D " \q_stop } } { \seq_gput_right:Nn \g_@@_stack_seq { { } {#1} {#2} } \@@_tmp:w } } \cs_if_exist:NT \@currnamestack { \exp_after:wN \@@_tmp:w \@currnamestack } \group_end: % % \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_curr_name_str } % % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_base_name_tl, \l_@@_full_name_tl} % For storing the basename and full path whilst passing data internally. % \begin{macrocode} \tl_new:N \l_@@_base_name_tl \tl_new:N \l_@@_full_name_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_dir_str, \l_@@_ext_str, \l_@@_name_str} % Used in parsing a path into parts: in contrast to the above, these are % never used outside of the current module. % \begin{macrocode} \str_new:N \l_@@_dir_str \str_new:N \l_@@_ext_str \str_new:N \l_@@_name_str % \end{macrocode} % \end{variable} % % \begin{variable}{\l_file_search_path_seq} % The current search path. % \begin{macrocode} \seq_new:N \l_file_search_path_seq % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_tmp_seq} % Scratch space for comma list conversion in package mode. % \begin{macrocode} %<*package> \seq_new:N \l_@@_tmp_seq % % \end{macrocode} % \end{variable} % % \begin{macro}{\__kernel_file_name_sanitize:nN} % \begin{macro}{\@@_name_quote:nN} % \begin{macro}{\@@_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 \__kernel_file_name_sanitize:nN #1#2 { \group_begin: \seq_map_inline:Nn \l_char_active_seq { \tl_set:Nx \l_@@_internal_tl { \iow_char:N ##1 } \char_set_active_eq:NN ##1 \l_@@_internal_tl } \tl_set:Nx \l_@@_internal_tl {#1} \exp_args:NNNV \group_end: \str_set:Nn #2 \l_@@_internal_tl } \cs_new_protected:Npn \@@_name_quote:nN #1#2 { \str_set:Nx #2 {#1} \int_if_even:nF { 0 \tl_map_function:NN #2 \@@_name_quote_aux:n } { \__kernel_msg_error:nnx { kernel } { unbalanced-quote-in-filename } {#2} } \tl_remove_all:Nn #2 { " } \tl_if_in:NnT #2 { ~ } { \str_set:Nx #2 { " \exp_not:V #2 " } } } \cs_new:Npn \@@_name_quote_aux:n #1 { \token_if_eq_charcode:NNT #1 " { + 1 } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{variable}{\c_@@_marker_tl} % The same idea as the marker for rescanning token lists: this pair of % tokens cannot appear in a file that is being input. % \begin{macrocode} \tl_const:Nx \c_@@_marker_tl { : \token_to_str:N : } % \end{macrocode} % \end{variable} % % \begin{macro}[TF]{\file_get:nnN} % \begin{macro}{\file_get:nnN,\@@_get_aux:nnN,\@@_get_do:Nw} % The approach here is similar to that for \cs{tl_set_rescan:Nnn}. % The file contents are grabbed as an argument delimited by % \cs{c_@@_marker_tl}. A few subtleties: braces in \cs{if_false:} % \ldots{} \cs{fi:} to deal with possible alignment tabs, % \tn{tracingnesting} to avoid a warning about a group being closed % inside the \tn{scantokens}, and \cs{prg_return_true:} is placed % after the end-of-file marker. % \begin{macrocode} \cs_new_protected:Npn \file_get:nnN #1#2#3 { \file_get:nnNF {#1} {#2} #3 { \tl_set:Nn #3 { \q_no_value } } } \prg_new_protected_conditional:Npnn \file_get:nnN #1#2#3 { T , F , TF } { \file_get_full_name:nNTF {#1} \l_@@_full_name_tl { \exp_args:NV \@@_get_aux:nnN \l_@@_full_name_tl {#2} #3 \prg_return_true: } { \prg_return_false: } } \cs_new_protected:Npn \@@_get_aux:nnN #1#2#3 { \if_false: { \fi: \group_begin: \int_set_eq:NN \tex_tracingnesting:D \c_zero_int \exp_args:No \tex_everyeof:D { \c_@@_marker_tl } #2 \scan_stop: \exp_after:wN \@@_get_do:Nw \exp_after:wN #3 \exp_after:wN \prg_do_nothing: \tex_input:D #1 \scan_stop: \if_false: } \fi: } \exp_args:Nno \use:nn { \cs_new_protected:Npn \@@_get_do:Nw #1#2 } { \c_@@_marker_tl } { \group_end: \tl_set:No #1 {#2} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\file_get_full_name:nN, \file_get_full_name:VN} % \begin{macro}[TF]{\file_get_full_name:nN, \file_get_full_name:VN} % \begin{macro}{\@@_get_full_name_search:nN} % The way to test if a file exists is to try to open it: if it does % not exist then \TeX{} reports end-of-file. A search is made % looking at each potential path in turn (starting from the current % directory). The first location is of course treated as the correct % one: this is done by jumping to \cs{prg_break_point:}. If nothing % is found, |#2| is returned empty. A special case when there is no % extension is that once the first location is found we test the % existence of the file with |.tex| extension in that directory, and % if it exists we include the |.tex| extension in the result. % \begin{macrocode} \cs_new_protected:Npn \file_get_full_name:nN #1#2 { \file_get_full_name:nNF {#1} #2 { \tl_set:Nn #2 { \q_no_value } } } \cs_generate_variant:Nn \file_get_full_name:nN { V } \prg_new_protected_conditional:Npnn \file_get_full_name:nN #1#2 { T , F , TF } { \__kernel_file_name_sanitize:nN {#1} \l_@@_base_name_tl \@@_get_full_name_search:nN { } \use:n \seq_map_inline:Nn \l_file_search_path_seq { \@@_get_full_name_search:nN { ##1 / } \seq_map_break:n } %<*package> \cs_if_exist:NT \input@path { \tl_map_inline:Nn \input@path { \@@_get_full_name_search:nN { ##1 } \tl_map_break:n } } % \tl_set:Nn \l_@@_full_name_tl { \q_no_value } \prg_break_point: \quark_if_no_value:NTF \l_@@_full_name_tl { \ior_close:N \g_@@_internal_ior \prg_return_false: } { \file_parse_full_name:VNNN \l_@@_full_name_tl \l_@@_dir_str \l_@@_name_str \l_@@_ext_str \str_if_empty:NT \l_@@_ext_str { \__kernel_ior_open:No \g_@@_internal_ior { \l_@@_full_name_tl .tex } \ior_if_eof:NF \g_@@_internal_ior { \tl_put_right:Nn \l_@@_full_name_tl { .tex } } } \ior_close:N \g_@@_internal_ior \tl_set_eq:NN #2 \l_@@_full_name_tl \prg_return_true: } } \cs_generate_variant:Nn \file_get_full_name:nNT { V } \cs_generate_variant:Nn \file_get_full_name:nNF { V } \cs_generate_variant:Nn \file_get_full_name:nNTF { V } \cs_new_protected:Npn \@@_get_full_name_search:nN #1#2 { \@@_name_quote:nN { \tl_to_str:n {#1} \l_@@_base_name_tl } \l_@@_full_name_tl \__kernel_ior_open:No \g_@@_internal_ior \l_@@_full_name_tl \ior_if_eof:NF \g_@@_internal_ior { #2 { \prg_break: } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro} % {\file_get_mdfive_hash:nN, \file_get_size:nN, \file_get_timestamp:nN} % \begin{macro}{\@@_get_details:nnN} % These are all wrappers around the \pdfTeX{} primitives doing the same % jobs: as we want consistent file paths to be found, they are all set up % using \cs{file_get_full_name:nN} and so are non-expandable \texttt{get} % functions. Much of the code is repetitive but we need to branch for % \LuaTeX{} (emulation in Lua), for the slightly different syntax % needed for \tn{tex_mdfivesum:D} and for the fact that primitive % coverage varies in other engines. % \begin{macrocode} \cs_new_protected:Npn \file_get_mdfive_hash:nN #1#2 { \file_get_mdfive_hash:nNF {#1} #2 { \tl_set:Nn #2 { \q_no_value } } } \cs_new_protected:Npn \file_get_size:nN #1#2 { \file_get_size:nNF {#1} #2 { \tl_set:Nn #2 { \q_no_value } } } \cs_new_protected:Npn \file_get_timestamp:nN #1#2 { \file_get_timestamp:nNF {#1} #2 { \tl_set:Nn #2 { \q_no_value } } } \prg_new_protected_conditional:Npnn \file_get_mdfive_hash:nN #1#2 { T , F , TF } { \@@_get_details:nnN {#1} { mdfivesum } {#2} } \prg_new_protected_conditional:Npnn \file_get_size:nN #1#2 { T , F , TF } { \@@_get_details:nnN {#1} { size } {#2} } \prg_new_protected_conditional:Npnn \file_get_timestamp:nN #1#2 { T , F , TF } { \@@_get_details:nnN {#1} { moddate } {#2} } \cs_new_protected:Npn \@@_get_details:nnN #1#2#3 { \file_get_full_name:nNTF {#1} \l_@@_full_name_tl { \tl_set:Nx #3 { \use:c { tex_file #2 :D } \exp_after:wN { \l_@@_full_name_tl } } \prg_return_true: } { \prg_return_false: } } \sys_if_engine_luatex:TF { \cs_set_protected:Npn \@@_get_details:nnN #1#2#3 { \file_get_full_name:nNTF {#1} \l_@@_full_name_tl { \tl_set:Nx #3 { \lua_now:e { l3kernel.file#2 ( " \lua_escape:e { \l_@@_full_name_tl } " ) } } \prg_return_true: } { \prg_return_false: } } } { \prg_set_protected_conditional:Npnn \file_get_mdfive_hash:nN #1#2 { T , F , TF } { \file_get_full_name:nNTF {#1} \l_@@_full_name_tl { \tl_set:Nx #2 { \tex_mdfivesum:D file \exp_after:wN { \l_@@_full_name_tl } } \prg_return_true: } { \prg_return_false: } } \cs_if_exist:NF \tex_filesize:D { \cs_set_protected:Npn \@@_get_details:nnN #1#2#3 { \tl_clear:N #3 \__kernel_msg_error:nnx { kernel } { primitive-not-available } { \exp_not:c { (pdf)file #2 } } \prg_return_false: } } } \__kernel_msg_new:nnnn { kernel } { primitive-not-available } { Primitive~\token_to_str:N #1 not~available } { The~version~of~XeTeX~in~use~does~not~provide~functionality~equivalent~to~ the~\token_to_str:N #1 primitive. } % \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 contains % something, whereas if the file was not located then the return value % is empty. % \begin{macrocode} \prg_new_protected_conditional:Npnn \file_if_exist:n #1 { T , F , TF } { \file_get_full_name:nNTF {#1} \l_@@_full_name_tl { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\file_if_exist_input:n, \file_if_exist_input:nF} % Input of a file with a test for existence. We do not define the |T| % or |TF| variants because the most useful place to place the % \meta{true code} would be inconsistent with other conditionals. % \begin{macrocode} \cs_new_protected:Npn \file_if_exist_input:n #1 { \file_get_full_name:nNT {#1} \l_@@_full_name_tl { \@@_input:V \l_@@_full_name_tl } } \cs_new_protected:Npn \file_if_exist_input:nF #1#2 { \file_get_full_name:nNTF {#1} \l_@@_full_name_tl { \@@_input:V \l_@@_full_name_tl } {#2} } % \end{macrocode} % \end{macro} % % \begin{macro}{\file_input_stop:} % A simple rename. % \begin{macrocode} \cs_new_protected:Npn \file_input_stop: { \tex_endinput:D } % \end{macrocode} % \end{macro} % % \begin{macro}{\__kernel_file_missing:n} % An error message for a missing file, also used in \cs{ior_open:Nn}. % \begin{macrocode} \cs_new_protected:Npn \__kernel_file_missing:n #1 { \__kernel_file_name_sanitize:nN {#1} \l_@@_base_name_tl \__kernel_msg_error:nnx { kernel } { file-not-found } { \l_@@_base_name_tl } } % \end{macrocode} % \end{macro} % % \begin{macro}{\file_input:n} % \begin{macro}{\@@_input:n, \@@_input:V} % \begin{macro}{\@@_input_push:n, \__kernel_file_input_push:n} % \begin{macro}{\@@_input_pop:, \__kernel_file_input_pop:} % \begin{macro}{\@@_input_pop:nnn} % 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 { \file_get_full_name:nNTF {#1} \l_@@_full_name_tl { \@@_input:V \l_@@_full_name_tl } { \__kernel_file_missing:n {#1} } } \cs_new_protected:Npn \@@_input: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} } % \@@_input_push:n {#1} \tex_input:D #1 \c_space_tl \@@_input_pop: } \cs_generate_variant:Nn \@@_input:n { V } % \end{macrocode} % Keeping a track of the file data is easy enough: we store the separated % parts so we do not need to parse them twice. % \begin{macrocode} \cs_new_protected:Npn \@@_input_push:n #1 { \seq_gpush:Nx \g_@@_stack_seq { { \g_file_curr_dir_str } { \g_file_curr_name_str } { \g_file_curr_ext_str } } \file_parse_full_name:nNNN {#1} \l_@@_dir_str \l_@@_name_str \l_@@_ext_str \str_gset_eq:NN \g_file_curr_dir_str \l_@@_dir_str \str_gset_eq:NN \g_file_curr_name_str \l_@@_name_str \str_gset_eq:NN \g_file_curr_ext_str \l_@@_ext_str } %<*package> \cs_new_eq:NN \__kernel_file_input_push:n \__file_input_push:n % \cs_new_protected:Npn \@@_input_pop: { \seq_gpop:NN \g_@@_stack_seq \l_@@_internal_tl \exp_after:wN \@@_input_pop:nnn \l_@@_internal_tl } %<*package> \cs_new_eq:NN \__kernel_file_input_pop: \__file_input_pop: % \cs_new_protected:Npn \@@_input_pop:nnn #1#2#3 { \str_gset:Nn \g_file_curr_dir_str {#1} \str_gset:Nn \g_file_curr_name_str {#2} \str_gset:Nn \g_file_curr_ext_str {#3} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\file_parse_full_name:nNNN, \file_parse_full_name:VNNN} % \begin{macro} % {\@@_parse_full_name_auxi:w, \@@_parse_full_name_split:nNNNTF} % Parsing starts by stripping off any surrounding quotes. Then find % the directory |#4| by splitting at the last~|/|. (The auxiliary % returns \texttt{true}/\texttt{false} depending on whether it found % the delimiter.) We correct for the case of a file in the root |/|, % as in that case we wish to keep the trailing (and only) slash. Then % split the base name |#5| at the last dot. If there was indeed a % dot, |#5| contains the name and |#6| the extension without the dot, % which we add back for convenience. In the special case of no % extension given, the auxiliary stored the name into |#6|, we just % have to move it to |#5|. % \begin{macrocode} \cs_new_protected:Npn \file_parse_full_name:nNNN #1#2#3#4 { \exp_after:wN \@@_parse_full_name_auxi:w \tl_to_str:n { #1 " #1 " } \q_stop #2#3#4 } \cs_generate_variant:Nn \file_parse_full_name:nNNN { V } \cs_new_protected:Npn \@@_parse_full_name_auxi:w #1 " #2 " #3 \q_stop #4#5#6 { \@@_parse_full_name_split:nNNNTF {#2} / #4 #5 { \str_if_empty:NT #4 { \str_set:Nn #4 { / } } } { } \exp_args:No \@@_parse_full_name_split:nNNNTF {#5} . #5 #6 { \str_put_left:Nn #6 { . } } { \str_set_eq:NN #5 #6 \str_clear:N #6 } } \cs_new_protected:Npn \@@_parse_full_name_split:nNNNTF #1#2#3#4 { \cs_set_protected:Npn \@@_tmp:w ##1 ##2 #2 ##3 \q_stop { \tl_if_empty:nTF {##3} { \str_set:Nn #4 {##2} \tl_if_empty:nTF {##1} { \str_clear:N #3 \use_ii:nn } { \str_set:Nx #3 { \str_tail:n {##1} } \use_i:nn } } { \@@_tmp:w { ##1 #2 ##2 } ##3 \q_stop } } \@@_tmp:w { } #1 #2 \q_stop } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\file_show_list:, \file_log_list:, \@@_list:N} % \begin{macro}[EXP]{\@@_list_aux:n} % 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 it to a % string (this does not affect the commas of this comma list). % \begin{macrocode} \cs_new_protected:Npn \file_show_list: { \@@_list:N \msg_show:nnxxxx } \cs_new_protected:Npn \file_log_list: { \@@_list:N \msg_log:nnxxxx } \cs_new_protected:Npn \@@_list:N #1 { \seq_clear:N \l_@@_tmp_seq %<*package> \clist_if_exist:NT \@filelist { \exp_args:NNx \seq_set_from_clist:Nn \l_@@_tmp_seq { \tl_to_str:N \@filelist } } % \seq_concat:NNN \l_@@_tmp_seq \l_@@_tmp_seq \g_@@_record_seq \seq_remove_duplicates:N \l_@@_tmp_seq #1 { LaTeX/kernel } { file-list } { \seq_map_function:NN \l_@@_tmp_seq \@@_list_aux:n } { } { } { } } \cs_new:Npn \@@_list_aux:n #1 { \iow_newline: #1 } % \end{macrocode} % \end{macro} % \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 { \exp_args:NNx \seq_set_from_clist:Nn \l_@@_tmp_seq { \tl_to_str:N \@filelist } \seq_gconcat:NNN \g_@@_record_seq \g_@@_record_seq \l_@@_tmp_seq } % % \end{macrocode} % % \subsection{GetIfInfo} % % \begin{macro}{\GetIdInfo} % \begin{macro}{\@@_id_info_auxi:w, \@@_id_info_auxii:w, \@@_id_info_auxiii:w} % As documented in \pkg{expl3.dtx} this function extracts file name % etc from an \textsc{svn} \texttt{Id} line. This used to be how we % got version number and so on in all modules, so it had to be defined % in \pkg{l3bootstrap}. Now it's more convenient to define it after % we have set up quite a lot of tools, and \pkg{l3file} seems the % least unreasonable place for it. % % The idea here is to extract out the information needed from a standard % \textsc{svn} \texttt{Id} line, but to avoid a line that would get % changed when the file is checked in. Hence the fact that none of the % lines here include both a dollar sign and the \texttt{Id} keyword! % \begin{macrocode} \cs_new_protected:Npn \GetIdInfo { \group_begin: \char_set_catcode_space:n { 32 } \exp_after:wN \group_end: \@@_id_info_auxi:w } % \end{macrocode} % A first check for a completely empty \textsc{svn} field. If that is % not the case, there is a second case when a file created using % \texttt{svn cp} but has not been checked in. That leaves a special % marker \texttt{-1} version, which has no further data. Dealing % correctly with that is the reason for the space in the line to use % \cs{@@_id_info_auxii:w}. % \begin{macrocode} \cs_new_protected:Npn \@@_id_info_auxi:w $ #1 $ #2 { \tl_set:Nn \ExplFileDescription {#2} \str_if_eq:nnTF {#1} { Id } { \tl_set:Nn \ExplFileDate { 0000/00/00 } \tl_set:Nn \ExplFileName { [unknown] } \tl_set:Nn \ExplFileExtension { [unknown~extension] } \tl_set:Nn \ExplFileVersion {-1} } { \@@_id_info_auxii:w #1 ~ \q_stop } } % \end{macrocode} % Here, |#1| is |Id|, |#2| is the file name, |#3| is the extension, % |#4| is the version, |#5| is the check in date and |#6| is the check % in time and user, plus some trailing spaces. If |#4| is the marker % |-1| value then |#5| and |#6| are empty. % \begin{macrocode} \cs_new_protected:Npn \@@_id_info_auxii:w #1 ~ #2.#3 ~ #4 ~ #5 ~ #6 \q_stop { \tl_set:Nn \ExplFileName {#2} \tl_set:Nn \ExplFileExtension {#3} \tl_set:Nn \ExplFileVersion {#4} \str_if_eq:nnTF {#4} {-1} { \tl_set:Nn \ExplFileDate { 0000/00/00 } } { \@@_id_info_auxiii:w #5 - 0 - 0 - \q_stop } } % \end{macrocode} % Convert an \textsc{svn}-style date into a \LaTeX{}-style one. % \begin{macrocode} \cs_new_protected:Npn \@@_id_info_auxiii:w #1 - #2 - #3 - #4 \q_stop { \tl_set:Nn \ExplFileDate { #1/#2/#3 } } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Messages} % % \begin{macrocode} \__kernel_msg_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. } \__kernel_msg_new:nnn { kernel } { file-list } { >~File~List~< #1 \\ ............. } \__kernel_msg_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. } \__kernel_msg_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. } \__kernel_msg_new:nnnn { kernel } { unbalanced-quote-in-filename } { Unbalanced~quotes~in~file~name~'#1'. } { File~names~must~contain~balanced~numbers~of~quotes~("). } \__kernel_msg_new:nnnn { kernel } { iow-indent } { Only~#1 (arg~1)~allows~#2 } { The~command~#2 can~only~be~used~in~messages~ which~will~be~wrapped~using~#1. \tl_if_empty:nF {#3} { ~ It~was~called~with~argument~'#3'. } } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex