diff options
Diffstat (limited to 'Master/texmf-dist/source/latex/l3kernel/l3file.dtx')
-rw-r--r-- | Master/texmf-dist/source/latex/l3kernel/l3file.dtx | 1547 |
1 files changed, 1419 insertions, 128 deletions
diff --git a/Master/texmf-dist/source/latex/l3kernel/l3file.dtx b/Master/texmf-dist/source/latex/l3kernel/l3file.dtx index 12ce5b17b17..28f53230235 100644 --- a/Master/texmf-dist/source/latex/l3kernel/l3file.dtx +++ b/Master/texmf-dist/source/latex/l3kernel/l3file.dtx @@ -1,6 +1,6 @@ % \iffalse meta-comment % -%% File: l3file.dtx Copyright (C) 2009-2012 The LaTeX3 Project +%% File: l3file.dtx Copyright (C) 1990-2012 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 @@ -35,8 +35,8 @@ % %<*driver|package> \RequirePackage{l3names} -\GetIdInfo$Id: l3file.dtx 3287 2012-01-31 22:08:14Z joseph $ - {L3 Experimental file operations} +\GetIdInfo$Id: l3file.dtx 3464 2012-02-26 15:30:29Z bruno $ + {L3 Experimental file and I/O operations} %</driver|package> %<*driver> \documentclass[full]{l3doc} @@ -47,7 +47,7 @@ % \fi % % \title{^^A -% The \pkg{l3file} package\\ File operations^^A +% The \pkg{l3file} package\\ File and I/O operations^^A % \thanks{This file describes v\ExplFileVersion, % last revised \ExplFileDate.}^^A % } @@ -67,16 +67,16 @@ % % \begin{documentation} % -% In contrast to the \pkg{l3io} module, which deals with the lowest -% level of file management, the \pkg{l3file} module provides a higher -% level interface for handling file contents. This involves providing -% convenient wrappers around many of the functions in \pkg{l3io} -% to make them more generally accessible. +% This module provides functions for working with external files. Some of these +% functions apply to an entire file, and have prefix \cs{file_\ldots}, while +% others are used to work with files on a line by line basis and have prefix +% \cs{ior_\ldots} (reading) or \cs{iow_\ldots} (writing). % -% It is important to remember that \TeX{} will attempt to locate files 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. +% It is important to remember that when reading external files \TeX{} will +% attempt to locate them both the operating system path and entries in the +% \TeX{} file database (most \TeX{} systems use such a database). Thus the +% \enquote{current path} for \TeX{} is somewhat broader than that for other +% programs. % % \section{File operation functions} % @@ -88,7 +88,7 @@ % \cs{file_input:n}. % \end{variable} % -% \begin{function}[TF, updated = 2012-01-25]{\file_if_exist:n} +% \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} @@ -102,14 +102,15 @@ % \end{texnote} % \end{function} % -% \begin{function}[updated = 2012-01-25]{\file_add_path:nN} +% \begin{function}[updated = 2012-02-10]{\file_add_path:nN} % \begin{syntax} % \cs{file_add_path:nN} \Arg{file name} \meta{tl var} % \end{syntax} % Searches for \meta{file name} in the path as detailed for % \cs{file_if_exist:nTF}, and if found sets the \meta{tl var} the % fully-qualified name of the file, \emph{i.e}.~the path and file name. -% If the file is not found then the \meta{tl var} will be empty. +% If the file is not found then the \meta{tl var} will contain the +% marker \cs{q_no_value}. % \begin{texnote} % The \meta{file name} may contain both literal items and expandable % content, which should on full expansion be the desired file name. @@ -119,7 +120,7 @@ % \end{texnote} % \end{function} % -% \begin{function}[updated = 2012-01-25]{\file_input:n} +% \begin{function}[updated = 2012-02-17]{\file_input:n} % \begin{syntax} % \cs{file_input:n} \Arg{file name} % \end{syntax} @@ -127,7 +128,7 @@ % \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. +% function. An error will be raised if the file is not found % \begin{texnote} % The \meta{file name} may contain both literal items and expandable % content, which should on full expansion be the desired file name. @@ -137,24 +138,63 @@ % \end{texnote} % \end{function} % -% \begin{function}[added = 2012-01-25]{\file_split_path_name_ext:nNNN} +% \begin{function}{\file_path_include:n} % \begin{syntax} -% \cs{file_split_path_name_ext:nNNN} \Arg{name} \meta{path} \meta{filename} \meta{ext} +% \cs{file_path_include:n} \Arg{path} % \end{syntax} -% Splits the file \meta{name} into any \meta{path} (up to the last |/|), -% any \meta{ext} (from the last |.| to the end of the \meta{name}), -% and the the \meta{filename} (the rest of the \meta{name}). Thus for -% example -% \begin{verbatim} -% \file_split_path_name_ext:nNNN { figures / example.foo.eps } -% \end{verbatim} -% will result in \meta{path} \texttt{figures/}, \meta{filename} -% \texttt{example.foo} and \meta{ext} \texttt{eps}. -% The three parts of the \meta{name} will be stored in the -% token list variables given as the \meta{name}, \meta{path} and -% \meta{filename} arguments. +% Adds \meta{path} to the list of those used to search when reading +% files. The assignment is local. +% \end{function} +% +% \begin{function}{\file_path_remove:n} +% \begin{syntax} +% \cs{file_path_remove:n} \Arg{path} +% \end{syntax} +% Removes \meta{path} from the list of those used to search when reading +% files. The assignment is local. +% \end{function} +% +% \begin{function}{\file_list:} +% \begin{syntax} +% \cs{file_list:} +% \end{syntax} +% This function will list all files loaded using \cs{file_input:n} +% in the log file. +% \end{function} +% +% \subsection{Input--output stream management} +% +% As \TeX{} is limited to $16$ input streams and $16$ output streams, direct +% use of the streams by the programmer is not supported in \LaTeX3. Instead, an +% internal pool of streams is maintained, and these are allocated and +% deallocated as needed by other modules. As a result, the programmer should +% close streams when they are no longer needed, to release them for other +% processes. +% +% \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:Nn} \meta{stream} +% \end{syntax} +% Globally reserves the name of the \meta{stream}, either for reading +% or for writing as appropriate. The \meta{stream} is not opened until the +% appropriate \cs{\ldots_open:Nn} function is used. Attempting to use +% a \meta{stream} which has not been opened will result in a \TeX{} +% error. +% \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 file ends. % \begin{texnote} -% The \meta{name} may contain both literal items and expandable +% The \meta{file name} may contain both literal items and expandable % content, which should on full expansion be the desired file name. % Any active characters (as declared in \cs{l_char_active_seq}) will % \emph{not} be expanded, allowing the direct use of these in @@ -162,30 +202,278 @@ % \end{texnote} % \end{function} % -% \begin{function}{\file_path_include:n} +% \begin{function}[updated = 2012-02-09]{\iow_open:Nn, \iow_open:cn} % \begin{syntax} -% \cs{file_path_include:n} \Arg{path} +% \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 file ends. Opening a file for writing will clear +% any existing content in the file (\emph{i.e.}~writing is \emph{not} +% additive). +% \begin{texnote} +% The \meta{file name} may contain both literal items and expandable +% content, which should on full expansion be the desired file name. +% Any active characters (as declared in \cs{l_char_active_seq}) will +% \emph{not} be expanded, allowing the direct use of these in +% file names. +% \end{texnote} +% \end{function} +% +% \begin{function}[updated = 2011-12-27]{\ior_close:N, \ior_close:c} +% \begin{syntax} +% \cs{ior_close:N} \meta{stream} % \end{syntax} -% Adds \meta{path} to the list of those used to search for files -% by the \cs{file_input:n} and \cs{file_if_exist:n} function. The -% assignment is local. +% Closes the \meta{stream}. Streams should always be closed when +% they are finished with as this ensures that they remain available +% to other programmer. % \end{function} % -% \begin{function}{\file_path_remove:n} +% \begin{function}[updated = 2011-12-27]{\iow_close:N, \iow_close:c} % \begin{syntax} -% \cs{file_path_remove:n} \Arg{path} +% \cs{iow_close:N} \meta{stream} % \end{syntax} -% Removes \meta{path} from the list of those used to search for files -% by the \cs{file_input:n} and \cs{file_if_exist:n} function. The -% assignment is local. +% Closes the \meta{stream}. Streams should always be closed when +% they are finished with as this ensures that they remain available +% to other programmer. % \end{function} % -% \begin{function}{\file_list:} +% \begin{function}{\ior_list_streams:, \iow_list_streams:} % \begin{syntax} -% \cs{file_list:} +% \cs{ior_list_streams:} +% \cs{iow_list_streams:} % \end{syntax} -% This function will list all files loaded using \cs{file_input:n} -% in the log file. +% Displays a list of the file names associated with each open +% stream: intended for tracking down problems. +% \end{function} +% +% \section{Reading from files} +% +% \begin{function}{\ior_to:NN,\ior_gto:NN} +% \begin{syntax} +% \cs{ior_to:NN} \meta{stream} \meta{token list variable} +% \end{syntax} +% Functions 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 in the \meta{token list} variable, locally or globally. If the +% \meta{stream} is not open, input is requested from the terminal. +% The material read from the \meta{stream} will be tokenized by +% \TeX{} according to the category codes in force when the function +% is used. +% \begin{texnote} +% This protected macro expands to the \TeX{} primitives \tn{read} +% or \tn{global}\tn{read} along with the |to| keyword. +% \end{texnote} +% \end{function} +% +% \begin{function}{\ior_str_to:NN,\ior_str_gto:NN} +% \begin{syntax} +% \cs{ior_str_to:NN} \meta{stream} \meta{token list variable} +% \end{syntax} +% Functions that reads one line from the input \meta{stream} and stores +% the result in the \meta{token list} variable, locally or globally. If the +% \meta{stream} is not open, input is requested from the terminal. +% The material 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). +% \begin{texnote} +% This protected macro expands to the \eTeX{} primitives \tn{readline} +% or \tn{global}\tn{readline} along with the |to| keyword. +% \end{texnote} +% \end{function} +% +%\begin{function}[updated = 2012-02-10, EXP, pTF]{\ior_if_eof:N} +% \begin{syntax} +% \cs{ior_if_eof_p:N} \meta{stream} \\ +% \cs{ior_if_eof:NTF} \meta{stream} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests if the end of a \meta{stream} has been reached during a reading +% operation. The test will also return a \texttt{true} value if +% the \meta{stream} is not open. +%\end{function} +% +% \section{Writing to files} +% +% \begin{function}{\iow_now:Nn, \iow_now:Nx} +% \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}). +% \begin{texnote} +% \cs{iow_now:Nx} is a protected macro which expands to +% the two \TeX{} primitives \tn{immediate}\tn{write}. +% \end{texnote} +% \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} +% \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}). +% \end{function} +% +% \begin{function}{\iow_shipout_x:Nn, \iow_shipout_x:Nx} +% \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} +% \cs{iow_shipout_x:Nn} is the \TeX{} primitive \tn{write} renamed. +% \end{texnote} +% \end{function} +% +% \begin{function}[EXP]{\iow_char:N} +% \begin{syntax} +% \cs{iow_char:N} \meta{token} +% \end{syntax} +% Inserts \meta{token} 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}). +% \end{function} +% +% \section{Wrapping lines in output} +% +% \begin{function}[updated = 2011-09-21]{\iow_wrap:xnnnN} +% \begin{syntax} +% \cs{iow_wrap:xnnnN} \Arg{text} \Arg{run-on text} \Arg{run-on length} \Arg{set up} \meta{function} +% \end{syntax} +% This function will wrap the \meta{text} to a fixed number of +% characters per line. At the start of each line which is wrapped, +% the \meta{run-on text} will be inserted. The line length targeted +% will be the value of \cs{l_iow_line_length_int} minus the +% \meta{run-on length}. The later value should be the number of +% characters in the \meta{run-on text}. Additional functions may be +% added to the wrapping by using the \meta{set up}, which is executed +% before the wrapping takes place. The result of the wrapping operation +% is passed as a braced argument to the \meta{function}, which will +% typically be a wrapper around a writing operation. Within the +% \meta{text}, +% \begin{itemize} +% \item |\\| may be used to force a new line, +% \item \verb|\ | may be used to represent a forced space +% (for example after a control sequence), +% \item |\#|, |\%|, |\{|, |\}|, |\~| may be used to represent +% the corresponding character, +% \item \cs{iow_indent:n} may be used to indent a part of the +% message. +% \end{itemize} +% Both the wrapping process and the subsequent write operation +% will perform \texttt{x}-type expansion. For this reason, material which +% is to be written \enquote{as is} should be given as the argument to +% \cs{token_to_str:N} or \cs{tl_to_str:n} (as appropriate) within +% the \meta{text}. The output of \cs{iow_wrap:xnnnN} (\emph{i.e.}~the +% argument passed to the \meta{function}) will consist of characters of +% category code $12$ (other) and $10$ (space) only. This means that the +% output will \emph{not} expand further when written to a file. +% \end{function} +% +% \begin{function}[added = 2011-09-21]{\iow_indent:n} +% \begin{syntax} +% \cs{iow_indent:n} \Arg{text} +% \end{syntax} +% In the context of \cs{iow_wrap:xnnnN} (for instance in messages), +% indents \meta{text} by four spaces. This function will not cause +% a line break, and only affects lines which start within the scope +% of the \meta{text}. In case the indented \meta{text} should appear +% on separate lines from the surrounding text, use |\\| to force +% line breaks. +% \end{function} +% +% \begin{variable}{\l_iow_line_length_int} +% The maximum length of a line to be written by the \cs{iow_wrap:xxnnN} +% function. This value depends on the \TeX{} system in use: the standard +% value is $78$, which is typically correct for unmodified \TeX{}live +% and MiK\TeX{} systems. +% \end{variable} +% +% \begin{variable}[added = 2011-09-05]{\c_catcode_other_space_tl} +% Token list containing one character with category code $12$, +% (\enquote{other}), and character code $32$ (space). +% \end{variable} +% +% \section{Constant input--output streams} +% +% \begin{variable}{\c_term_ior} +% Constant input stream for reading from the terminal. Reading from this +% stream using \cs{ior_to:NN} or similar will result in a prompt from +% \TeX{} of the form +% \begin{verbatim} +% <tl>= +% \end{verbatim} +% \end{variable} +% +% \begin{variable}{\c_log_iow, \c_term_iow} +% Constant output streams for writing to the log and to the terminal +% (plus the log), respectively. +% \end{variable} +% +% \section{Experimental functions} +% +% \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 \meta{items} obtained by +% reading one or more lines (until an equal number of left and right +% braces are found) from the \meta{stream}. The \meta{inline function} +% should consist of code which will receive 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} \Arg{stream} \Arg{inline function} +% \end{syntax} +% Applies the \meta{inline function} to every \meta{line} +% in the \meta{file}. 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 +% will receive the \meta{line} as |#1|. % \end{function} % % \section{Internal file functions} @@ -220,6 +508,67 @@ % the comma lists \tn{input@path} and \tn{@filelist} to sequences. % \end{variable} % +% \section{Internal input--output functions} +% +% \begin{function}[added = 2012-02-09]{\file_name_sanitize:nn} +% \begin{syntax} +% \cs{file_name_sanitize:nn} \Arg{name} \Arg{tokens} +% \end{syntax} +% Exhaustively-expands the \meta{name} with the exception of any +% category \meta{active} (catcode~$12$) tokens, which are not expanded. +% The list of \meta{active} tokens is taken from \cs{l_char_active_seq}. +% The \meta{sanitized name} is then inserted (in braces) after the +% \meta{tokens}, which should further process the file name. If any +% spaces are found in the name after expansion, an error is raised. +% \end{function} +% +% \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} +% +% \begin{function}[added = 2012-01-23] +% {\ior_open_unsafe:Nn, \ior_open_unsafe:No, \iow_open_unsafe:Nn} +% \begin{syntax} +% \cs{ior_open_unsafe:Nn} \meta{stream} \Arg{file name} +% \end{syntax} +% These functions have identical syntax to the generally-available +% versions without the |_unsafe| suffix. However, these functions do not +% take precautions against active characters in the \meta{file name}: they +% are therefore intended to be used by higher-level functions which +% have already fully expanded the \meta{file name} and which need to +% perform multiple open or close operations. See for example the +% implementation of \cs{file_add_path:Nn}, +% \end{function} +% +% \begin{function}{\ior_raw_new:N, \ior_raw_new:c} +% \begin{syntax} +% \cs{ior_raw_new:N} \meta{stream} +% \end{syntax} +% Directly allocates a new stream for reading, bypassing the stack +% system. This is to be used only when a new stream is required at a +% \TeX{} level, when a new stream is requested by the stack itself. +% \end{function} +% +% \begin{function}{\iow_raw_new:N, \iow_raw_new:c} +% \begin{syntax} +% \cs{iow_raw_new:N} \meta{stream} +% \end{syntax} +% Directly allocates a new stream for writing, bypassing the stack +% system. This is to be used only when a new stream is required at a +% \TeX{} level, when a new stream is requested by the stack itself. +% \end{function} +% % \end{documentation} % % \begin{implementation} @@ -240,6 +589,8 @@ %</package> % \end{macrocode} % +% \subsection{File operations} +% % \begin{variable}{\g_file_current_name_tl} % The name of the current file should be available at all times. % \begin{macrocode} @@ -318,8 +669,33 @@ % \end{macrocode} % \end{variable} % +% \begin{macro}[int]{\file_name_sanitize:nn} +% For converting a token list to a string where active characters are treated +% as strings from the start. +% \begin{macrocode} +\cs_new_protected:Npn \file_name_sanitize:nn #1#2 + { + \group_begin: + \seq_map_inline:Nn \l_char_active_seq + { \cs_set_nopar:Npx ##1 { \token_to_str:N ##1 } } + \tl_set:Nx \l_file_internal_name_tl {#1} + \tl_set:Nx \l_file_internal_name_tl + { \tl_to_str:N \l_file_internal_name_tl } + \tl_if_in:NnTF \l_file_internal_name_tl { ~ } + { + \msg_kernel_error:nnx { file } { space-in-file-name } + { \l_file_internal_name_tl } + } + \use:x + { + \group_end: + \exp_not:n {#2} { \l_file_internal_name_tl } + } + } +% \end{macrocode} +% \end{macro} +% % \begin{macro}{\file_add_path:nN} -% \begin{variable}{\g_file_internal_ior} % \begin{macro}[aux]{\file_add_path_aux:nN, \file_add_path_search:nN} % The way to test if a file exists is to try to open it: if it does not % exist then \TeX{} will report end-of-file. For files which are in the @@ -328,19 +704,8 @@ % location is of course treated as the correct one. If nothing is found, % |#2| is returned empty. % \begin{macrocode} -\ior_new:N \g_file_internal_ior \cs_new_protected:Npn \file_add_path:nN #1 - { - \group_begin: - \tl_to_str_active_safe:Nx \l_file_internal_name_tl {#1} - \tl_if_in:NnT \l_file_internal_name_tl { ~ } - { - \msg_kernel_error:nnx { io } { space-in-file-name } - { \l_file_internal_name_tl } - } - \exp_args:NNo \group_end: - \file_add_path_aux:nN \l_file_internal_name_tl - } + { \file_name_sanitize:nn {#1} { \file_add_path_aux:nN } } \cs_new_protected:Npn \file_add_path_aux:nN #1#2 { \ior_open_unsafe:Nn \g_file_internal_ior {#1} @@ -353,7 +718,7 @@ } \cs_new_protected:Npn \file_add_path_search:nN #1#2 { - \tl_clear:N #2 + \tl_set:Nn #2 { \q_no_value } %<*package> \cs_if_exist:NT \input@path { @@ -380,7 +745,6 @@ } % \end{macrocode} % \end{macro} -% \end{variable} % \end{macro} % % \begin{macro}[TF]{\file_if_exist:n} @@ -389,10 +753,10 @@ % something, whereas if the file was not located then the return value % will be empty. % \begin{macrocode} -\prg_new_protected_conditional:Nnn \file_if_exist:n { T , F , TF } +\prg_new_protected_conditional:Npnn \file_if_exist:n #1 { T , F , TF } { \file_add_path:nN {#1} \l_file_internal_name_tl - \tl_if_empty:NTF \l_file_internal_name_tl + \quark_if_no_value:NTF \l_file_internal_name_tl { \prg_return_false: } { \prg_return_true: } } @@ -400,126 +764,1053 @@ % \end{macro} % % \begin{macro}{\file_input:n} +% \begin{macro}[aux]{\file_input_aux:n \file_input_aux:V, \file_input_error:n} % Loading a file is done in a safe way, checking first that the file % exists and loading only if it does. % \begin{macrocode} \cs_new_protected:Npn \file_input:n #1 { \file_add_path:nN {#1} \l_file_internal_name_tl - \tl_if_empty:NF \l_file_internal_name_tl - { + \quark_if_no_value:NTF \l_file_internal_name_tl + { \file_name_sanitize:nn {#1} { \file_input_error:n } } + { \file_input_aux:V \l_file_internal_name_tl } + } +\cs_new_protected:Npn \file_input_aux:n #1 + { %<*initex> - \seq_gput_right:Nx \g_file_record_seq {#1} + \seq_gput_right:Nn \g_file_record_seq {#1} %</initex> %<*package> - \@addtofilelist {#1} + \@addtofilelist {#1} %</package> - \seq_gpush:NV \g_file_stack_seq \g_file_current_name_tl - \tl_gset:Nn \g_file_current_name_tl {#1} - \exp_after:wN \tex_input:D \l_file_internal_name_tl \c_space_tl - \seq_gpop:NN \g_file_stack_seq \g_file_current_name_tl - } + \seq_gpush:Nn \g_file_stack_seq \g_file_current_name_tl + \tl_gset:Nn \g_file_current_name_tl {#1} + \tex_input:D #1 \c_space_tl + \seq_gpop:NN \g_file_stack_seq \g_file_current_name_tl } +\cs_generate_variant:Nn \file_input_aux:n { V } +\cs_new_protected:Npn \file_input_error:n #1 + { \msg_kernel_error:nnx { file } { file-not-found } {#1} } % \end{macrocode} % \end{macro} +% \end{macro} % -% \begin{macro}{\file_split_path_name_ext:nNNN} -% \begin{macro}[aux]{\file_split_path_name_ext_aux:nNNN} -% \begin{macro}[aux]{\file_split_path:wNNN, \file_split_name_ext:wNN} -% Splits a file name into any path part, the file name and the extension, -% which is considered as the part after the last |.|. +% \begin{macro}{\file_path_include:n} +% \begin{macro}{\file_path_remove:n} +% Wrapper functions to manage the search path. % \begin{macrocode} -\cs_new_protected:Npn \file_split_path_name_ext:nNNN #1 +\cs_new_protected:Npn \file_path_include:n #1 { - \group_begin: - \tl_to_str_active_safe:Nx \l_file_internal_name_tl {#1} - \tl_if_in:NnT \l_file_internal_name_tl { ~ } - { - \msg_kernel_error:nnx { io } { space-in-file-name } - { \l_file_internal_name_tl } - } - \exp_args:NNo \group_end: - \file_split_path_name_ext_aux:nNNN \l_file_internal_name_tl + \seq_if_in:NnF \l_file_search_path_seq {#1} + { \seq_put_right:Nn \l_file_search_path_seq {#1} } } -\cs_new_protected:Npn \file_split_path_name_ext_aux:nNNN #1#2#3#4 +\cs_new_protected:Npn \file_path_remove:n #1 + { \seq_remove_all:Nn \l_file_search_path_seq {#1} } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\file_list:} +% A function to list all files used to the log. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \file_list: { - \tl_clear:N #2 - \tl_clear:N #3 - \file_split_path:wNNN #1 / \q_nil / \q_stop #2#3#4 + \seq_remove_duplicates:N \g_file_record_seq + \iow_log:n { *~File~List~* } + \seq_map_inline:Nn \g_file_record_seq { \iow_log:n {##1} } + \iow_log:n { ************* } } -\cs_new_protected:Npn \file_split_path:wNNN #1 / #2 / #3 \q_stop #4 +% \end{macrocode} +% \end{macro} +% +% When used as a package, there is a need to hold onto the standard +% file list as well as the new one here. +% \begin{macrocode} +%<*package> +\AtBeginDocument + { + \seq_set_from_clist:NN \l_file_internal_seq \@filelist + \seq_gconcat:NNN \g_file_record_seq \g_file_record_seq \l_file_internal_seq + } +%</package> +% \end{macrocode} +% +% \subsection{Input--output variables constants} +% +% \begin{variable}{\c_term_ior} +% Reading from the terminal (with a prompt) is done using a positive +% but non-existent stream number. Unlike writing, there is no concept +% of reading from the log. +% \begin{macrocode} +\cs_new_eq:NN \c_term_ior \c_sixteen +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\c_log_iow, \c_term_iow} +% Here we allocate two output streams for writing to the transcript +% file only (\cs{c_log_iow}) and to both the terminal and +% transcript file (\cs{c_term_iow}). +% \begin{macrocode} +\cs_new_eq:NN \c_log_iow \c_minus_one +\cs_new_eq:NN \c_term_iow \c_sixteen +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\c_iow_streams_tl, \c_ior_streams_tl} +% The list of streams available, by number. +% \begin{macrocode} +\tl_const:Nn \c_iow_streams_tl { - \quark_if_nil:nTF {#2} - { \file_split_name_ext:wNN #1 . \q_nil . \q_stop } + \c_zero + \c_one + \c_two + \c_three + \c_four + \c_five + \c_six + \c_seven + \c_eight + \c_nine + \c_ten + \c_eleven + \c_twelve + \c_thirteen + \c_fourteen + \c_fifteen + } +\cs_new_eq:NN \c_ior_streams_tl \c_iow_streams_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\g_iow_streams_prop, \g_ior_streams_prop} +% The allocations for streams are stored in property lists, which +% are set up to have a \enquote{full} set of allocations from the start. +% In package mode, a few slots are always taken, so these are +% blocked off from use. +% \begin{macrocode} +\prop_new:N \g_iow_streams_prop +\prop_new:N \g_ior_streams_prop +%<*package> +\prop_put:Nnn \g_iow_streams_prop { 0 } { LaTeX2e~reserved } +\prop_put:Nnn \g_iow_streams_prop { 1 } { LaTeX2e~reserved } +\prop_put:Nnn \g_iow_streams_prop { 2 } { LaTeX2e~reserved } +\prop_put:Nnn \g_ior_streams_prop { 0 } { LaTeX2e~reserved } +%</package> +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_iow_stream_int, \l_ior_stream_int} +% Used to track the number allocated to the stream being created: +% this is taken from the property list but does alter. +% \begin{macrocode} +\int_new:N \l_iow_stream_int +\cs_new_eq:NN \l_ior_stream_int \l_iow_stream_int +% \end{macrocode} +% \end{variable} +% +% \subsection{Stream management} +% +% \begin{macro}[int]{\ior_raw_new:N, \ior_raw_new:c} +% \begin{macro}[int]{\iow_raw_new:N, \iow_raw_new:c} +% The lowest level for stream management is actually creating raw \TeX{} +% streams. As these are very limited (even with \eTeX{}), this should not +% be addressed directly. +% \begin{macrocode} +%<*initex> +\alloc_setup_type:nnn { ior } \c_zero \c_sixteen +\cs_new_protected:Npn \ior_raw_new:N #1 + { \alloc_reg:nNN { ior } \tex_chardef:D #1 } +\alloc_setup_type:nnn { iow } \c_zero \c_sixteen +\cs_new_protected:Npn \iow_raw_new:N #1 + { \alloc_reg:nNN { iow } \tex_chardef:D #1 } +%</initex> +%<*package> +\cs_set_eq:NN \iow_raw_new:N \newwrite +\cs_set_eq:NN \ior_raw_new:N \newread +%</package> +\cs_generate_variant:Nn \ior_raw_new:N { c } +\cs_generate_variant:Nn \iow_raw_new:N { c } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\ior_new:N, \ior_new:c, \iow_new:N, \iow_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 } +\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_file_internal_ior} +% Delayed from above so that the mechanisms are in place. +% \begin{macrocode} +\ior_new:N \g_file_internal_ior +% \end{macrocode} +% \end{variable} +% +% \begin{macro}{\ior_open:Nn, \ior_open:cn, \iow_open:Nn, \iow_open:cn} +% \begin{macro}[int]{\ior_open_aux:Nn} +% \begin{macro}[TF]{\ior_open:Nn} +% \begin{macro}[aux]{\ior_open_aux:NnTF} +% \begin{macro}[int] +% {\ior_open_unsafe:Nn, \ior_open_unsafe:No, \iow_open_unsafe:Nn} +% In both cases, opening a stream starts with a call to the closing +% function: this is safest. There is then a loop through the +% allocation number list to find the first free stream number. +% When one is found the allocation can take place, the information +% can be stored and finally the file can actually be opened. Before +% any actual file operations there is a precaution against special +% characters in file names. For reading files, there is an intermediate +% auxiliary to allow path addition, keeping the internal function fast +% and avoiding an infinite loop. +% \begin{macrocode} +\cs_new_protected:Npn \ior_open:Nn #1#2 + { \file_name_sanitize:nn {#2} { \ior_open_aux:Nn #1 } } +\cs_generate_variant:Nn \ior_open:Nn { c } +\cs_new_protected:Npn \iow_open:Nn #1#2 + { \file_name_sanitize:nn {#2} { \iow_open_unsafe:Nn #1 } } +\cs_generate_variant:Nn \iow_open:Nn { c } +\cs_new_protected:Npn \ior_open_aux:Nn #1#2 + { + \file_add_path:nN {#2} \l_file_name_internal_tl + \quark_if_no_value:NTF \l_file_internal_name_tl + { \file_input_error:n {#2} } + { \ior_open_unsafe:No #1 \l_file_name_internal_tl } + } +\prg_new_protected_conditional:Npnn \ior_open:Nn #1#2 { T , F , TF } + { \file_name_sanitize:nn {#2} { \ior_open_aux:NnTF #1 } } +\cs_new_protected:Npn \ior_open_aux:NnTF #1#2 + { + \file_add_path:nN {#2} \l_file_name_internal_tl + \quark_if_no_value:NTF \l_file_internal_name_tl + { \prg_return_false: } + { + \ior_open_unsafe:No #1 \l_file_name_internal_tl + \prg_return_true: + } + } +\cs_generate_variant:Nn \ior_open:NnT { c } +\cs_generate_variant:Nn \ior_open:NnF { c } +\cs_generate_variant:Nn \ior_open:NnTF { c } +\cs_new_protected:Npn \ior_open_unsafe:Nn #1#2 + { + \ior_close:N #1 + \int_set:Nn \l_ior_stream_int \c_sixteen + \tl_map_function:NN \c_ior_streams_tl \ior_alloc_read:n + \int_compare:nNnTF \l_ior_stream_int = \c_sixteen + { \msg_kernel_fatal:nn { ior } { streams-exhausted } } { - \tl_put_right:Nn #4 { #1 / } - \file_split_path:wNNN #2 / #3 \q_stop #4 + \ior_stream_alloc:N #1 + \prop_gput:NVn \g_ior_streams_prop \l_ior_stream_int {#2} + \tex_openin:D #1#2 \scan_stop: } } -\cs_new_protected:Npn \file_split_name_ext:wNN #1 . #2 . #3 \q_stop #4#5 +\cs_generate_variant:Nn \ior_open_unsafe:Nn { No } +\cs_new_protected:Npn \iow_open_unsafe:Nn #1#2 { - \quark_if_nil:nTF {#2} + \iow_close:N #1 + \int_set:Nn \l_iow_stream_int \c_sixteen + \tl_map_function:NN \c_iow_streams_tl \iow_alloc_write:n + \int_compare:nNnTF \l_iow_stream_int = \c_sixteen + { \msg_kernel_fatal:nn { iow } { streams-exhausted } } { - \tl_if_empty:NTF #4 + \iow_stream_alloc:N #1 + \prop_gput:NVn \g_iow_streams_prop \l_iow_stream_int {#2} + \tex_immediate:D \tex_openout:D #1#2 \scan_stop: + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[aux]{\ior_alloc_read:n} +% \begin{macro}[aux]{\iow_alloc_write:n} +% These functions are used to see if a particular stream is available. +% The property list contains file names for streams in use, so +% any unused ones are for the taking. +% \begin{macrocode} +\cs_new_protected:Npn \iow_alloc_write:n #1 + { + \prop_if_in:NnF \g_iow_streams_prop {#1} + { + \int_set:Nn \l_iow_stream_int {#1} + \tl_map_break: + } + } +\cs_new_protected:Npn \ior_alloc_read:n #1 + { + \prop_if_in:NnF \g_iow_streams_prop {#1} + { + \int_set:Nn \l_ior_stream_int {#1} + \tl_map_break: + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[aux]{\iow_stream_alloc:N, \ior_stream_alloc:N} +% \begin{macro}[aux]{\iow_stream_alloc_aux:, \ior_stream_alloc_aux:} +% \begin{variable}{\g_iow_internal_iow, \g_ior_internal_ior} +% Allocating a raw stream is much easier in \IniTeX{} mode than for +% the package. For the format, all streams will be allocated by +% \pkg{l3file} and so there is a simple check to see if a raw +% stream is actually available. On the other hand, for the +% package there will be non-managed streams. So if the managed +% one is not open, a check is made to see if some other managed +% stream is available before deciding to open a new one. If a new +% one is needed, we get the number allocated by \LaTeXe{} to get +% \enquote{back on track} with allocation. +% \begin{macrocode} +\iow_new:N \g_iow_internal_iow +\ior_new:N \g_ior_internal_ior +\cs_new_protected:Npn \iow_stream_alloc:N #1 + { + \cs_if_exist:cTF { g_iow_ \int_use:N \l_iow_stream_int _iow } + { \cs_gset_eq:Nc #1 { g_iow_ \int_use:N \l_iow_stream_int _iow } } + { +%<*package> + \iow_stream_alloc_aux: + \int_compare:nNnT \l_iow_stream_int = \c_sixteen { - \tl_set:Nn #4 {#1} - \tl_clear:N #5 + \iow_raw_new:N \g_iow_internal_iow + \int_set:Nn \l_iow_stream_int { \g_iow_internal_iow } + \cs_gset_eq:cN + { g_iow_ \int_use:N \l_iow_stream_int _iow } \g_iow_internal_iow } - { \tl_set:Nn #5 {#1} } +%</package> +%<*initex> + \iow_raw_new:c { g_iow_ \int_use:N \l_iow_stream_int _iow } +%</initex> + \cs_gset_eq:Nc #1 { g_iow_ \int_use:N \l_iow_stream_int _iow } } + } +%<*package> +\cs_new_protected_nopar:Npn \iow_stream_alloc_aux: + { + \int_incr:N \l_iow_stream_int + \int_compare:nNnT \l_iow_stream_int < \c_sixteen { - \tl_put_right:Nx #4 + \cs_if_exist:cTF { g_iow_ \int_use:N \l_iow_stream_int _iow } + { + \prop_if_in:NVT \g_iow_streams_prop \l_iow_stream_int + { \iow_stream_alloc_aux: } + } + { \iow_stream_alloc_aux: } + } + } +%</package> +\cs_new_protected:Npn \ior_stream_alloc:N #1 + { + \cs_if_exist:cTF { g_ior_ \int_use:N \l_ior_stream_int _ior } + { \cs_gset_eq:Nc #1 { g_ior_ \int_use:N \l_ior_stream_int _ior } } + { +%<*package> + \ior_stream_alloc_aux: + \int_compare:nNnT \l_ior_stream_int = \c_sixteen { - \tl_if_empty:NF #4 { . } - #1 + \ior_raw_new:N \g_ior_internal_ior + \int_set:Nn \l_ior_stream_int { \g_ior_internal_ior } + \cs_gset_eq:cN + { g_ior_ \int_use:N \l_iow_stream_int _ior } \g_ior_internal_ior } - \file_split_name_ext:wNN #2 . #3 \q_stop #4#5 +%</package> +%<*initex> + \ior_raw_new:c { g_ior_ \int_use:N \l_ior_stream_int _ior } +%</initex> + \cs_gset_eq:Nc #1 { g_ior_ \int_use:N \l_ior_stream_int _ior } + } + } +%<*package> +\cs_new_protected_nopar:Npn \ior_stream_alloc_aux: + { + \int_incr:N \l_ior_stream_int + \int_compare:nNnT \l_ior_stream_int < \c_sixteen + { + \cs_if_exist:cTF { g_ior_ \int_use:N \l_ior_stream_int _ior } + { + \prop_if_in:NVT \g_ior_streams_prop \l_ior_stream_int + { \ior_stream_alloc_aux: } + } + { \ior_stream_alloc_aux: } } } +%</package> % \end{macrocode} +% \end{variable} % \end{macro} % \end{macro} +% +% \begin{macro}{\ior_close:N, \ior_close:c, \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 \ior_close:N #1 + { + \cs_if_exist:NT #1 + { + \int_compare:nNnF #1 = \c_minus_one + { + \int_compare:nNnF #1 = \c_sixteen + { \tex_closein:D #1 } + \prop_gdel:NV \g_ior_streams_prop #1 + \cs_gset_eq:NN #1 \c_term_ior + } + } + } +\cs_new_protected:Npn \iow_close:N #1 + { + \cs_if_exist:NT #1 + { + \int_compare:nNnF #1 = \c_minus_one + { + \int_compare:nNnF #1 = \c_sixteen + { \tex_closein:D #1 } + \prop_gdel:NV \g_iow_streams_prop #1 + \cs_gset_eq:NN #1 \c_term_iow + } + } + } +\cs_generate_variant:Nn \ior_close:N { c } +\cs_generate_variant:Nn \iow_close:N { c } +% \end{macrocode} % \end{macro} % -% \begin{macro}{\file_path_include:n} -% \begin{macro}{\file_path_remove:n} -% Wrapper functions to manage the search path. +% \begin{macro}{\ior_list_streams:} +% Show the property lists, but with some \enquote{pretty printing}. +% See the \pkg{l3msg} module. If there are no open read streams, +% issue the message \texttt{show-no-stream}, and show an empty +% token list. If there are open read streams, format them with +% \cs{msg_aux_show_unbraced:nn}, and with the message +% \texttt{show-open-streams}. % \begin{macrocode} -\cs_new_protected:Npn \file_path_include:n #1 +\cs_new_protected_nopar:Npn \ior_list_streams: + { \ior_list_streams_aux:Nn \g_ior_streams_prop { ior } } +\cs_new_protected_nopar:Npn \iow_list_streams: + { \ior_list_streams_aux:Nn \g_iow_streams_prop { iow } } +\cs_new_protected:Npn \ior_list_streams_aux:Nn #1#2 { - \seq_if_in:NnF \l_file_search_path_seq {#1} - { \seq_put_right:Nn \l_file_search_path_seq {#1} } + \msg_aux_use:nn { LaTeX / #2 } + { \prop_if_empty:NTF #1 { show-no-stream } { show-open-streams } } + \msg_aux_show:x + { \prop_map_function:NN #1 \msg_aux_show_unbraced:nn } } -\cs_new_protected:Npn \file_path_remove:n #1 - { \seq_remove_all:Nn \l_file_search_path_seq {#1} } % \end{macrocode} % \end{macro} +% +% Text for the error messages. +% \begin{macrocode} +\msg_kernel_new:nnnn { iow } { streams-exhausted } + { Output~streams~exhausted } + { + TeX~can~only~open~up~to~16~output~streams~at~one~time.\\ + All~16 are currently~in~use,~and~something~wanted~to~open + another~one. + } +\msg_kernel_new:nnnn { ior } { 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. + } +% \end{macrocode} +% +% \subsection{Deferred writing} +% +% \begin{macro}{\iow_shipout_x:Nn, \iow_shipout_x:Nx} +% First the easy part, this is the primitive. +% \begin{macrocode} +\cs_new_eq:NN \iow_shipout_x:Nn \tex_write:D +\cs_generate_variant:Nn \iow_shipout_x:Nn { Nx } +% \end{macrocode} % \end{macro} % -% \begin{macro}{\file_list:} -% A function to list all files used to the log. +% \begin{macro}{\iow_shipout:Nn, \iow_shipout:Nx} +% With \eTeX{} available deferred writing is easy. % \begin{macrocode} -\cs_new_protected_nopar:Npn \file_list: +\cs_new_protected:Npn \iow_shipout:Nn #1#2 + { \iow_shipout_x:Nn #1 { \exp_not:n {#2} } } +\cs_generate_variant:Nn \iow_shipout:Nn { Nx } +% \end{macrocode} +% \end{macro} +% +% \subsection{Immediate writing} +% +% \begin{macro}{\iow_now:Nx} +% An abbreviation for an often used operation, which immediately +% writes its second argument expanded to the output stream. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \iow_now:Nx { \tex_immediate:D \iow_shipout_x:Nn } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\iow_now:Nn} +% 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. +% \begin{macrocode} +\cs_new_protected:Npn \iow_now:Nn #1#2 + { \iow_now:Nx #1 { \exp_not:n {#2} } } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\iow_log:n, \iow_log:x} +% \begin{macro}{\iow_term:n, \iow_term:x} +% Writing to the log and the terminal directly are relatively easy. +% \begin{macrocode} +\cs_set_protected_nopar:Npn \iow_log:x { \iow_now:Nx \c_log_iow } +\cs_new_protected_nopar:Npn \iow_log:n { \iow_now:Nn \c_log_iow } +\cs_set_protected_nopar:Npn \iow_term:x { \iow_now:Nx \c_term_iow } +\cs_new_protected_nopar:Npn \iow_term:n { \iow_now:Nn \c_term_iow } +% \end{macrocode} +%\end{macro} +%\end{macro} +% +% \subsection{Special characters for writing} +% +% \begin{macro}{\iow_newline:} +% Global variable holding the character that forces a new line when +% something is written to an output stream +% \begin{macrocode} +\cs_new_nopar:Npn \iow_newline: { ^^J } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\iow_char:N} +% Function to write any escaped char to an output stream. +% \begin{macrocode} +\cs_new_eq:NN \iow_char:N \cs_to_str:N +% \end{macrocode} +% \end{macro} +% +% \subsection{Hard-wrapping lines based on length} +% +% The code here implements a generic hard-wrapping function. This is +% used by the messaging system, but is designed such that it is +% available for other uses. +% +% \begin{macro}{\l_iow_line_length_int} +% This is the \enquote{raw} length of a line which can be written to a +% file. The standard value is the line length typically used by +% \TeX{}Live and Mik\TeX{}. +% \begin{macrocode} +\int_new:N \l_iow_line_length_int +\int_set:Nn \l_iow_line_length_int { 78 } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux]{\l_iow_target_length_int} +% This stores the target line length: the full length minus any +% part for a leader at the start of each line. +% \begin{macrocode} +\int_new:N \l_iow_target_length_int +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux] +% { +% \l_iow_current_line_int, +% \l_iow_current_word_int, +% \l_iow_current_indentation_int +% } +% These store the number of characters in the line and word currently +% being constructed, and the current indentation, respectively. +% \begin{macrocode} +\int_new:N \l_iow_current_line_int +\int_new:N \l_iow_current_word_int +\int_new:N \l_iow_current_indentation_int +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux] +% { +% \l_iow_current_line_tl, +% \l_iow_current_word_tl, +% \l_iow_current_indentation_tl +% } +% These hold the current line of text and current word, +% and a number of spaces for indentation, respectively. +% \begin{macrocode} +\tl_new:N \l_iow_current_line_tl +\tl_new:N \l_iow_current_word_tl +\tl_new:N \l_iow_current_indentation_tl +% \end{macrocode} +%\end{macro} +% +% \begin{macro}[aux]{\l_iow_wrap_tl} +% Used for the expansion step before detokenizing. +% \begin{macrocode} +\tl_new:N \l_iow_wrap_tl +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux]{\l_iow_wrapped_tl} +% The output from wrapping text: fully expanded and with lines +% which are not overly long. +% \begin{macrocode} +\tl_new:N \l_iow_wrapped_tl +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux]{\l_iow_line_start_bool} +% Boolean to avoid adding a space at the beginning of forced newlines. +% \begin{macrocode} +\bool_new:N \l_iow_line_start_bool +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux]{\c_catcode_other_space_tl} +% Lowercase a character with category code $12$ to produce an +% \enquote{other} space. We can do everything within the group, +% because \cs{tl_const:Nn} defines its argument globally. +% \begin{macrocode} +\group_begin: + \char_set_catcode_other:N \* + \char_set_lccode:nn {`\*} {`\ } + \tl_to_lowercase:n { \tl_const:Nn \c_catcode_other_space_tl { * } } +\group_end: +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux]{\c_iow_wrap_marker_tl} +% \begin{macro}[aux]{ +% \c_iow_wrap_end_marker_tl, +% \c_iow_wrap_newline_marker_tl, +% \c_iow_wrap_indent_marker_tl, +% \c_iow_wrap_unindent_marker_tl} +% \begin{macro}[aux]{\iow_wrap_new_marker:n} +% Every special action of the wrapping code is preceeded by +% the same recognizable string, \cs{c_iow_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_iow_wrap_marker_tl} look +% nicer. Note that \cs{iow_wrap_new_marker:n} does not +% survive the group, but all constants are defined globally. +% \begin{macrocode} +\group_begin: + \int_set_eq:NN \tex_escapechar:D \c_minus_one + \tl_const:Nx \c_iow_wrap_marker_tl + { \tl_to_str:n { \^^I \^^O \^^W \^^_ \^^W \^^R \^^A \^^P } } + \cs_set:Npn \iow_wrap_new_marker:n #1 + { + \tl_const:cx { c_iow_wrap_ #1 _marker_tl } + { + \c_catcode_other_space_tl + \c_iow_wrap_marker_tl + \c_catcode_other_space_tl + #1 + \c_catcode_other_space_tl + } + } + \iow_wrap_new_marker:n { end } + \iow_wrap_new_marker:n { newline } + \iow_wrap_new_marker:n { indent } + \iow_wrap_new_marker:n { unindent } +\group_end: +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\iow_indent:n} +% \begin{macro}[aux]{\iow_indent_expandable:n} +% We give a dummy (protected) definition to \cs{iow_indent:n} +% when outside messages. Within wrapped message, it places +% the instruction for increasing the indentation before its +% argument, and the instruction for unindenting afterwards. +% Note that there will be no forced line-break, so the indentation +% only changes when the next line is started. +% \begin{macrocode} +\cs_new_protected:Npn \iow_indent:n #1 { } +\cs_new:Npx \iow_indent_expandable:n #1 { - \seq_remove_duplicates:N \g_file_record_seq - \iow_log:n { *~File~List~* } - \seq_map_inline:Nn \g_file_record_seq { \iow_log:n {##1} } - \iow_log:n { ************* } + \c_iow_wrap_indent_marker_tl + #1 + \c_iow_wrap_unindent_marker_tl } % \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. +% \begin{macro}{\iow_wrap:xnnnN} +% The main wrapping function works as follows. The target number of +% characters in a line is calculated, before fully-expanding the input +% such that |\\| and \verb*|\ | are converted into the appropriate +% values. There is then a loop over each word in the input, which +% will do the actual wrapping. After the loop, the resulting text is +% passed on to the function which has been given as a post-processor. +% The argument |#4| is available for additional set up steps for +% the output. The definition of |\\| and \verb*|\ | use an +% \enquote{other} space rather than a normal space, because the latter +% might be absorbed by \TeX{} to end a number or other \texttt{f}-type +% expansions. The \cs{tl_to_str:N} step converts the \enquote{other} +% space back to a normal space. % \begin{macrocode} +\cs_new_protected:Npn \iow_wrap:xnnnN #1#2#3#4#5 + { + \group_begin: + \int_set:Nn \l_iow_target_length_int { \l_iow_line_length_int - ( #3 ) } + \int_zero:N \l_iow_current_indentation_int + \tl_clear:N \l_iow_current_indentation_tl + \int_zero:N \l_iow_current_line_int + \tl_clear:N \l_iow_current_line_tl + \tl_clear:N \l_iow_wrap_tl + \bool_set_true:N \l_iow_line_start_bool + \int_set_eq:NN \tex_escapechar:D \c_minus_one + \cs_set_nopar:Npx \{ { \token_to_str:N \{ } + \cs_set_nopar:Npx \# { \token_to_str:N \# } + \cs_set_nopar:Npx \} { \token_to_str:N \} } + \cs_set_nopar:Npx \% { \token_to_str:N \% } + \cs_set_nopar:Npx \~ { \token_to_str:N \~ } + \int_set:Nn \tex_escapechar:D { 92 } + \cs_set_eq:NN \\ \c_iow_wrap_newline_marker_tl + \cs_set_eq:NN \ \c_catcode_other_space_tl + \cs_set_eq:NN \iow_indent:n \iow_indent_expandable:n + #4 +%<*initex> + \tl_set:Nx \l_iow_wrap_tl {#1} +%</initex> %<*package> -\AtBeginDocument + \protected@edef \l_iow_wrap_tl {#1} +%</package> + \cs_set:Npn \\ { \iow_newline: #2 } + \use:x + { + \iow_wrap_loop:w + \tl_to_str:N \l_iow_wrap_tl + \tl_to_str:N \c_iow_wrap_end_marker_tl + \c_space_tl \c_space_tl + \exp_not:N \q_stop + } + \exp_args:NNo \group_end: + #5 \l_iow_wrapped_tl + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux]{\iow_wrap_loop:w} +% The loop grabs one word in the input, and checks whether it is +% the special marker, or a normal word. +% \begin{macrocode} +\cs_new_protected:Npn \iow_wrap_loop:w #1 ~ % { - \seq_set_from_clist:NN \l_file_internal_seq \@filelist - \seq_gconcat:NNN \g_file_record_seq \g_file_record_seq \l_file_internal_seq + \tl_set:Nn \l_iow_current_word_tl {#1} + \tl_if_eq:NNTF \l_iow_current_word_tl \c_iow_wrap_marker_tl + { \iow_wrap_special:w } + { \iow_wrap_word: } } -%</package> % \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux]{\iow_wrap_word:} +% \begin{macro}[aux]{\iow_wrap_word_fits:} +% \begin{macro}[aux]{\iow_wrap_word_newline:} +% For a normal word, update the line length, then test if the current +% word would fit in the current line, and call the appropriate function. +% If the word fits in the current line, add it to the line, preceded by +% a space unless it is the first word of the line. +% Otherwise, the current line is added to the result, with the run-on text. +% The current word (and its length) are then put in the new line. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \iow_wrap_word: + { + \int_set:Nn \l_iow_current_word_int + { \str_length_skip_spaces:N \l_iow_current_word_tl } + \int_add:Nn \l_iow_current_line_int { \l_iow_current_word_int } + \int_compare:nNnTF \l_iow_current_line_int < \l_iow_target_length_int + { \iow_wrap_word_fits: } + { \iow_wrap_word_newline: } + \iow_wrap_loop:w + } +\cs_new_protected_nopar:Npn \iow_wrap_word_fits: + { + \bool_if:NTF \l_iow_line_start_bool + { + \bool_set_false:N \l_iow_line_start_bool + \tl_put_right:Nx \l_iow_current_line_tl + { \l_iow_current_indentation_tl \l_iow_current_word_tl } + \int_add:Nn \l_iow_current_line_int + { \l_iow_current_indentation_int } + } + { + \tl_put_right:Nx \l_iow_current_line_tl + { ~ \l_iow_current_word_tl } + \int_incr:N \l_iow_current_line_int + } + } +\cs_new_protected_nopar:Npn \iow_wrap_word_newline: + { + \tl_put_right:Nx \l_iow_wrapped_tl + { \l_iow_current_line_tl \\ } + \int_set:Nn \l_iow_current_line_int + { + \l_iow_current_word_int + + \l_iow_current_indentation_int + } + \tl_set:Nx \l_iow_current_line_tl + { \l_iow_current_indentation_tl \l_iow_current_word_tl } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[aux]{\iow_wrap_special:w} +% \begin{macro}[aux]{\iow_wrap_newline:w} +% \begin{macro}[aux]{\iow_wrap_indent:w} +% \begin{macro}[aux]{\iow_wrap_unindent:w} +% \begin{macro}[aux]{\iow_wrap_end:w} +% When the \enquote{special} marker is encountered, +% read what operation to perform, as a space-delimited +% argument, perform it, and remember to loop. +% In fact, to avoid spurious spaces when two special +% actions follow each other, we look ahead for another +% copy of the marker. +% Forced newlines are almost identical to those caused by overflow, +% except that here the word is empty. +% To indent more, add four spaces to the start of the indentation +% token list. To reduce indentation, rebuild the indentation token +% list using \cs{prg_replicate:nn}. +% At the end, we simply save the last line (without the run-on text), +% and prevent the loop. +% \begin{macrocode} +\cs_new_protected:Npn \iow_wrap_special:w #1 ~ #2 ~ #3 ~ % + { + \use:c { iow_wrap_#1: } + \str_if_eq:xxTF { #2~#3 } { ~ \c_iow_wrap_marker_tl } + { \iow_wrap_special:w } + { \iow_wrap_loop:w #2 ~ #3 ~ } + } +\cs_new_protected_nopar:Npn \iow_wrap_newline: + { + \tl_put_right:Nx \l_iow_wrapped_tl + { \l_iow_current_line_tl \\ } + \int_zero:N \l_iow_current_line_int + \tl_clear:N \l_iow_current_line_tl + \bool_set_true:N \l_iow_line_start_bool + } +\cs_new_protected_nopar:Npx \iow_wrap_indent: + { + \int_add:Nn \l_iow_current_indentation_int \c_four + \tl_put_right:Nx \exp_not:N \l_iow_current_indentation_tl + { \c_space_tl \c_space_tl \c_space_tl \c_space_tl } + } +\cs_new_protected_nopar:Npn \iow_wrap_unindent: + { + \int_sub:Nn \l_iow_current_indentation_int \c_four + \tl_set:Nx \l_iow_current_indentation_tl + { \prg_replicate:nn \l_iow_current_indentation_int { ~ } } + } +\cs_new_protected_nopar:Npn \iow_wrap_end: + { + \tl_put_right:Nx \l_iow_wrapped_tl + { \l_iow_current_line_tl } + \use_none_delimit_by_q_stop:w + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[aux]{\str_length_skip_spaces:N} +% \begin{macro}[aux]{\str_length_skip_spaces:n} +% \begin{macro}[aux]{\str_length_loop:NNNNNNNNN} +% The wrapping code requires to measure the number of character +% in each word. This could be done with \cs{tl_length:n}, but +% it is ten times faster (literally) to use the code below. +% \begin{macrocode} +\cs_new_nopar:Npn \str_length_skip_spaces:N + { \exp_args:No \str_length_skip_spaces:n } +\cs_new:Npn \str_length_skip_spaces:n #1 + { + \int_value:w \int_eval:w + \exp_after:wN \str_length_loop:NNNNNNNNN \tl_to_str:n {#1} + { X8 } { X7 } { X6 } { X5 } { X4 } { X3 } { X2 } { X1 } { X0 } \q_stop + \int_eval_end: + } +\cs_new:Npn \str_length_loop:NNNNNNNNN #1#2#3#4#5#6#7#8#9 + { + \if_catcode:w X #9 + \exp_after:wN \use_none_delimit_by_q_stop:w + \else: + 9 + + \exp_after:wN \str_length_loop:NNNNNNNNN + \fi: + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \subsection{Reading input} +% +% \begin{macro}[int]{\if_eof:w} +% The primitive conditional +% \begin{macrocode} +\cs_new_eq:NN \if_eof:w \tex_ifeof:D +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[pTF]{\ior_if_eof:N} +% To test if some particular input stream is exhausted the following +% conditional is provided. +% \begin{macrocode} +\prg_new_conditional:Nnn \ior_if_eof:N { p , T , F , TF } + { + \cs_if_exist:NTF #1 + { + \if_int_compare:w #1 = \c_sixteen + \prg_return_true: + \else: + \if_eof:w #1 + \prg_return_true: + \else: + \prg_return_false: + \fi: + \fi: + } + { \prg_return_true: } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ior_to:NN, \ior_gto:NN} +% And here we read from files. +% \begin{macrocode} +\cs_new_protected:Npn \ior_to:NN #1#2 + { \tex_read:D #1 to #2 } +\cs_new_protected:Npn \ior_gto:NN #1#2 + { \tex_global:D \tex_read:D #1 to #2 } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ior_str_to:NN, \ior_str_gto:NN} +% Reading as strings is also a primitive wrapper. +% \begin{macrocode} +\cs_new_protected:Npn \ior_str_to:NN #1#2 + { \etex_readline:D #1 to #2 } +\cs_new_protected:Npn \ior_str_gto:NN #1#2 + { \tex_global:D \etex_readline:D #1 to #2 } +% \end{macrocode} +% \end{macro} +% +% \subsection{Experimental functions} +% +% \begin{macro}{\ior_map_inline:Nn, \ior_str_map_inline:Nn} +% \begin{macro}[aux]{\ior_str_map_inline_aux:NNn} +% \begin{macro}[aux]{\ior_str_map_inline_aux:NNNn} +% \begin{macro}[aux]{\ior_str_map_inline_loop:NNN} +% \begin{variable}{\l_ior_internal_tl} +% 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}. +% This mapping cannot be nested as the stream has only one \enquote{current +% line}. +% \begin{macrocode} +\cs_new_protected_nopar:Npn \ior_map_inline:Nn + { \ior_map_inline_aux:NNn \ior_to:NN } +\cs_new_protected_nopar:Npn \ior_str_map_inline:Nn + { \ior_map_inline_aux:NNn \ior_str_to:NN } +\cs_new_protected_nopar:Npn \ior_map_inline_aux:NNn + { + \exp_args:Nc \ior_map_inline_aux:NNNn + { ior_map_ \int_use:N \g_prg_map_int :n } + } +\cs_new_protected:Npn \ior_map_inline_aux:NNNn #1#2#3#4 + { + \cs_set:Npn #1 ##1 {#4} + \int_gincr:N \g_prg_map_int + \ior_if_eof:NF #3 { \ior_map_inline_loop:NNN #1#2#3 } + \prg_break_point:n { \int_gdecr:N \g_prg_map_int } + } +\cs_new_protected:Npn \ior_map_inline_loop:NNN #1#2#3 + { + #2 #3 \l_ior_internal_tl + \ior_if_eof:NF #3 + { + \exp_args:No #1 \l_ior_internal_tl + \ior_map_inline_loop:NNN #1#2#3 + } + } +\tl_new:N \l_ior_internal_tl +% \end{macrocode} +% \end{variable} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \subsection{Messages} +% +% \begin{macrocode} +\msg_kernel_new:nnnn { file } { file-not-found } + { Space~in~file~name~'#1'. } + { + Spaces~are~not~permitted~in~files~loaded~by~LaTeX: \\ + Further~errors~may~follow! + } +\msg_kernel_new:nnnn { file } { space-in-file-name } + { Space~in~file~name~'#1'. } + { + Spaces~are~not~permitted~in~files~loaded~by~LaTeX: \\ + Further~errors~may~follow! + } +% \end{macrocode} +% +% \subsection{Deprecated functions} +% +% Deprecated on 2012-02-10, for removal by 2012-05-31. +% +% \begin{macro}{\iow_now_when_avail:Nn, \iow_now_when_avail:Nx} +% For writing only if the stream requested is open at all. +% \begin{macrocode} +\cs_new_protected:Npn \iow_now_when_avail:Nn #1 + { \cs_if_free:NTF #1 { \use_none:n } { \iow_now:Nn #1 } } +\cs_new_protected:Npn \iow_now_when_avail:Nx #1 + { \cs_if_free:NTF #1 { \use_none:n } { \iow_now:Nx #1 } } +% \end{macrocode} +% \end{macro} +% +% Deprecated on 2011-05-27, for removal by 2011-08-31. +% +% \begin{macro}{\iow_now_buffer_safe:Nn, \iow_now_buffer_safe:Nx} +% This is much more easily done using the wrapping system: there is +% an expansion there, so a bit of a hack is needed. +% \begin{macrocode} +%<*deprecated> +\cs_new_protected:Npn \iow_now_buffer_safe:Nn #1#2 + { \iow_wrap:xnnnN { \exp_not:n {#2} } { } \c_zero { } \iow_now:Nn #1 } +\cs_new_protected:Npn \iow_now_buffer_safe:Nx #1#2 + { \iow_wrap:xnnnN {#2} { } \c_zero { } \iow_now:Nn #1 } +%</deprecated> +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ior_open_streams:} +% \begin{macro}{\iow_open_streams:} +% Slightly misleading names. +% \begin{macrocode} +%<*deprecated> +\cs_new_eq:NN \ior_open_streams: \ior_list_streams: +\cs_new_eq:NN \iow_open_streams: \iow_list_streams: +%</deprecated> +% \end{macrocode} +% \end{macro} +% \end{macro} % % \begin{macrocode} %</initex|package> |