diff options
Diffstat (limited to 'macros/latex/required/l3kernel/l3sys.dtx')
-rw-r--r-- | macros/latex/required/l3kernel/l3sys.dtx | 1383 |
1 files changed, 1383 insertions, 0 deletions
diff --git a/macros/latex/required/l3kernel/l3sys.dtx b/macros/latex/required/l3kernel/l3sys.dtx new file mode 100644 index 0000000000..085b5f2c97 --- /dev/null +++ b/macros/latex/required/l3kernel/l3sys.dtx @@ -0,0 +1,1383 @@ +% \iffalse meta-comment +% +%% File: l3sys.dtx +% +% Copyright (C) 2015-2024 The LaTeX 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} +\end{document} +%</driver> +% \fi +% +% \title{^^A +% The \pkg{l3sys} module\\ System/runtime functions^^A +% } +% +% \author{^^A +% The \LaTeX{} Project\thanks +% {^^A +% E-mail: +% \href{mailto:latex-team@latex-project.org} +% {latex-team@latex-project.org}^^A +% }^^A +% } +% +% \date{Released 2024-04-11} +% +% \maketitle +% +% \begin{documentation} +% +% \section{The name of the job} +% +% \begin{variable}[added = 2015-09-19, updated = 2019-10-27]{\c_sys_jobname_str} +% Constant that gets the \enquote{job name} assigned when \TeX{} starts. +% \begin{texnote} +% This is the \TeX{} primitive \tn{jobname}. For technical +% reasons, the string here is not of the same internal form as other, +% but may be manipulated using normal string functions. +% \end{texnote} +% \end{variable} +% +% \section{Date and time} +% +% \begin{variable}[added = 2015-09-22] +% { +% \c_sys_minute_int, +% \c_sys_hour_int, +% \c_sys_day_int, +% \c_sys_month_int, +% \c_sys_year_int, +% } +% The date and time at which the current job was started: these are +% all reported as integers. +% \begin{texnote} +% Whilst the underlying \TeX{} primitives \tn{time}, \tn{day}, \tn{month}, +% and~\tn{year} can be altered by the user, this +% interface to the time and date is intended to be the \enquote{real} +% values. +% \end{texnote} +% \end{variable} +% +% \begin{variable}[added = 2023-08-27]{\c_sys_timestamp_str} +% The timestamp for the current job: the format is as described for +% \cs{file_timestamp:n}. +% \end{variable} +% +% \section{Engine} +% +% \begin{function}[added = 2015-09-07, EXP, pTF] +% { +% \sys_if_engine_luatex:, +% \sys_if_engine_pdftex:, +% \sys_if_engine_ptex: , +% \sys_if_engine_uptex: , +% \sys_if_engine_xetex: +% } +% \begin{syntax} +% \cs{sys_if_engine_pdftex_p:} +% \cs{sys_if_engine_pdftex:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Conditionals which allow engine-specific code to be used. The names +% follow naturally from those of the engine binaries: note that the +% |(u)ptex| tests are for \epTeX{} and \eupTeX{} as \pkg{expl3} requires +% the \eTeX{} extensions. Each conditional is true for +% \emph{exactly one} supported engine. In particular, +% |\sys_if_engine_ptex_p:| is true for \epTeX{} but false for \eupTeX{}. +% \end{function} +% +% \begin{variable}[added = 2015-09-19]{\c_sys_engine_str} +% The current engine given as a lower case string: one of +% |luatex|, |pdftex|, |ptex|, |uptex| or |xetex|. +% \end{variable} +% +% \begin{variable}[added = 2020-08-20]{\c_sys_engine_exec_str} +% The name of the standard executable for the current \TeX{} engine given +% as a lower case string: one of |luatex|, +% |luahbtex|, |pdftex|, |eptex|, |euptex| or |xetex|. +% \end{variable} +% +% \begin{variable}[added = 2020-08-20]{\c_sys_engine_format_str} +% The name of the preloaded format for the current \TeX{} run given +% as a lower case string: one of +% |lualatex| (or |dvilualatex|), +% |pdflatex| (or |latex|), |platex|, |uplatex| or |xelatex| for \LaTeX{}, +% similar names for plain \TeX{} (except \pdfTeX{} in DVI mode yields +% |etex|), and |cont-en| for Con\TeX{}t (i.e.~the +% \tn{fmtname}). +% \end{variable} +% +% \begin{variable}[added = 2018-05-02]{\c_sys_engine_version_str} +% The version string of the current engine, in the same form as +% given in the banner issued when running a job. For \pdfTeX{} +% and \LuaTeX{} this is of the form +% \begin{quote} +% \meta{major}.\meta{minor}.\meta{revision} +% \end{quote} +% For \XeTeX{}, the form is +% \begin{quote} +% \meta{major}.\meta{minor} +% \end{quote} +% For \pTeX{} and \upTeX{}, only releases since \TeX{} Live 2018 +% make the data available, and the form is more complex, as it comprises +% the \pTeX{} version, the \upTeX{} version and the e-\pTeX{} version. +% \begin{quote} +% p\meta{major}.\meta{minor}.\meta{revision}-u\meta{major}.\meta{minor}^^A +% -\meta{epTeX} +% \end{quote} +% where the |u| part is only present for \upTeX{}. +% \end{variable} +% +% \begin{function}[added = 2021-05-12, EXP]{\sys_timer:} +% \begin{syntax} +% \cs{sys_timer:} +% \end{syntax} +% Expands to the current value of the engine's timer clock, a +% non-negative integer. This function is only defined for engines with +% timer support. This command measures not just CPU time but +% real time (including time waiting for user input). The unit are +% scaled seconds ($2^{-16}$ seconds). +% \end{function} +% +% \begin{function}[added = 2021-05-12, EXP, pTF]{\sys_if_timer_exist:} +% \begin{syntax} +% \cs{sys_if_timer_exist_p:} +% \cs{sys_if_timer_exist:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests whether current engine has timer support. +% \end{function} +% +% \section{Output format} +% +% \begin{function}[added = 2015-09-19, EXP, pTF] +% { +% \sys_if_output_dvi:, +% \sys_if_output_pdf: +% } +% \begin{syntax} +% \cs{sys_if_output_dvi_p:} +% \cs{sys_if_output_dvi:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Conditionals which give the current output mode the \TeX{} run is +% operating in. This is always one of two outcomes, DVI mode or +% PDF mode. The two sets of conditionals are thus complementary and +% are both provided to allow the programmer to emphasise the most +% appropriate case. +% \end{function} +% +% \begin{variable}[added = 2015-09-19]{\c_sys_output_str} +% The current output mode given as a lower case string: one of +% |dvi| or |pdf|. +% \end{variable} +% +% \section{Platform} +% +% \begin{function}[added = 2018-07-27, EXP, pTF] +% { +% \sys_if_platform_unix:, +% \sys_if_platform_windows: +% } +% \begin{syntax} +% \cs{sys_if_platform_unix_p:} +% \cs{sys_if_platform_unix:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Conditionals which allow platform-specific code to be used. The names +% follow the \Lua{} |os.type()| function, \emph{i.e.}~all Unix-like systems +% are |unix| (including Linux and MacOS). +% \end{function} +% +% \begin{variable}[added = 2018-07-27]{\c_sys_platform_str} +% The current platform given as a lower case string: one of +% |unix|, |windows| or |unknown|. +% \end{variable} +% +% \section{Random numbers} +% +% \begin{function}[added = 2017-05-27, EXP]{\sys_rand_seed:} +% \begin{syntax} +% \cs{sys_rand_seed:} +% \end{syntax} +% Expands to the current value of the engine's random seed, a +% non-negative integer. In engines without random number support this +% expands to $0$. +% \end{function} +% +% \begin{function}[added = 2017-05-27]{\sys_gset_rand_seed:n} +% \begin{syntax} +% \cs{sys_gset_rand_seed:n} \Arg{int expr} +% \end{syntax} +% Globally sets the seed for the engine's pseudo-random number +% generator to the \meta{integer expression}. This random seed +% affects all \cs[no-index]{\ldots{}_rand} functions (such as +% \cs{int_rand:nn} or \cs{clist_rand_item:n}) as well as other +% packages relying on the engine's random number generator. In +% engines without random number support this produces an error. +% \begin{texnote} +% While a $32$-bit (signed) integer can be given as a seed, only the +% absolute value is used and any number beyond $2^{28}$ is divided +% by an appropriate power of~$2$. We recommend using an integer in +% $[0,2^{28}-1]$. +% \end{texnote} +% \end{function} +% +% \section{Access to the shell} +% +% \begin{function}[noTF, added = 2019-09-20] +% {\sys_get_shell:nnN} +% \begin{syntax} +% \cs{sys_get_shell:nnN} \Arg{shell~command} \Arg{setup} \meta{tl~var} +% \cs{sys_get_shell:nnNTF} \Arg{shell~command} \Arg{setup} \meta{tl~var} \Arg{true code} \Arg{false code} +% \end{syntax} +% Defines \meta{tl~var} to the text returned by the \meta{shell command}. +% The \meta{shell command} is converted to a string using +% \cs{tl_to_str:n}. Category codes may need to be set appropriately +% via the \meta{setup} argument, which is run just before running the +% \meta{shell command} (in a group). +% If shell escape is disabled, the \meta{tl~var} will be set to +% \cs{q_no_value} in the non-branching version. +% Note that quote characters (|"|) \emph{cannot} be used inside the +% \meta{shell command}. The \cs{sys_get_shell:nnNTF} conditional +% inserts the \meta{true code} if the shell is available and no quote is +% detected, and the \meta{false code} otherwise. +% +% \emph{Note}: It is not possible to tell from \TeX{} if a command is allowed +% in restricted shell escape. If restricted escape is enabled, the +% \texttt{true} branch is taken: if the command is forbidden at this stage, a +% low-level \TeX{} error will arise. +% \end{function} +% +% \begin{variable}[added = 2017-05-27]{\c_sys_shell_escape_int} +% This variable exposes the internal triple of the shell escape +% status. The possible values are +% \begin{description} +% \item[0] Shell escape is disabled +% \item[1] Unrestricted shell escape is enabled +% \item[2] Restricted shell escape is enabled +% \end{description} +% \end{variable} +% +% \begin{function}[added = 2017-05-27, EXP, pTF]{\sys_if_shell:} +% \begin{syntax} +% \cs{sys_if_shell_p:} +% \cs{sys_if_shell:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Performs a check for whether shell escape is enabled. This +% returns true if either of restricted or unrestricted shell escape +% is enabled. +% \end{function} +% +% \begin{function}[added = 2017-05-27, EXP, pTF]{\sys_if_shell_unrestricted:} +% \begin{syntax} +% \cs{sys_if_shell_unrestricted_p:} +% \cs{sys_if_shell_unrestricted:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Performs a check for whether \emph{unrestricted} shell escape is +% enabled. +% \end{function} +% +% \begin{function}[added = 2017-05-27, EXP, pTF]{\sys_if_shell_restricted:} +% \begin{syntax} +% \cs{sys_if_shell_restricted_p:} +% \cs{sys_if_shell_restricted:TF} \Arg{true code} \Arg{false code} +% \end{syntax} +% Performs a check for whether \emph{restricted} shell escape is +% enabled. This returns false if unrestricted shell escape is +% enabled. Unrestricted shell escape is not considered a superset +% of restricted shell escape in this case. To find whether any +% shell escape is enabled use \cs{sys_if_shell:TF}. +% \end{function} +% +% \begin{function}[added = 2017-05-27] +% {\sys_shell_now:n, \sys_shell_now:e} +% \begin{syntax} +% \cs{sys_shell_now:n} \Arg{tokens} +% \end{syntax} +% Execute \meta{tokens} through shell escape immediately. +% \end{function} +% +% \begin{function}[added = 2017-05-27] +% {\sys_shell_shipout:n, \sys_shell_shipout:e} +% \begin{syntax} +% \cs{sys_shell_shipout:n} \Arg{tokens} +% \end{syntax} +% Execute \meta{tokens} through shell escape at shipout. +% \end{function} +% +% \section{System queries} +% +% Some queries can be made about the file system, etc., without needing to +% use unrestricted shell escape. This is carried out using the script +% \texttt{l3sys-query}, which is documented separately. The wrappers here +% use this script, if available, to obtain system information that is +% not directly available within the \TeX{} run. Note that if restricted +% shell escape is disabled, no results can be obtained. +% +% \begin{function}[added = 2024-03-08, updated = 2024-04-08] +% {\sys_get_query:nN, \sys_get_query:nnN, \sys_get_query:nnnN} +% \begin{syntax} +% \cs{sys_get_query:nN} \Arg{cmd} \Arg{tl var} +% \cs{sys_get_query:nnN} \Arg{cmd} \Arg{spec} \Arg{tl var} +% \cs{sys_get_query:nnnN} \Arg{cmd} \Arg{options} \Arg{spec} \Arg{tl var} +% \end{syntax} +% Sets the \meta{tl var} to the information returned by the +% \texttt{l3sys-query} \meta{cmd}, potentially supplying the \meta{options} +% and \meta{spec} to the query call. The valid \meta{cmd} names are at present +% \begin{itemize} +% \item \texttt{pwd} Returns the present working directory +% \item \texttt{ls} Returns a directory listing, using the \meta{spec} to +% select files and applying the \meta{options} if given +% \end{itemize} +% The \meta{spec} is likely to contain the wildcards |*| or |?|, +% and will automatically be passed to +% the script without shell expansion. In a glob is needed within the +% \meta{options}, this will need to be protected from shell expansion +% using |'| tokens. +% +% The \meta{spec} and \meta{options}, if given, are expanded fully +% before passing to the underlying script. +% +% Spaces in the output are stored as active tokens, allowing them to be +% replaced by for example a visible space easily. Other non-letter +% characters in the ASCII range are set to category code~12. The category +% codes for characters out of the ASCII range are left unchanged: typically +% this will mean that with an 8-bit engine, accented values can be typeset +% directly whilst in Unicode engines, standard category code setup will +% apply. +% +% If more than one line of text is returned by the \meta{cmd}, these will be +% separated by character~13 (|^^M|) tokens of category code~12. In most +% cases, \cs{sys_split_query:nnnN} should be preferred when multi-line +% output is expected. +% \end{function} +% +% \begin{function}[added = 2024-03-08] +% {\sys_split_query:nN, \sys_split_query:nnN, \sys_split_query:nnnN} +% \begin{syntax} +% \cs{sys_split_query:nN} \Arg{cmd} \Arg{seq} +% \cs{sys_split_query:nnN} \Arg{cmd} \Arg{spec} \Arg{seq} +% \cs{sys_split_query:nnnN} \Arg{cmd} \Arg{options} \Arg{spec} \Arg{seq} +% \end{syntax} +% Works as described for \cs{sys_split_query:nnnN}, but sets the \meta{seq} +% to contain one entry for each line returned by \texttt{l3sys-query}. +% This function should therefore be preferred where multi-line return is +% expected, e.g.~for the \texttt{ls} command. +% \end{function} +% +% \section{Loading configuration data} +% +% \begin{function}[added = 2019-09-12]{\sys_load_backend:n} +% \begin{syntax} +% \cs{sys_load_backend:n} \Arg{backend} +% \end{syntax} +% Loads the additional configuration file needed for backend support. +% If the \meta{backend} is empty, the standard backend for the engine in +% use will be loaded. This command may only be used once. +% \end{function} +% +% \begin{function}[added = 2022-07-29]{\sys_ensure_backend:} +% \begin{syntax} +% \cs{sys_ensure_backend:} +% \end{syntax} +% Ensures that a backend has been loaded by calling \cs{sys_load_backend:n} +% if required. +% \end{function} +% +% \begin{variable}{\c_sys_backend_str} +% Set to the name of the backend in use by \cs{sys_load_backend:n} when +% issued. Possible values are +% \begin{itemize} +% \item \texttt{pdftex} +% \item \texttt{luatex} +% \item \texttt{xetex} +% \item \texttt{dvips} +% \item \texttt{dvipdfmx} +% \item \texttt{dvisvgm} +% \end{itemize} +% \end{variable} +% +% \begin{function}[added = 2019-09-12]{\sys_load_debug:} +% \begin{syntax} +% \cs{sys_load_debug:} +% \end{syntax} +% Load the additional configuration file for debugging support. +% \end{function} +% +% \subsection{Final settings} +% +% \begin{function}[added = 2019-10-06]{\sys_finalise:} +% \begin{syntax} +% \cs{sys_finalise:} +% \end{syntax} +% Finalises all system-dependent functionality: required before loading +% a backend. +% \end{function} +% +% \end{documentation} +% +% \begin{implementation} +% +% \section{\pkg{l3sys} implementation} +% +% \begin{macrocode} +%<@@=sys> +% \end{macrocode} +% +% \subsection{Kernel code} +% +% \begin{macrocode} +%<*package> +%<*tex> +% \end{macrocode} +% +% \begin{macro}{\l_@@_tmp_tl} +% \begin{macrocode} +\tl_new:N \l_@@_tmp_tl +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Detecting the engine} +% +% \begin{macro}{\@@_const:nn} +% Set the |T|, |F|, |TF|, |p| forms of |#1| to be constants equal to +% the result of evaluating the boolean expression~|#2|. +% \begin{macrocode} +\cs_new_protected:Npn \@@_const:nn #1#2 + { + \bool_if:nTF {#2} + { + \cs_new_eq:cN { #1 :T } \use:n + \cs_new_eq:cN { #1 :F } \use_none:n + \cs_new_eq:cN { #1 :TF } \use_i:nn + \cs_new_eq:cN { #1 _p: } \c_true_bool + } + { + \cs_new_eq:cN { #1 :T } \use_none:n + \cs_new_eq:cN { #1 :F } \use:n + \cs_new_eq:cN { #1 :TF } \use_ii:nn + \cs_new_eq:cN { #1 _p: } \c_false_bool + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[pTF, EXP] +% { +% \sys_if_engine_luatex:, +% \sys_if_engine_pdftex:, +% \sys_if_engine_ptex:, +% \sys_if_engine_uptex:, +% \sys_if_engine_xetex: +% } +% \begin{variable}{\c_sys_engine_str} +% Set up the engine tests on the basis exactly one test should be true. +% Mainly a case of looking for the appropriate marker primitive. +% \begin{macrocode} +\str_const:Ne \c_sys_engine_str + { + \cs_if_exist:NT \tex_luatexversion:D { luatex } + \cs_if_exist:NT \tex_pdftexversion:D { pdftex } + \cs_if_exist:NT \tex_kanjiskip:D + { + \cs_if_exist:NTF \tex_enablecjktoken:D + { uptex } + { ptex } + } + \cs_if_exist:NT \tex_XeTeXversion:D { xetex } + } +\tl_map_inline:nn { { luatex } { pdftex } { ptex } { uptex } { xetex } } + { + \@@_const:nn { sys_if_engine_ #1 } + { \str_if_eq_p:Vn \c_sys_engine_str {#1} } + } +% \end{macrocode} +% \end{variable} +% \end{macro} +% +% \begin{variable}{\c_sys_engine_exec_str,\c_sys_engine_format_str} +% Take the functions defined above, and set up the engine and format +% names. \cs{c_sys_engine_exec_str} differs from \cs{c_sys_engine_str} +% as it is the \emph{actual} engine name, not a \enquote{filtered} +% version. It differs for |ptex| and |uptex|, which have a leading +% |e|, and for |luatex|, because \LaTeX{} uses the \Lua HB\TeX{} +% engine. +% +% \cs{c_sys_engine_format_str} is quite similar to +% \cs{c_sys_engine_str}, except that it differentiates |pdflatex| from +% |latex| (which is \pdfTeX{} in DVI mode). This differentiation, +% however, is reliable only if the user doesn't change +% \cs{tex_pdfoutput:D} before loading this code. +% \begin{macrocode} +\group_begin: + \cs_set_eq:NN \lua_now:e \tex_directlua:D + \str_const:Ne \c_sys_engine_exec_str + { + \sys_if_engine_pdftex:T { pdf } + \sys_if_engine_xetex:T { xe } + \sys_if_engine_ptex:T { ep } + \sys_if_engine_uptex:T { eup } + \sys_if_engine_luatex:T + { + lua \lua_now:e + { + if (pcall(require, 'luaharfbuzz')) then ~ + tex.print("hb") ~ + end + } + } + tex + } +\group_end: +\str_const:Ne \c_sys_engine_format_str + { + \cs_if_exist:NTF \fmtname + { + \bool_lazy_or:nnTF + { \str_if_eq_p:Vn \fmtname { plain } } + { \str_if_eq_p:Vn \fmtname { LaTeX2e } } + { + \sys_if_engine_pdftex:T + { \int_compare:nNnT { \tex_pdfoutput:D } = { 1 } { pdf } } + \sys_if_engine_xetex:T { xe } + \sys_if_engine_ptex:T { p } + \sys_if_engine_uptex:T { up } + \sys_if_engine_luatex:T + { + \int_compare:nNnT { \tex_pdfoutput:D } = { 0 } { dvi } + lua + } + \str_if_eq:VnTF \fmtname { LaTeX2e } + { latex } + { + \bool_lazy_and:nnT + { \sys_if_engine_pdftex_p: } + { \int_compare_p:nNn { \tex_pdfoutput:D } = { 0 } } + { e } + tex + } + } + { \fmtname } + } + { unknown } + } +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\c_sys_engine_version_str} +% Various different engines, various different ways to extract the +% data! +% \begin{macrocode} +\str_const:Ne \c_sys_engine_version_str + { + \str_case:on \c_sys_engine_str + { + { pdftex } + { + \int_div_truncate:nn { \tex_pdftexversion:D } { 100 } + . + \int_mod:nn { \tex_pdftexversion:D } { 100 } + . + \tex_pdftexrevision:D + } + { ptex } + { + \cs_if_exist:NT \tex_ptexversion:D + { + p + \int_use:N \tex_ptexversion:D + . + \int_use:N \tex_ptexminorversion:D + \tex_ptexrevision:D + - + \int_use:N \tex_epTeXversion:D + } + } + { luatex } + { + \int_div_truncate:nn { \tex_luatexversion:D } { 100 } + . + \int_mod:nn { \tex_luatexversion:D } { 100 } + . + \tex_luatexrevision:D + } + { uptex } + { + \cs_if_exist:NT \tex_ptexversion:D + { + p + \int_use:N \tex_ptexversion:D + . + \int_use:N \tex_ptexminorversion:D + \tex_ptexrevision:D + - + u + \int_use:N \tex_uptexversion:D + \tex_uptexrevision:D + - + \int_use:N \tex_epTeXversion:D + } + } + { xetex } + { + \int_use:N \tex_XeTeXversion:D + \tex_XeTeXrevision:D + } + } + } +% \end{macrocode} +% \end{variable} +% +% \subsubsection{Platform} +% +% \begin{macro}[pTF]{\sys_if_platform_unix:, \sys_if_platform_windows:} +% \begin{variable}{\c_sys_platform_str} +% Setting these up requires the file module (file lookup), so is actually +% implemented there. +% \end{variable} +% \end{macro} +% +% \subsubsection{Configurations} +% +% \begin{macro}{\sys_load_backend:n} +% \begin{macro}{\@@_load_backend_check:N} +% \begin{variable}{\c_sys_backend_str} +% Loading the backend code is pretty simply: check that the backend is valid, +% then load it up. +% \begin{macrocode} +\cs_new_protected:Npn \sys_load_backend:n #1 + { + \sys_finalise: + \str_if_exist:NTF \c_sys_backend_str + { + \str_if_eq:VnF \c_sys_backend_str {#1} + { \msg_error:nn { sys } { backend-set } } + } + { + \tl_if_blank:nF {#1} + { \tl_gset:Nn \g_@@_backend_tl {#1} } + \@@_load_backend_check:N \g_@@_backend_tl + \str_const:Ne \c_sys_backend_str { \g_@@_backend_tl } + \__kernel_sys_configuration_load:n + { l3backend- \c_sys_backend_str } + } + } +\cs_new_protected:Npn \@@_load_backend_check:N #1 + { + \sys_if_engine_xetex:TF + { + \str_case:VnF #1 + { + { dvisvgm } { } + { xdvipdfmx } { \tl_gset:Nn #1 { xetex } } + { xetex } { } + } + { + \msg_error:nnee { sys } { wrong-backend } + #1 { xetex } + \tl_gset:Nn #1 { xetex } + } + } + { + \sys_if_output_pdf:TF + { + \str_if_eq:VnTF #1 { pdfmode } + { + \sys_if_engine_luatex:TF + { \tl_gset:Nn #1 { luatex } } + { \tl_gset:Nn #1 { pdftex } } + } + { + \bool_lazy_or:nnF + { \str_if_eq_p:Vn #1 { luatex } } + { \str_if_eq_p:Vn #1 { pdftex } } + { + \msg_error:nnee { sys } { wrong-backend } + #1 { \sys_if_engine_luatex:TF { luatex } { pdftex } } + \sys_if_engine_luatex:TF + { \tl_gset:Nn #1 { luatex } } + { \tl_gset:Nn #1 { pdftex } } + } + } + } + { + \str_case:VnF #1 + { + { dvipdfmx } { } + { dvips } { } + { dvisvgm } { } + } + { + \msg_error:nnee { sys } { wrong-backend } + #1 { dvips } + \tl_gset:Nn #1 { dvips } + } + } + } + } +% \end{macrocode} +% \end{variable} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\sys_ensure_backend:} +% A simple wrapper. +% \begin{macrocode} +\cs_new_protected:Npn \sys_ensure_backend: + { + \str_if_exist:NF \c_sys_backend_str + { \sys_load_backend:n { } } + } +% \end{macrocode} +% \end{macro} +% +% \begin{variable}{\g_@@_debug_bool} +% \begin{macrocode} +\bool_new:N \g_@@_debug_bool +% \end{macrocode} +% \end{variable} +% +% \begin{macro}{\sys_load_debug:} +% The most complicated thing here is that we can only use +% \cs{__kernel_sys_configuration_load:n} in the preamble in \LaTeX{}. +% \begin{macrocode} +\cs_new_protected:Npn \sys_load_debug: + { + \bool_if:NF \g_@@_debug_bool + { \__kernel_sys_configuration_load:n { l3debug } } + \bool_gset_true:N \g_@@_debug_bool + } +\cs_if_exist:NT \@expl@finalise@setup@@@@ + { + \tl_gput_right:Nn \@expl@finalise@setup@@@@ + { + \tl_gput_right:Nn \@kernel@after@begindocument + { + \cs_gset_protected:Npn \sys_load_debug: + { \msg_error:nn { sys } { load-debug-in-preamble } } + } + } + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Access to the shell} +% +% \begin{variable}{\l_@@_internal_tl} +% \begin{macrocode} +\tl_new:N \l_@@_internal_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\c_@@_marker_tl} +% The same idea as the marker for rescanning token lists. +% \begin{macrocode} +\tl_const:Ne \c_@@_marker_tl { : \token_to_str:N : } +% \end{macrocode} +% \end{variable} +% +% \begin{macro}[TF]{\sys_get_shell:nnN} +% \begin{macro}{\sys_get_shell:nnN,\@@_get:nnN,\@@_get_do:Nw} +% Setting using a shell is at this level just a slightly specialised file +% operation, with an additional check for quotes, as these are not supported. +% \begin{macrocode} +\cs_new_protected:Npn \sys_get_shell:nnN #1#2#3 + { + \sys_get_shell:nnNF {#1} {#2} #3 + { \tl_set:Nn #3 { \q_no_value } } + } +\prg_new_protected_conditional:Npnn \sys_get_shell:nnN #1#2#3 { T , F , TF } + { + \sys_if_shell:TF + { \exp_args:No \@@_get:nnN { \tl_to_str:n {#1} } {#2} #3 } + { \prg_return_false: } + } +\cs_new_protected:Npn \@@_get:nnN #1#2#3 + { + \tl_if_in:nnTF {#1} { " } + { + \msg_error:nne + { kernel } { quote-in-shell } {#1} + \prg_return_false: + } + { + \group_begin: + \if_false: { \fi: + \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: + \prg_return_true: + } + } +\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{variable}{\c_@@_shell_stream_int} +% This is not needed for \LuaTeX{}: shell escape there isn't done using +% a \TeX{} interface. +% \begin{macrocode} +\sys_if_engine_luatex:F + { \int_const:Nn \c_@@_shell_stream_int { 18 } } +% \end{macrocode} +% \end{variable} +% +% \begin{macro}{\sys_shell_now:n, \sys_shell_now:e, \sys_shell_now:x} +% \begin{macro}{\@@_shell_now:e} +% Execute commands through shell escape immediately. +% +% For \LuaTeX{}, we use a pseudo-primitive to do the actual work. +% \begin{macrocode} +%</tex> +%<*lua> +do + local os_exec = os.execute + + local function shellescape(cmd) + local status,msg = os_exec(cmd) + if status == nil then + write_nl("log","runsystem(" .. cmd .. ")...(" .. msg .. ")\n") + elseif status == 0 then + write_nl("log","runsystem(" .. cmd .. ")...executed\n") + else + write_nl("log","runsystem(" .. cmd .. ")...failed " .. (msg or "") .. "\n") + end + end + luacmd("@@_shell_now:e", function() + shellescape(scan_string()) + end, "global", "protected") +%</lua> +% \end{macrocode} +% +% \begin{macrocode} +%<*tex> +\sys_if_engine_luatex:TF + { + \cs_new_protected:Npn \sys_shell_now:n #1 + { \@@_shell_now:e { \exp_not:n {#1} } } + } + { + \cs_new_protected:Npn \sys_shell_now:n #1 + { \iow_now:Nn \c_@@_shell_stream_int {#1} } + } +\cs_generate_variant:Nn \sys_shell_now:n { e, x } +%</tex> +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\sys_shell_shipout:n, \sys_shell_shipout:e, \sys_shell_shipout:x} +% \begin{macro}{\@@_shell_shipout:e} +% Execute commands through shell escape at shipout. +% +% For \LuaTeX, we use the same helper as above but delayed using a |late_lua| whatsit. +% Creating a |late_lua| whatsit works a bit different if we are running under Con\TeX{}t. +% \begin{macrocode} +%<*lua> + local new_latelua = nodes and nodes.nuts and nodes.nuts.pool and nodes.nuts.pool.latelua or (function() + local whatsit_id = node.id'whatsit' + local latelua_sub = node.subtype'late_lua' + local node_new = node.direct.new + local setfield = node.direct.setwhatsitfield or node.direct.setfield + return function(f) + local n = node_new(whatsit_id, latelua_sub) + setfield(n, 'data', f) + return n + end + end)() + local node_write = node.direct.write + + luacmd("@@_shell_shipout:e", function() + local cmd = scan_string() + node_write(new_latelua(function() shellescape(cmd) end)) + end, "global", "protected") +end +%</lua> +% \end{macrocode} +% +% \begin{macrocode} +%<*tex> +\sys_if_engine_luatex:TF + { + \cs_new_protected:Npn \sys_shell_shipout:n #1 + { \@@_shell_shipout:e { \exp_not:n {#1} } } + } + { + \cs_new_protected:Npn \sys_shell_shipout:n #1 + { \iow_shipout:Nn \c_@@_shell_stream_int {#1} } + } +\cs_generate_variant:Nn \sys_shell_shipout:n { e , x } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Dynamic (every job) code} +% +% \begin{macro}{\__kernel_sys_everyjob:} +% \begin{macro}{\@@_everyjob:n} +% \begin{variable}{\g_@@_everyjob_tl} +% \begin{macrocode} +\cs_new_protected:Npn \__kernel_sys_everyjob: + { + \tl_use:N \g_@@_everyjob_tl + \tl_gclear:N \g_@@_everyjob_tl + } +\cs_new_protected:Npn \@@_everyjob:n #1 + { \tl_gput_right:Nn \g_@@_everyjob_tl {#1} } +\tl_new:N \g_@@_everyjob_tl +% \end{macrocode} +% \end{variable} +% \end{macro} +% \end{macro} +% +% \subsubsection{The name of the job} +% +% \begin{variable}{\c_sys_jobname_str} +% Inherited from the \LaTeX3 name for the primitive. This \emph{has} to be +% the primitive as it's set in \tn{everyjob}. If the user does +% \begin{verbatim} +% pdflatex \input some-file-name +% \end{verbatim} +% then \tn{everyjob} is inserted \emph{before} \tn{jobname} is changed form +% |texput|, and thus we would have the wrong result. +% \begin{macrocode} +\@@_everyjob:n + { \cs_new_eq:NN \c_sys_jobname_str \tex_jobname:D } +% \end{macrocode} +% \end{variable} +% +% \subsubsection{Time and date} +% +% \begin{variable} +% { +% \c_sys_minute_int, +% \c_sys_hour_int, +% \c_sys_day_int, +% \c_sys_month_int, +% \c_sys_year_int, +% } +% Copies of the information provided by \TeX{}. There is a lot of defensive +% code in package mode: someone may have moved the primitives, and they can +% only be recovered if we have \tn{primitive} and it is working correctly. +% For Ini\TeX{} of course that is all redundant but does no harm. +% \begin{macrocode} +\@@_everyjob:n + { + \group_begin: + \cs_set:Npn \@@_tmp:w #1 + { + \str_if_eq:eeTF { \cs_meaning:N #1 } { \token_to_str:N #1 } + { #1 } + { + \cs_if_exist:NTF \tex_primitive:D + { + \bool_lazy_and:nnTF + { \sys_if_engine_xetex_p: } + { + \int_compare_p:nNn + { \exp_after:wN \use_none:n \tex_XeTeXrevision:D } + < { 99999 } + } + { 0 } + { \tex_primitive:D #1 } + } + { 0 } + } + } + \int_const:Nn \c_sys_minute_int + { \int_mod:nn { \@@_tmp:w \time } { 60 } } + \int_const:Nn \c_sys_hour_int + { \int_div_truncate:nn { \@@_tmp:w \time } { 60 } } + \int_const:Nn \c_sys_day_int { \@@_tmp:w \day } + \int_const:Nn \c_sys_month_int { \@@_tmp:w \month } + \int_const:Nn \c_sys_year_int { \@@_tmp:w \year } + \group_end: + } +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\c_sys_timestamp_str} +% A simple expansion: Lua\TeX{} chokes if we use \tn{pdffeedback} here, +% hence the direct use of Lua. Notice that the function there is in the +% \texttt{pdf} library but isn actually tied to PDF. +% \begin{macrocode} +\@@_everyjob:n + { + \str_const:Ne \c_sys_timestamp_str + { + \cs_if_exist:NTF \tex_directlua:D + { \tex_directlua:D { tex.print(pdf.getcreationdate()) } } + { \tex_creationdate:D } + } + } +% \end{macrocode} +% \end{variable} +% +% \subsubsection{Random numbers} +% +% \begin{macro}[EXP]{\sys_rand_seed:} +% Unpack the primitive. +% \begin{macrocode} +\@@_everyjob:n + { + \cs_new:Npn \sys_rand_seed: { \tex_the:D \tex_randomseed:D } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\sys_gset_rand_seed:n} +% The primitive always assigns the seed globally. +% \begin{macrocode} +\@@_everyjob:n + { + \cs_new_protected:Npn \sys_gset_rand_seed:n #1 + { \tex_setrandomseed:D \int_eval:n {#1} \exp_stop_f: } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP]{\sys_timer:, \@@_elapsedtime:} +% \begin{macro}[EXP, pTF]{\sys_if_timer_exist:} +% In \LuaTeX{}, create a pseudo-primitve, otherwise try to +% locate the real primitive. The elapsed time will be +% available if this succeeds. +% \begin{macrocode} +%</tex> +%<*lua> + local gettimeofday = os.gettimeofday + local epoch = gettimeofday() - os.clock() + local write = tex.write + local tointeger = math.tointeger + luacmd('@@_elapsedtime:', function() + write(tointeger((gettimeofday() - epoch)*65536 // 1)) + end, 'global') +%</lua> +%<*tex> +\sys_if_engine_luatex:TF + { + \cs_new:Npn \sys_timer: + { \@@_elapsedtime: } + } + { + \cs_if_exist:NTF \tex_elapsedtime:D + { + \cs_new:Npn \sys_timer: + { \int_value:w \tex_elapsedtime:D } + } + { + \cs_new:Npn \sys_timer: + { + \int_value:w + \msg_expandable_error:nnn { kernel } { no-elapsed-time } + { \sys_timer: } + \c_zero_int + } + } + } +\@@_const:nn { sys_if_timer_exist } + { \cs_if_exist_p:N \tex_elapsedtime:D || \cs_if_exist_p:N \@@_elapsedtime: } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsubsection{Access to the shell} +% +% \begin{variable}{\c_sys_shell_escape_int} +% Expose the engine's shell escape status to the user. +% \begin{macrocode} +\@@_everyjob:n + { + \int_const:Nn \c_sys_shell_escape_int + { + \sys_if_engine_luatex:TF + { + \tex_directlua:D + { tex.sprint(status.shell_escape~or~os.execute()) } + } + { \tex_shellescape:D } + } + } +% \end{macrocode} +% \end{variable} +% +% \begin{macro}[EXP, pTF]{\sys_if_shell:, \sys_if_shell_unrestricted:, \sys_if_shell_restricted:} +% Performs a check for whether shell escape is enabled. The first set +% of functions returns true if either of restricted or unrestricted +% shell escape is enabled, while the other two sets of functions +% return true in only one of these two cases. +% \begin{macrocode} +\@@_everyjob:n + { + \@@_const:nn { sys_if_shell } + { \int_compare_p:nNn \c_sys_shell_escape_int > 0 } + \@@_const:nn { sys_if_shell_unrestricted } + { \int_compare_p:nNn \c_sys_shell_escape_int = 1 } + \@@_const:nn { sys_if_shell_restricted } + { \int_compare_p:nNn \c_sys_shell_escape_int = 2 } + } +% \end{macrocode} +% \end{macro} +% +% \subsection{System queries} +% +% \begin{macro}{\sys_get_query:nN} +% \begin{macro}{\sys_get_query:nnN} +% \begin{macro} +% { +% \sys_get_query:nnnN, +% \@@_get_query_auxi:nnnN, \@@_get_query_auxi:neeN, +% \@@_get_query_auxii:nnnN, \@@_get_query_auxii:neeN +% } +% Calling the query system is quite straight-forward: most of the effort is +% in making the read-back catcode-safe. We also want to trim off the trailing +% |^^M| from the last line. +% \begin{macrocode} +\cs_new_protected:Npn \sys_get_query:nN #1#2 + { \sys_get_query:nnnN {#1} { } { } #2 } +\cs_new_protected:Npn \sys_get_query:nnN #1#2#3 + { \sys_get_query:nnnN {#1} { } {#2} #3 } +\cs_new_protected:Npn \sys_get_query:nnnN #1#2#3#4 + { + \tl_clear:N #4 + \@@_get_query_auxi:neeN {#1} {#2} {#3} #4 + } +\cs_new:Npn \@@_get_query_auxi:nnnN #1#2#3#4 + { + \@@_get_query_auxii:neeN {#1} + { \tl_if_blank:nF {#2} { \tl_to_str:n { ~ #2 } } } + { + \tl_if_blank:nF {#3} + { + \c_space_tl + \sys_if_shell_restricted:F ' + \tl_to_str:n {#3} + \sys_if_shell_restricted:F ' + } + } + #4 + } +\cs_generate_variant:Nn \@@_get_query_auxi:nnnN { nee } +\cs_new_protected:Npn \@@_get_query_auxii:nnnN #1#2#3#4 + { + \sys_if_shell:T + { + \sys_get_shell:nnN + { l3sys-query~#1 #2 #3 } + { + \int_step_inline:nnn { 0 } { `A - 1 } + { \char_set_catcode_other:n {##1} } + \int_step_inline:nnn { `Z + 1 } { `a - 1 } + { \char_set_catcode_other:n {##1} } + \int_step_inline:nnn { `z + 1 } { 127 } + { \char_set_catcode_other:n {##1} } + \char_set_catcode_active:n { `\ } + \tex_endlinechar:D 13 \scan_stop: + } + \l_@@_tmp_tl + \tl_if_empty:NF \l_@@_tmp_tl + { + \exp_after:wN \@@_get_query:Nw \exp_after:wN #4 + \l_@@_tmp_tl \q_stop + } + } + } +\cs_generate_variant:Nn \@@_get_query_auxii:nnnN { nee } +\group_begin: + \tex_lccode:D `\* = 13 \scan_stop: + \tex_lowercase:D + { + \group_end: + \cs_new_protected:Npn \@@_get_query:Nw #1#2 * \q_stop + } + { \tl_set:Nn #1 {#2} } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\sys_split_query:nN} +% \begin{macro}{\sys_split_query:nnN} +% \begin{macro}{\sys_split_query:nnnN} +% A wrapper for convenience. +% \begin{macrocode} +\cs_new_protected:Npn \sys_split_query:nN #1#2 + { \sys_split_query:nnnN {#1} { } { } #2 } +\cs_new_protected:Npn \sys_split_query:nnN #1#2#3 + { \sys_split_query:nnnN {#1} { } {#2} #3 } +\group_begin: + \tex_lccode:D `\* = 13 \scan_stop: + \tex_lowercase:D + { + \group_end: + \cs_new_protected:Npn \sys_split_query:nnnN #1#2#3#4 + { + \seq_clear:N #4 + \sys_get_query:nnnN {#1} {#2} {#3} \l_@@_tmp_tl + \tl_if_empty:NF \l_@@_tmp_tl + { \seq_set_split:NnV #4 * \l_@@_tmp_tl } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \subsubsection{Held over from \pkg{l3file}} +% +% \begin{variable}{\g_file_curr_name_str} +% See comments about \cs{c_sys_jobname_str}: here, as soon as there is +% file input/output, things get \enquote{tided up}. +% \begin{macrocode} +\@@_everyjob:n + { \cs_gset_eq:NN \g_file_curr_name_str \tex_jobname:D } +% \end{macrocode} +% \end{variable} +% +% \subsection{Last-minute code} +% +% \begin{macro}{\sys_finalise:} +% \begin{macro}{\@@_finalise:n} +% \begin{variable}{\g_@@_finalise_tl} +% A simple hook to finalise the system-dependent layer. This is forced by +% the backend loader, which is forced by the main loader, so we do not need +% to include that here. +% \begin{macrocode} +\cs_new_protected:Npn \sys_finalise: + { + \__kernel_sys_everyjob: + \tl_use:N \g_@@_finalise_tl + \tl_gclear:N \g_@@_finalise_tl + } +\cs_new_protected:Npn \@@_finalise:n #1 + { \tl_gput_right:Nn \g_@@_finalise_tl {#1} } +\tl_new:N \g_@@_finalise_tl +% \end{macrocode} +% \end{variable} +% \end{macro} +% \end{macro} +% +% \subsubsection{Detecting the output} +% +% \begin{macro}[pTF, EXP] +% { +% \sys_if_output_dvi:, +% \sys_if_output_pdf: +% } +% \begin{variable}{\c_sys_output_str} +% This is a simple enough concept: the two views here are complementary. +% \begin{macrocode} +\@@_finalise:n + { + \str_const:Ne \c_sys_output_str + { + \int_compare:nNnTF + { \cs_if_exist_use:NF \tex_pdfoutput:D { 0 } } > { 0 } + { pdf } + { dvi } + } + \@@_const:nn { sys_if_output_dvi } + { \str_if_eq_p:Vn \c_sys_output_str { dvi } } + \@@_const:nn { sys_if_output_pdf } + { \str_if_eq_p:Vn \c_sys_output_str { pdf } } + } +% \end{macrocode} +% \end{variable} +% \end{macro} +% +% \subsubsection{Configurations} +% +% \begin{variable}{\g_@@_backend_tl} +% As the backend has to be checked and possibly adjusted, the approach here +% is to create a variable and use that in a one-shot to set a constant. +% \begin{macrocode} +\tl_new:N \g_@@_backend_tl +\@@_finalise:n + { + \__kernel_tl_gset:Nx \g_@@_backend_tl + { + \sys_if_engine_xetex:TF + { xetex } + { + \sys_if_output_pdf:TF + { + \sys_if_engine_pdftex:TF + { pdftex } + { luatex } + } + { dvips } + } + } + } +% \end{macrocode} +% If there is a class option set, and recognised, we pick it up: these +% will over-ride anything set automatically but will themselves be +% over-written if there is a package option. +% \begin{macrocode} +\@@_finalise:n + { + \cs_if_exist:NT \@classoptionslist + { + \cs_if_eq:NNF \@classoptionslist \scan_stop: + { + \clist_map_inline:Nn \@classoptionslist + { + \str_case:nnT {#1} + { + { dvipdfmx } + { \tl_gset:Nn \g_@@_backend_tl { dvipdfmx } } + { dvips } + { \tl_gset:Nn \g_@@_backend_tl { dvips } } + { dvisvgm } + { \tl_gset:Nn \g_@@_backend_tl { dvisvgm } } + { pdftex } + { \tl_gset:Nn \g_@@_backend_tl { pdfmode } } + { xetex } + { \tl_gset:Nn \g_@@_backend_tl { xdvipdfmx } } + } + { \clist_remove_all:Nn \@unusedoptionlist {#1} } + } + } + } + } +% \end{macrocode} +% \end{variable} +% +% \begin{macrocode} +%</tex> +%</package> +% \end{macrocode} +% +%\end{implementation} +% +%\PrintIndex |