\ProvidesPackage{robust-externalize}[2025/04/08 v3.0 Cache anything (tikz, latex, python) in a robust, efficient and pure way.]
% todo:
% change order argument replace from list, it is hard to read this way
\RequirePackage{pgfkeys} % We use the /robExt/... path to store our keys.
\RequirePackage{pgffor} % For the .list keys
\RequirePackage{graphicx} % For the includegraphics command
\RequirePackage{verbatim} % For the \verbatim command, useful for debugging purpose for instance
\RequirePackage{xsimverb} % To easily write verbatim code to files
\RequirePackage{etoolbox} % To easily write verbatim code to files
\RequirePackage{iftex} % computing md5 sum differs on lualatex/pdflatex/xelatex
\RequirePackage{xparse} % solve https://github.com/leo-colisson/robust-externalize/issues/6
\RequirePackage{atveryend} % to compile in parallel all images
%\usepackage{etl} % For auto forward https://tex.stackexchange.com/a/700844/116348
%% TODO list:
% - provide an easy way to use cross-ref, bibtex etc (we just need to add them when writing the file) without recompiling the whole document (we don't want to lose the cache everytime a new bib entry is added) but while preserving.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Utils %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% If the file contains a space, the jobname gets quotes around...
% https://tex.stackexchange.com/questions/418670/avoid-quotation-marks-when-using-jobname-or-currfilename
\newcommand*{\jobnameNoQuotes}{}
\newcommand*{\setjobnameNoQuotes}{%
\edef\jobnameNoQuotes{\jobname}%
\expandafter\expandafter\expandafter\setjobnameNoQuotesaux
\expandafter\jobnameNoQuotes\expandafter"\jobnameNoQuotes"\relax
}
\newcommand*{\setjobnameNoQuotesaux}{}
\def\setjobnameNoQuotesaux#1"#2"#3\relax{\def\jobnameNoQuotes{#2}}
\setjobnameNoQuotes
\ExplSyntaxOn
% in pgf it is not possible to use explsyntax
\NewDocumentCommand{\robExtMsgError}{m}{%
\msg_error:nn{robExt}{#1}
}
\NewDocumentCommand{\robExtIfWindowsTF}{mm}{%
\sys_if_platform_windows:TF {#1}{#2}%
}
% This will be printed in from of the error message, mostly useful in texstudio that only print lines
% containing errors
\def\robExtPrefixLogMessage{}
% Prints the next 4 lines after an error:
\def\robExtLinesAfterError{3}
\def\robExtRemoveLineNumber{}
%% Needed for studio to prepend ! in front of all lines, including big ones.
%% https://tex.stackexchange.com/questions/705502/message-add-symbol-in-front-of-new-lines-even-if-there-is-a-line-break
\def\robExtMessageWithPrefixNumberLines{^^J}%
\cs_new:Nn \robExt__message_with_prefix:n {
\str_set:Nn \l_tmpa_str {#1}
\int_compare:nNnTF {\str_count:N \l_tmpa_str + \str_count:N \robExtPrefixLogMessage} > {78} {
\typeout{\robExtPrefixLogMessage \str_range:Nnn \l_tmpa_str {1} {78 - \str_count:N \robExtPrefixLogMessage}\robExtMessageWithPrefixNumberLines}
\robExt__message_with_prefix:x {\str_range:Nnn \l_tmpa_str {78 - \str_count:N \robExtPrefixLogMessage + 1} {-1}}
}{
\typeout{\robExtPrefixLogMessage \l_tmpa_str \robExtMessageWithPrefixNumberLines}
}
}
\cs_generate_variant:Nn \robExt__message_with_prefix:n { x }
% We might want to remove l.XX in front of lines as emacs interpret this as a line of error and goes to a wrong place.
\cs_new:Nn \robExt__clean_line_if_needed:n {
\ifdefined\robExtRemoveLineNumber%
\str_compare:eNeTF {\str_range:nnn {#1} {1} {2}} = {l.} {
\str_range:nnn {#1} {3} {-1}%
} {
#1
}
\else
#1
\fi
}
\cs_generate_variant:Nn \robExt__clean_line_if_needed:n { V }
\cs_new:Nn \robExt_get_errors_from_file:n {
\ior_open:Nn \g__robExt_read_ior {#1}%
\cs_undefine:N \l__robExt_I_saw_an_error:
\str_clear_new:N \l__robExt_tmp_str%
\int_set:Nn \l_tmpa_int {0}
\ior_str_map_inline:Nn \g__robExt_read_ior {%
\ifdefined\robExtPrintWholeFile
\str_gput_right:Nx \l__robExt_tmp_str {\tl_to_str:n{##1}^^J}%
\cs_set:Nn \l__robExt_I_saw_an_error: {}
\int_set:Nn \l_tmpa_int {\robExtLinesAfterError}
\else
\str_if_in:xnTF {\str_lowercase:n{##1}} {error} {
\str_gput_right:Nx \l__robExt_tmp_str {\robExt__clean_line_if_needed:V{\tl_to_str:n{##1}^^J}}%
\cs_set:Nn \l__robExt_I_saw_an_error: {}
\int_set:Nn \l_tmpa_int {\robExtLinesAfterError}
} {
\str_if_in:xnTF {\str_lowercase:n{##1}} {invalid} {
\str_gput_right:Nx \l__robExt_tmp_str {\robExt__clean_line_if_needed:V{\tl_to_str:n{##1}^^J}}%
\cs_set:Nn \l__robExt_I_saw_an_error: {}
\int_set:Nn \l_tmpa_int {\robExtLinesAfterError}
} {
% Check if it starts with a ! (usually latex errors start with this symbol)
\str_compare:eNeTF {\str_range:nnn {##1} {1} {1}} = {!} {
\str_gput_right:Nx \l__robExt_tmp_str {\robExt__clean_line_if_needed:V{\tl_to_str:n{##1}^^J}}%
\cs_set:Nn \l__robExt_I_saw_an_error: {}
\int_set:Nn \l_tmpa_int {\robExtLinesAfterError}
}{
% Check if we saw an error not too long ago:
\int_compare:nNnTF {\l_tmpa_int} > {0} {
\str_gput_right:Nx \l__robExt_tmp_str {\robExt__clean_line_if_needed:V{\tl_to_str:n{##1}^^J}}%
} {
\str_set:Nn \l_tmp_str {##1}
}
\int_set:Nn \l_tmpa_int {\l_tmpa_int - 1}
}
}
}
\fi
}%
\cs_if_exist:NTF \l__robExt_I_saw_an_error: {} {
% We print the last line if we found no error. Might be useful for errors like:
% sh: ligne 1: gnuplot : commande introuvable.
\str_set_eq:NN \l__robExt_tmp_str \l_tmp_str
}
}
\cs_new:Nn \robExt_write_file_in_log:n {
\ior_open:Nn \g__robExt_read_ior {#1}%
\ior_str_map_inline:Nn \g__robExt_read_ior {%
\ifdefined\robExtPrintWholeFile
\ifdefempty{\robExtPrefixLogMessage}{
\typeout{\tl_to_str:n{##1}}%
}{
\robExt__message_with_prefix:x {\tl_to_str:n{##1}}
}
\else
\typeout{\tl_to_str:n{##1}}%
\fi
}%
}
\cs_generate_variant:Nn \robExt_write_file_in_log:n { x }
\ExplSyntaxOff
% Debug function: by default, do nothing
\def\robExtDebugMessage#1{}
\def\robExtDebugInfo#1{\message{#1}}
\def\robExtDebugWarning#1{\message{^^J[robExt warning] #1}}
\NewDocumentCommand{\robExtConfigure}{m}{%
\pgfqkeys{/robExt}{#1}% faster than \pgfkeys{/robExt/.cd,#1}
}
% https://tex.stackexchange.com/questions/690700/latex3-elegant-way-to-forward-a-variable-outside-of-the-group
% modified to deal with csname instead
\def\robExtKeepaftergroup#1{%
%\global \expandafter \expandafter \let \csname x:#1\endcsname =\csname #1\endcsname
\global \expanded{\noexpand \let \expandafter\noexpand\csname x:#1\endcsname =\expandafter\noexpand\csname #1\endcsname}%
\aftergroup\let%
\expandafter\aftergroup\csname #1\endcsname%
\expandafter\aftergroup \csname x:\string#1\endcsname%
}
\ExplSyntaxOn
%%%
%%% Expl3 variants
%%%
\cs_generate_variant:Nn \str_replace_all:Nnn { Nnx, Nnv, cnx, cxx, NnV, Nnx, Nnv }
\cs_generate_variant:Nn \str_set:Nn { NV }
\cs_generate_variant:Nn \str_if_in:nnTF { xnTF }
\cs_generate_variant:Nn \tl_rescan:nn { nv }
\cs_generate_variant:Nn \tl_set_rescan:Nnn { Nnv, NnV }
\cs_generate_variant:Nn \tl_gset_rescan:Nnn { cnv }
\cs_generate_variant:Nn \seq_remove_all:Nn { NV }
\cs_generate_variant:Nn \iow_now:Nn { NV,Nx }
\cs_generate_variant:Nn \iow_open:Nn { Nx }
\cs_generate_variant:Nn \ior_str_get:NN { Nc }
\cs_generate_variant:Nn \file_if_exist:nTF { xTF }
\cs_generate_variant:Nn \file_mdfive_hash:n { x }
\cs_generate_variant:Nn \cs_replacement_spec:N { c }
\cs_generate_variant:Nn \cs_argument_spec:N { c }
\cs_generate_variant:Nn \str_count_ignore_spaces:n { V }
\cs_generate_variant:Nn \str_count_spaces:n { e }
\cs_generate_variant:Nn \regex_extract_all:nnN { VVN, nVN, VVN }
\cs_generate_variant:Nn \regex_extract_all:NnN { NVN }
\cs_generate_variant:Nn \regex_match:nnTF { nVTF }
\cs_generate_variant:Nn \seq_set_split_keep_spaces:Nnn {Nnv}
%%%
%%% String manipulation
%%%
\cs_set:Nn \robExt_set_hash_robust:Nn {
\str_set:Nn {#1} {#2}
\str_replace_all:Nnx {#1} { ## } { \c_hash_str }
}
\cs_generate_variant:Nn \robExt_set_hash_robust:Nn { cn }
\cs_generate_variant:Nn \robExt_set_hash_robust:Nn { cx }
\NewDocumentCommand{\robExt@set@hash@robust}{mm}{\robExt_set_hash_robust:Nn #1 {#2}}
% Double the number of hashes... quite dirty but cannot find any solution or the user need to double it itself
%% See also https://tex.stackexchange.com/questions/695432/latex3-latex-doubles-the-number-of-hashes-when-storing-them-in-string/695460#695460
\NewDocumentCommand{\robExtStrSetDoubleHash}{mm}{
\str_set:Nn {#1} {#2}
\str_replace_all:Nnx {#1} { ## } { \c_hash_str \c_hash_str }
}
\NewDocumentCommand{\robExtStrSetHashRobust}{mm}{
\robExt_set_hash_robust:Nn {#1} {#2}
}
\NewDocumentCommand{\robExtRescanHashRobust}{m}{
\robExt_set_hash_robust:Nn \l__robExt_tmp_str {#1}
\tl_rescan:nv {}{ l__robExt_tmp_str }
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Error messages %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% In LaTeX3 error messages are defined here, this allows people to translate them, filter them, change them,
%% make them more verbose etc.
\msg_new:nnn {robExt}{underscore in name}{Error:~all~the~placeholders~should~contain~two~consecutive~underscores~in~their~name.}
\msg_new:nnn {robExt}{placeholder not existing}{[robExtGetPlaceholderInResultFromSinglePlaceholder] ~ The ~ placeholder ~ #1 ~ does ~ not ~ exists.}
\msg_new:nnn {robExt}{forgot template}{You ~ are ~ writing ~ __ROBEXT_TEMPLATE__ ~ to ~ your ~ file: ~ seems ~ like ~ you ~ forgot ~ to ~ define ~ your ~ template ~ or ~ set ~ a ~ preset}
\msg_new:nnn {robExt}{need shell escape}{You ~ need ~ to ~ compile ~ with ~ shell-escape ~ as ~ in: ~ "pdflatex ~ -shell-escape ~ yourfile.tex" ~ to ~ be ~ able ~ to ~ compile ~ automatically ~ the ~ figures}
\msg_new:nnn {robExt}{need shell escape with}{You ~ need ~ to ~ compile ~ with ~ shell-escape ~ as ~ in: ~ "pdflatex ~ -shell-escape ~ yourfile.tex" ~ to ~ be ~ able ~ to ~ #1}
\msg_new:nnn {robExt}{need shell escape parallel}{You ~ need ~ to ~ compile ~ with ~ shell-escape ~ as ~ in: ~ "pdflatex ~ -shell-escape ~ yourfile.tex" ~ to ~ be ~ able ~ to ~ compile ~ automatically ~ the ~ figures ~ in ~ parallel}
\msg_new:nnn {robExt}{missing compiled pdf parallel}{The~compilation~of~block~at~line~#2~failed~as~the~following ~ file~is~missing: ~ #1.pdf. ~ The ~ compilation ~ command ~ "#3"~used~to~compile~the~environment~on~line~#2~ certainly ~ failed, ~ see ~ logs ~ above ~ or ~ in ~ #1.log.}
\msg_new:nnn {robExt}{dependency does not exist}{The~dependency~#1~does~not~exist.}
\msg_new:nnn {robExt}{missing compiled pdf parallel with log}{The~compilation~of~the~code~block~at~line~#2~failed:~the~following ~ file~is~indeed~missing: ~ #1.pdf. ~ The ~ compilation ~ command ~ "#3"~used~to~compile~the~environment~on~line~#2~ certainly ~ failed~with~errors:^^Jvvvvvv^^J\l__robExt_tmp_str^^J\string^\string^\string^\string^\string^\string^ ^^JSee~full~logs~#4~ or ~ in ~ #1-compilation.log.}
\msg_new:nnn {robExt}{remove spaces until non spaces characters}{The~placeholder~#1~contains~characters~other~than~spaces~(#2)~before~the~separator~#3.}
\msg_new:nnn {robExt}{need to load pythonhighlight}{You~need~to~load~the~package~pythonhighlight~to~use~the~lstlisting~style~pythonhighlight-style.}
\msg_new:nnn {robExt}{auto forward not in cachemecode}{Auto~forward~is~less~efficient~in~cacheMeCode.}
\msg_new:nnn {robExt}{file does not exist}{The~file~#1~does~not~exist.}
% warnings
\msg_new:nnn {robExt}{recommend two underscores in placeholders}{We~recommend~to~use~only~placeholders~containing~two~consecutive~underscores~in~their~name}
\msg_new:nnn {robExt}{enabled parallel no shell escape}{Warning:~you~enabled~parallel~compilation~but~shell-escape~is~disabled.}
\msg_new:nnn {robExt}{rerun because parallel}{Warning:~Compiling~all~missing~figures~in~parallel~with~"#1".~You~need~to~rerun~LaTeX~to~include~them.}
\msg_new:nnn {robExt}{gpgetvar recompilation needed}{Warning:~you~need~to~recompile~as~the~gpgetvar~variable~"#1"~does~not~exist~yet.}
% dummy placeholders added if image is not present
\def\robExtImagePlaceholderIfManualMode{
\framebox[\linewidth]
{
\begin{minipage}{\linewidth}
\textbf{Manual ~ mode: ~ either ~ compile ~ with ~ \texttt{-shell-escape} ~ or ~ compile:\newline \texttt{\robExtAddCachePathAndName{\robExtFinalHash.tex}}\newline via ~ \newline \texttt{\l__robExt_current_compilation_command_str}\newline or ~ call ~ \newline\texttt{bash ~ \jobnameNoQuotes-\robExtAddPrefixName{compile-missing-figures.sh}}\newline to ~ compile ~ all ~ missing ~ figures.}
\end{minipage}
}
}
\def\robExtImagePlaceholderIfFallbackMode{
\framebox[\linewidth]
{
\begin{minipage}{\linewidth}
\textbf{Falling ~ back ~ to ~ draft ~ mode: ~ either ~ compile ~ with ~ \texttt{-shell-escape} ~ or ~ compile:\newline \texttt{\robExtAddCachePathAndName{\robExtFinalHash.tex}}\newline via ~ \newline \texttt{\l__robExt_current_compilation_command_str}\newline or ~ call ~ \newline\texttt{bash ~ \jobnameNoQuotes-\robExtAddPrefixName{compile-missing-figures.sh}}\newline to ~ compile ~ all ~ missing ~ figures.}
\end{minipage}
}
}
\def\robExtImagePlaceholderIfParallelCompilation{
\framebox[\linewidth]
{
\begin{minipage}{\linewidth}
\textbf{The~file:\\
\texttt{\robExtAddCachePathAndName{\robExtFinalHash.tex}}\\
is~compiled~in~parallel~mode.~You~need~to~recompile~your~document~to~include~it.}
\end{minipage}
}
}
\ExplSyntaxOff
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%% SCRIPTS %%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Important: this script is NOT executed by this file, it is only created so that users can remove unused previously cached files by running this script
%%% outside of LaTeX.
%%% Create scripts to remove useless files:
\begin{filecontents}[noheader,overwrite]{robExt-remove-old-figures.py}
#!/usr/bin/env python3
## This file is automatically generated, so do not change that file directly
## Instead, if you need to change it, copy it with a different name and edit the copy
import os
import re
import glob
# Just run this script in order to remove all old figures not listed in robExt-all-figures.txt.
# Note that this part is not extracted from the pdf file since it might be different on a previous run. You can however hardcode
# it here, your updated script will not be overriden unless you remove it yourself.
prefixes = [ "robExt-" ]
folders = [ "robustExternalize" ]
def main():
imagesToKeep = dict()
list_all_figures_file = glob.glob('*robExt-all-figures.txt')
for filename in list_all_figures_file:
with open(filename) as f:
for line in f:
line = line.strip()
if line.endswith('.tex'):
imagesToKeep[line[:-4]] = True # The exact value is not important, we mostly use dict to get ~O(1) access
listOfFilesToRemove = []
# We are looking for images in the folders
for folder in folders:
for root, dirs, files in os.walk(folder):
for f in files:
for prefix in prefixes: # Not the most efficient, but anyway we typically have a single prefix
# In case prefix contains weird caracters that collide with regexps:
prefixEsc = re.escape(prefix)
# result_search = re.search(rf"^({prefixEsc}[A-F0-1]{32}).*", f)
result_search = re.search(rf"^(.*[A-F0-9]{{32}}).*", f)
if result_search:
if result_search.group(1) not in imagesToKeep:
listOfFilesToRemove.append(os.path.join(root,f))
for f in listOfFilesToRemove:
print(f"-- {f}")
print(f"Above are the files to remove, are you sure you want to proceed? [y/N] (based on prefixes {prefixes})")
x = input().strip()
if x not in ["y", "Y"]:
print("All right, we abort.")
exit(1)
for f in listOfFilesToRemove:
os.remove(f)
print(f"Removed {f}")
if __name__ == '__main__':
main()
\end{filecontents}
\begin{filecontents}[noheader,overwrite]{robExt-prepare-for-arxiv.py}
#!/usr/bin/env python3
## This file is automatically generated, so do not change that file directly
## Instead, if you need to change it, copy it with a different name and edit the copy
## This file must be called when sending stuff to the arxiv website, that would otherwise remove the .pdf
## in presence of a .tex.
## This script will simply rename the .tex files in robustExternalize into .tex-backup.
## Then, you should call "rename backup files for arxiv" to rename back the files.
import os
import re
import glob
# Just run this script in order to remove all old figures not listed in robExt-all-figures.txt.
# Note that this part is not extracted from the pdf file since it might be different on a previous run. You can however hardcode
# it here, your updated script will not be overriden unless you remove it yourself.
prefixes = [ "robExt-" ]
folders = [ "robustExternalize" ]
def main():
listOfFilesToMove = []
listOfFilesAlreadyAdded = set()
for folder in folders:
for root, dirs, files in os.walk(folder):
for f in files:
if f.endswith(".tex"):
pdf = os.path.splitext(f)[0] + ".pdf"
if os.path.exists(os.path.join(root,pdf)):
listOfFilesToMove.append((os.path.join(root,f), os.path.join(root,f + "-backup")))
if f.endswith(".tex-backup"):
tex = os.path.splitext(f)[0] + ".tex"
listOfFilesAlreadyAdded.add(os.path.join(root,tex))
for (f,f2) in listOfFilesToMove:
print(f"-- {f} moved to {f2}")
listOfFilesAlreadyAdded.discard(f)
for f in listOfFilesAlreadyAdded:
print(f"-- {f} already moved")
print(f"Above are the files to move or already moved, are you sure you want to proceed? [y/N] (based on prefixes {prefixes})")
x = input().strip()
if x not in ["y", "Y"]:
print("All right, we abort.")
exit(1)
for (f,f2) in listOfFilesToMove:
os.replace(f, f2)
print(f"Moved {f} to {f2}")
with open('robExt-arxiv-files-to-rename.txt', 'w') as fd:
for (f,f2) in listOfFilesToMove:
fd.write(f'{f}\n')
for f in listOfFilesAlreadyAdded:
fd.write(f'{f}\n')
print('The files have been written to robExt-arxiv-files-to-rename.txt.')
print('Add \\robExtConfigure{rename backup files for arxiv} in your tex file.')
if __name__ == '__main__':
main()
\end{filecontents}
\ExplSyntaxOn
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Compatibility %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% These commands exist on really recent kernels (~june 2023), so we provide them in case they do not already exist
\ProvideDocumentCommand \NewEnvironmentCopy {mm} {
\expandafter \NewCommandCopy \csname#1\expandafter\endcsname \csname#2\endcsname
\expandafter \NewCommandCopy \csname end#1\expandafter\endcsname \csname end#2\endcsname
}
\ProvideDocumentCommand \RenewEnvironmentCopy {mm} {
\expandafter \RenewCommandCopy \csname#1\expandafter\endcsname \csname#2\endcsname
\expandafter \RenewCommandCopy \csname end#1\expandafter\endcsname \csname end#2\endcsname
}
\ProvideDocumentCommand \DeclareEnvironmentCopy {mm} {
\expandafter \DeclareCommandCopy \csname#1\expandafter\endcsname \csname#2\endcsname
\expandafter \DeclareCommandCopy \csname end#1\expandafter\endcsname \csname end#2\endcsname
}
\ProvideDocumentCommand \RenewEnvironmentCopy {mm} {
\expandafter \RenewCommandCopy \csname#1\expandafter\endcsname \csname#2\endcsname
\expandafter \RenewCommandCopy \csname end#1\expandafter\endcsname \csname end#2\endcsname
}
%% Depending on the engine, computing md5 is done in different ways
\sys_if_engine_luatex:TF{
\def\robExt@pdfmdfivesum#1{%
\directlua{tex.print(string.upper(md5.sumhexa("\luaescapestring{#1}")))}%
}%
}{
\sys_if_engine_xetex:TF {%
\def\robExt@pdfmdfivesum{\mdfivesum}%
}{%
\def\robExt@pdfmdfivesum{\pdfmdfivesum}%
}%
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Paths %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% For support of --output-directory
\def\robExtPrefixAllCompilationCommands{\ifdefined\robExtOutputDirectory cd~"\robExtOutputDirectory"~&&~\fi}
\def\robExtPrefixFilename{robExt-}
\NewExpandableDocumentCommand{\robExtAddCachePathAndName}{m}{%
\ifdefined\robExtCacheFolder%
\robExtCacheFolder%
\fi\robExtPrefixFilename#1%
}
\NewExpandableDocumentCommand{\robExtAddCachePath}{m}{%
\ifdefined\robExtCacheFolder%
\robExtCacheFolder%
\fi#1%
}
% mv in unix and move in windows:
\robExtIfWindowsTF{
\def\robExtMv{move}
\def\robExtCp{copy}
}{
\def\robExtMv{mv}
\def\robExtCp{cp}
}
\NewDocumentCommand{\robExtBackupSource}{m}{%
% arXiv will remove the .pdf file if the .tex is present. Solution: we move the .tex into .tex-backup
% and restore it during compilation. We could also backup the pdf but usually this file is bigger and will contain
% binary data that might not be so easy to copy in pure tex (in arxiv shell escape is disabled so we cannot
% use cp).
% \show\inBackupSource
\robExtRunCmdIfPossible{%
\robExtMv\space"\robExtAddCachePath{#1}"~%
"\robExtAddCachePath{#1-backup}"%
}%
}
\NewDocumentCommand{\robExtRunCmdIfPossible}{m}{%
% Check if the output directory exists
\bool_if:nTF { \sys_if_shell_unrestricted_p: || \cs_if_exist_p:N \robExtForceCompilation}
{
\ifdefined\robExtManualMode
\message{Warning:~running~in~manual~mode~so~we~cannot~run~the~command~``#1''.}
\iow_now:Nx \g__robExt_write_manually_compile_all_missing_figures_iow {#1}
\else
\message{Running:~\robExtPrefixAllCompilationCommands #1}%
\sys_shell_now:x {\robExtPrefixAllCompilationCommands #1}
\fi
}{
\ifdefined\robExtFallbackManualMode
\message{Warning:~shell~escape~fallback~to~manual~mode~so~cannot~run~#1.}
\iow_now:Nx \g__robExt_write_manually_compile_all_missing_figures_iow {#1}
\else
\msg_error:nnx{robExt}{need shell escape with}{run~#1}
\fi
}
}
\NewDocumentCommand{\robExtCheckIfPrefixFolderExists}{}{
% Check if the output directory exists and is not empty
\ifdefined\robExtCacheFolder
\ifx\robExtCacheFolder\empty\else
\bool_if:nTF { \sys_if_shell_unrestricted_p: || \cs_if_exist_p:N \robExtForceCompilation}
{
\ifdefined\robExtDoNotMkdirFolder\else
\ifdefined\robExtManualMode
\message{If ~ you ~ get~ an~ error,~ make ~ sure ~ to ~ enable ~ pdflatex ~ -shell-escape ~ or ~ to ~ MANUALLY ~ CREATE ~ THE ~ FOLDER ~ \robExtCacheFolder.}
\else
\sys_shell_now:x {\robExtPrefixAllCompilationCommands mkdir ~ -p ~ \robExtCacheFolder}
\fi
\fi
}{
\message{Warning: If ~ you ~ get~ an~ error,~ make ~ sure ~ to ~ enable ~ pdflatex ~ -shell-escape ~ or ~ to ~ manually ~ CREATE ~ THE ~ FOLDER ~ \robExtCacheFolder.}
}
\fi
\fi
}
\NewDocumentCommand{\robExtCopyFileToCache}{m}{
\robExtCheckIfPrefixFolderExists%
\file_if_exist:xTF {#1}{}{
\msg_error:nnx{robExt}{file does not exist}{#1~(to~copy~to~cache)}
}
\file_if_exist:xTF {\robExtAddCachePath{#1}}{
\robExtDebugInfo{The~file~\robExtAddCachePath{#1}~already~exists,~let~us~compare~md5~sum.}
\str_set:Nx \l_tmpa_str {\file_mdfive_hash:n{#1}}
\str_set:Nx \l_tmpb_str {\file_mdfive_hash:n{\robExtAddCachePath{#1}}}
\str_compare:eNeTF {\l_tmpa_str} = {\l_tmpb_str} {
\robExtDebugInfo{The~file~\robExtAddCachePath{#1}~has~the~good~md5~hash.}
}{
\robExtDebugInfo{The~md5~hashes~of~#1~and~\robExtAddCachePath{#1}~are~different(\l_tmpa_str \space vs \space \l_tmpb_str),~let~us~copy~the~file.}%
\robExtRunCmdIfPossible{\robExtCp \space "#1"~"\robExtAddCachePath{#1}"}%
}
}{
\robExtDebugInfo{The~file~\robExtAddCachePath{#1}~does~not~exist,~let~us~copy~it:}
\robExtRunCmdIfPossible{\robExtCp \space "#1"~"\robExtAddCachePath{#1}"}%
}
}
\let\copyFileToCache\robExtCopyFileToCache
% \robExtRenameBackupFilesForArxiv{robExt-arxiv-files-to-rename.txt} takes all elements listed in the .tex file
% (like robustExternalize/robExt-F83AB245D8043E653A89489C2E25AA6A.tex) and rename the
% robustExternalize/robExt-F83AB245D8043E653A89489C2E25AA6A.tex-backup into
% robustExternalize/robExt-F83AB245D8043E653A89489C2E25AA6A.tex
% (needed for arxiv)
% This is done, importantly, without shell escape as arxiv does not have shell escape.
\NewDocumentCommand{\robExtRenameBackupFilesForArxiv}{O{-backup}m}{
\file_if_exist:xTF{#2} {
\robExtDebugInfo{We~will~read~the~file~#2.}
\ior_open:Nn \g__robExt_read_ior {#2}%
%% We avoid creating a new ior since the number of available files is limited in tex
\seq_clear:N \l_tmpa_seq
\ior_str_map_inline:Nn \g__robExt_read_ior {%
\file_if_exist:xTF{##1#1} {
\seq_put_right:Nn \l_tmpa_seq {##1}
}{
\robExtDebugInfo{Warning:~##1#1~does~not~exist,~so~we~will~not~rename~back~this~file~for~arXiv.}
}
}%
\ior_close:N \g__robExt_read_ior
\seq_map_inline:Nn \l_tmpa_seq {
\robExtDebugInfo{We~copy~the~source~##1#1~to~##1^^J}%
% First we read the file:
\ior_open:Nn \g__robExt_read_ior {##1#1}%
\iow_open:Nx \g__robExt_write_iow {##1}
\ior_str_map_inline:Nn \g__robExt_read_ior {%
\iow_now:Nx \g__robExt_write_iow {\tl_to_str:n{####1}}%
}%
\iow_close:N \g__robExt_write_iow
\ior_close:N \g__robExt_read_ior
}
}{
\robExtDebugInfo{Warning:~#2~does~not~exist,~so~we~will~not~rename~back~elements~for~arXiv.}
}
}
\NewExpandableDocumentCommand{\robExtGetPrefixPath}{}{%
\ifdefined\robExtCacheFolder%
\robExtCacheFolder%
\fi%
}
\NewExpandableDocumentCommand{\robExtAddPrefixName}{m}{%
\robExtPrefixFilename#1%
}
%% Todo: not sure if I should use \seq_push:Nx \l_file_search_path_seq {subfolder}
%% to find the subfolder (seems to work for input/includegraphics/...), or if it's
%% better to hardcode the subfolder.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Setup new commands and variables %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Temporary: used when quickly writing to a file
\iow_new:N \g__robExt_write_iow
\ior_new:N \g__robExt_read_ior
%% This is used to write the full list of figures in a single file (used for instance by Makefile etc...)
%% TODO: create a makefile.
\iow_new:N \g__robExt_write_list_all_figures_iow
%% Create a file robExt-all-figures.txt with the list of .tex files
\iow_open:Nx \g__robExt_write_list_all_figures_iow {\jobnameNoQuotes-\robExtAddPrefixName{all-figures.txt}}
\iow_new:N \g__robExt_write_manually_compile_all_missing_figures_iow
\iow_open:Nx \g__robExt_write_manually_compile_all_missing_figures_iow {\jobnameNoQuotes-\robExtAddPrefixName{compile-missing-figures.sh}}
% Contains the list of dependency files (useful to compute the final md5sum)
\seq_new:N \l__robExt_dependencies_str
% Contains a string where on each line we have: "md5sum, dependency". The first line has nothing as "dependency" as it is the main fine whose final name is the md5sum of the dependencies.
\str_new:N \l__robExt_dependencies_mdfive_str
% Contains the current compilation command (including the name of the file to compile).
\str_new:N \l__robExt_current_compilation_command_str
\str_new:N \l__robExt_tmp_str
% To have a code compatible even on older versions
% https://tex.stackexchange.com/questions/615010/how-can-i-use-latex-hooks-in-historical-versions-of-tex-live
\providecommand\RobExtIfFormatAtLeastTF{\@ifl@t@r\fmtversion}
\RobExtIfFormatAtLeastTF{2020-10-01}%
{
% https://ctan.math.illinois.edu/macros/latex/base/lthooks-doc.pdf
\NewHook{robust-externalize/just-before-writing-files}
\def\robExtUseHookJustBeforeWritingFiles{\UseHook{robust-externalize/just-before-writing-files}}%
}%
{
\def\robExtUseHookJustBeforeWritingFiles{}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Placeholders %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Placeholders are strings like "__MYLIBRARY__" or "__MAINCONTENT__" that will be replaced by a given content.
%% In practice:
%% - for every placeholder MYPLACEHOLDER, a macro \l__robExt_placeholder_MYPLACEHOLDER_str is created, containing
%% the string to use to replace it
%% - a sequence \l__robExt_placeholder_group_main_seq that is a list of string, contains the list of all placeholders,
%% in a string, like [MYLIBRARY, MAINCONTENT] etc...
%% One issue is that LaTeX does not keep some symbols (e.g. % etc...) when used inside a macro, so we define
%% different commands depending on whether they can be used in a macro or not, whether they should be taken
%% from an external file etc...
\seq_clear_new:N \l__robExt_placeholder_group_main_seq
%%%
%%% Debug in terminal
%%%
\NewDocumentCommand{\robExtShowPlaceholder}{sm}{
\cs_if_exist:cTF {l__robExt_placeholder_#2_str} {
\message{Placeholder ~ #2 ~ contains:^^J \use:c{l__robExt_placeholder_#2_str}}
}{
\message{Placeholder ~ #2 ~ does ~ not ~ exist.}
}
\IfBooleanTF{#1}{}{\show\def}
}
\NewDocumentCommand{\robExtShowPlaceholders}{s}{
\message{List ~ of ~ placeholders:}
\seq_map_inline:Nn \l__robExt_placeholder_group_main_seq {\message{##1,}}
\IfBooleanTF{#1}{}{\show\def}
}
\NewDocumentCommand{\robExtShowPlaceholdersContents}{s}{
\message{List ~ of ~ placeholders:}
\seq_map_inline:Nn \l__robExt_placeholder_group_main_seq {\robExtShowPlaceholder*{##1}}
\IfBooleanTF{#1}{}{\show\def}
}
%%%
%%% Debug in document
%%%
\NewDocumentCommand{\robExtPrintPlaceholderNoReplacement}{sm}{%
% For some reasons, newlines are displayed as \Omega. We need to replace them with \\
% https://tex.stackexchange.com/questions/694716/print-latex3-string-verbatim/694717
\tl_set_eq:Nc \l__robExt_tmp_str { l__robExt_placeholder_#2_str }
\tl_replace_all:Nnn \l__robExt_tmp_str {^^J} { \mbox{}\par } % mbox is helpful to print empty lines
\tl_replace_all:Nnn \l__robExt_tmp_str { ~ } { \ }
\IfBooleanTF{#1}{\texttt{\use:c{l__robExt_placeholder_#2_str}}}{\begin{flushleft}\ttfamily%
\l__robExt_tmp_str
\end{flushleft}%
}
}
\let\printPlaceholderNoReplacement\robExtPrintPlaceholderNoReplacement
\NewDocumentCommand{\robExtPrintPlaceholder}{sm}{%
\robExtGetPlaceholderInResult{#2}
% For some reasons, newlines are displayed as \Omega. We need to replace them with \\
% https://tex.stackexchange.com/questions/694716/print-latex3-string-verbatim/694717
\tl_set_eq:NN \l__robExt_tmp_str \l_robExt_result_str
\tl_replace_all:Nnn \l__robExt_tmp_str {^^J} { \mbox{}\par }
\tl_replace_all:Nnn \l__robExt_tmp_str { ~ } { \ }
\IfBooleanTF{#1}{\texttt{\l__robExt_tmp_str}}{\begin{flushleft}\ttfamily%
\l__robExt_tmp_str
\end{flushleft}%
}
}
\let\printPlaceholder\robExtPrintPlaceholder
\NewDocumentCommand{\robExtPrintAllPlaceholdersExceptDefaults}{s}{
List ~ of ~ placeholders:\\
\seq_map_inline:Nn \l__robExt_placeholder_group_main_seq {
% We hide the elements starting with __ROBEXT_
\str_if_in:nnTF { ##1 } { __ROBEXT_ } {
\IfBooleanTF {#1} {
- ~ Placeholder ~ called ~ \texttt{\tl_to_str:n {##1}} ~ defined ~ by ~ default ~ (we ~ hide ~ the ~ definition ~ to ~ save ~ space)\\
}{}
}{
- ~ Placeholder ~ called ~ \texttt{\tl_to_str:n {##1}} ~ contains: \robExtPrintPlaceholderNoReplacement{##1}
}
}
}
\let\printAllPlaceholdersExceptDefaults\robExtPrintAllPlaceholdersExceptDefaults
\let\printImportedPlaceholdersExceptDefaults\robExtPrintAllPlaceholdersExceptDefaults
\NewDocumentCommand{\robExtPrintAllPlaceholders}{}{
List ~ of ~ placeholders:\\
\seq_map_inline:Nn \l__robExt_placeholder_group_main_seq {- ~ Placeholder ~ called ~ \texttt{\tl_to_str:n {##1}} ~ contains: \robExtPrintPlaceholderNoReplacement{##1}}
}
\let\printAllPlaceholders\robExtPrintAllPlaceholders
\let\printImportedPlaceholders\robExtPrintAllPlaceholders
%%%
%%% Evaluation of placeholders
%%%
\NewDocumentCommand{\robExtEvalPlaceholderNoReplacement}{m}{
% scantokens add an empty space at the end, so we need to add \empty to avoid it having effects
% https://tex.stackexchange.com/questions/213659/could-someone-further-elucidate-expansion-catcodes-and-scantokens
\tl_rescan:nv {}{ l__robExt_placeholder_#1_str }
}
\let\evalPlaceholderNoReplacement\robExtEvalPlaceholderNoReplacement
\NewExpandableDocumentCommand{\robExtGetPlaceholderNoReplacement}{m}{
\str_use:c { l__robExt_placeholder_#1_str }
}
\let\getPlaceholderNoReplacement\robExtGetPlaceholderNoReplacement
\NewDocumentCommand{\robExtRescanPlaceholderInVariableNoReplacement}{mm}{
\tl_gset_rescan:cnv {#1} {} { l__robExt_placeholder_#2_str }
}
\let\rescanPlaceholderInVariableNoReplacement\robExtRescanPlaceholderInVariableNoReplacement
% Make sure that the placeholder is in the list \l__robExt_placeholder_group_main_seq.
% This should automatically be called by other tools
\NewDocumentCommand{\robExtAddPlaceholderToList}{m}{
\cs_if_exist:NTF {\robExtCompletelyDisableWholeImportSystem}{}{
% Make sure we have a string here:
\robExt_set_hash_robust:Nn \l__robExt_tmp_str { #1 }
% First we remove existing occurrences (useful to avoid listing the same macro more than once
% if we redefine a macro):
\seq_remove_all:NV \l__robExt_placeholder_group_main_seq \l__robExt_tmp_str
\seq_put_right:NV \l__robExt_placeholder_group_main_seq \l__robExt_tmp_str
}
}
\let\robExtImportPlaceholder\robExtAddPlaceholderToList
\let\importPlaceholder\robExtAddPlaceholderToList
\NewDocumentCommand{\robExtClearImportedPlaceholders}{}{
\seq_clear:N \l__robExt_placeholder_group_main_seq
}
\let\clearImportedPlaceholders\robExtClearImportedPlaceholders
\NewDocumentCommand{\robExtRemoveImportedPlaceholder}{m}{
\robExt_set_hash_robust:Nn \l__robExt_tmp_str { #1 }
\seq_remove_all:NV \l__robExt_placeholder_group_main_seq \l__robExt_tmp_str
}
\let\removeImportedPlaceholder\robExtRemoveImportedPlaceholder
% add it first in the list
\NewDocumentCommand{\robExtAddPlaceholderToListFirst}{m}{
% Make sure we have a string here:
\robExt_set_hash_robust:Nn \l__robExt_tmp_str { #1 }
% First we remove existing occurrences (useful to avoid listing the same macro more than once
% if we redefine a macro):
\seq_remove_all:NV \l__robExt_placeholder_group_main_seq \l__robExt_tmp_str
\seq_put_left:NV \l__robExt_placeholder_group_main_seq \l__robExt_tmp_str
}
\let\robExtImportPlaceholderFirst\robExtAddPlaceholderToListFirst
\let\importPlaceholderFirst\robExtAddPlaceholderToListFirst
% add it first in the list
\NewDocumentCommand{\robExtAddPlaceholdersToListFirst}{m}{
\robExt_set_hash_robust:Nn \l__robExt_tmp_str { #1 }
\seq_set_from_clist:NN \l_tmp_seq \l__robExt_tmp_str
% First we remove existing occurrences (useful to avoid listing the same macro more than once
% if we redefine a macro):
\seq_map_inline:Nn \l_tmp_seq {
% This seems to be required to ensure catcodes are correct before removing the elements:
\seq_remove_all:Nn \l__robExt_placeholder_group_main_seq {##1}
}
\seq_put_left:NV \l__robExt_placeholder_group_main_seq \l__robExt_tmp_str
}
\let\robExtImportPlaceholdersFirst\robExtAddPlaceholdersToListFirst
\let\importPlaceholdersFirst\robExtAddPlaceholdersToListFirst
\NewDocumentCommand{\robExtRemovePlaceholder}{m}{
% This seems to be required to ensure catcodes are correct before removing the elements:
\robExt_set_hash_robust:Nn \l__robExt_tmp_str { #1 }
% First we check if it exists, no need to loop over everything otherwise
\str_if_exist:cTF { l__robExt_placeholder_#1_str } {
\seq_remove_all:NV \l__robExt_placeholder_group_main_seq \l__robExt_tmp_str
\expandafter \let \csname l__robExt_placeholder_#1_str \endcsname \undefined
} { }
}
\let\removePlaceholder\robExtRemovePlaceholder
\NewDocumentCommand{\checkIfPlaceholderNameIsLegal}{m}{
\cs_if_exist:NTF {\robExtPlaceholderOnlyWithUnderscores} {
\str_if_in:nnTF {#1}{__}{}{
\msg_error:nn{robExt}{underscore in name}
}
} {
\str_if_in:nnTF {#1}{__}{}{
\msg_warning:nn{robExt}{recommend two underscores in placeholders}
}
}
}
%% Usage: \placeholderFromContent{MYTITLE}{My slide title}
%% MYTITLE will contain at the end "My slide title"
%% Warning: only LaTeX-friendly code should be placed here, as LaTeX does not preserve some symbols and adds spaces
%% Tested!
%% The star version does NOT import the placeholder (useful for efficiency reasons)
\NewDocumentCommand{\robExtPlaceholderFromContent}{sm+m}{
\robExt_set_hash_robust:cn { l__robExt_placeholder_#2_str } {#3}
\checkIfPlaceholderNameIsLegal{#2}
\ifdefined\robExtCompletelyDisableWholeImportSystem\else% Mostly for time optimizations...
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#2}}%
\fi
}
\let\placeholderFromContent\robExtPlaceholderFromContent
\let\robExtSetPlaceholder\robExtPlaceholderFromContent
\let\setPlaceholder\robExtPlaceholderFromContent
\NewDocumentCommand{\robExtPlaceholderFromContentFirst}{smm}{
\robExt_set_hash_robust:cn { l__robExt_placeholder_#2_str } {#3}
\checkIfPlaceholderNameIsLegal{#2}
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToListFirst{#2}}
}
\let\placeholderFromContentFirst\robExtPlaceholderFromContentFirst
\let\robExtSetPlaceholderFirst\robExtPlaceholderFromContentFirst
\let\setPlaceholderFirst\robExtPlaceholderFromContentFirst
\NewDocumentCommand{\robExtPlaceholderFromString}{smm}{
\str_set_eq:cN { l__robExt_placeholder_#2_str } #3
\checkIfPlaceholderNameIsLegal{#2}
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#2}}
}
\let\placeholderFromString\robExtPlaceholderFromString
\let\setPlaceholderFromString\robExtPlaceholderFromString
\NewDocumentCommand{\robExtPlaceholderFromStringExpanded}{smm}{
\str_set:cx { l__robExt_placeholder_#2_str } {#3}
\checkIfPlaceholderNameIsLegal{#2}
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#2}}
}
\let\placeholderFromStringExpanded\robExtPlaceholderFromStringExpanded
\let\setPlaceholderFromStringExpanded\robExtPlaceholderFromStringExpanded
\NewDocumentCommand{\robExtCopyPlaceholder}{smm}{
\str_set_eq:cc { l__robExt_placeholder_#2_str } { l__robExt_placeholder_#3_str }
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#2}}
}
\let\copyPlaceholder\robExtCopyPlaceholder
%% Add something to the right of the placeholder
%% By default it adds a space in between, unless we use the star version
\NewDocumentCommand{\robExtAddToPlaceholder}{smm}{
\str_if_exist:cTF { l__robExt_placeholder_#2_str } {
\robExt_set_hash_robust:Nn \l_tmp_str {#3} %% needed as put_right does not convert to string first
\IfBooleanTF{#1}{}{\str_put_right:cn { l__robExt_placeholder_#2_str } { ~ } }
\str_put_right:cV { l__robExt_placeholder_#2_str } \l_tmp_str
}{
\robExt_set_hash_robust:cn { l__robExt_placeholder_#2_str } {#3}
\robExtAddPlaceholderToList{#2}
}
}
\let\addToPlaceholder\robExtAddToPlaceholder
\NewDocumentCommand{\robExtAddToPlaceholderNoImport}{smm}{
\str_if_exist:cTF { l__robExt_placeholder_#2_str } {
\robExt_set_hash_robust:Nn \l_tmp_str {#3} %% needed as put_right does not convert to string first
\IfBooleanTF{#1}{}{\str_put_right:cn { l__robExt_placeholder_#2_str } { ~ } }
\str_put_right:cV { l__robExt_placeholder_#2_str } \l_tmp_str
}{
\robExt_set_hash_robust:cn { l__robExt_placeholder_#2_str } {#3}
}
}
\let\addToPlaceholderNoImport\robExtAddToPlaceholderNoImport
%% Add something to the right of the placeholder
%% By default it adds a space in between, unless we use the star version
\NewDocumentCommand{\robExtAddBeforePlaceholder}{smm}{
\str_if_exist:cTF { l__robExt_placeholder_#2_str } {
\robExt_set_hash_robust:Nn \l_tmp_str {#3} %% needed as put_right does not convert to string first
\IfBooleanTF{#1}{}{\str_put_left:cn { l__robExt_placeholder_#2_str } { ~ } }
\str_put_left:cV { l__robExt_placeholder_#2_str } \l_tmp_str
}{
\robExt_set_hash_robust:cn { l__robExt_placeholder_#2_str } {#3}
\robExtAddPlaceholderToList{#2}
}
}
\let\addBeforePlaceholder\robExtAddBeforePlaceholder
\NewDocumentCommand{\robExtAddBeforePlaceholderNoImport}{smm}{
\str_if_exist:cTF { l__robExt_placeholder_#2_str } {
\robExt_set_hash_robust:Nn \l_tmp_str {#3} %% needed as put_right does not convert to string first
\IfBooleanTF{#1}{}{\str_put_left:cn { l__robExt_placeholder_#2_str } { ~ } }
\str_put_left:cV { l__robExt_placeholder_#2_str } \l_tmp_str
}{
\robExt_set_hash_robust:cn { l__robExt_placeholder_#2_str } {#3}
}
}
\let\addBeforePlaceholderNoImport\robExtAddBeforePlaceholderNoImport
\ExplSyntaxOff
% except inside setplaceholderfromcode, we want to modify by default the main content orig placeholder
\def\robExtCurrentPlaceholderName{__ROBEXT_MAIN_CONTENT_ORIG__}
\pgfqkeys{/robExt}{
defaultPlaceholderFromCodeStyle/.style={
remove leading spaces,
},
}
\ExplSyntaxOn
% Usage:
% \begin{placeholderFromCode}{HELPERFUNCTION}
% def my_helper_function(bla):
% return bla + 1
% \end{placeholderFromCode}
% HELPERFUNCTION will contain at the end "def ..."
% This environment cannot be placed inside any other macro/align/...
\NewDocumentEnvironment{RobExtPlaceholderFromCode}{sO{defaultPlaceholderFromCodeStyle}m}{%
\checkIfPlaceholderNameIsLegal{#3}%
% % debug part
% \str_set:Nn \l_test_str {#1}
% \show\l_test_str
%% Environments can't use verbatim yet: https://github.com/latex3/latex3/issues/591
% Might be related: https://tex.stackexchange.com/questions/633523/saving-the-body-of-an-environment-to-a-file-verbatim-using-xparse
%% Here is the first part:
%% https://tex.stackexchange.com/a/680259/116348 might work and be more efficient, but it might be less reliable
%% and definitely more complicated and error prone. Instead, we write to a file and read the result.
%% TODO: try to do it using verbatim, might be trivial or complicated, not sure, maybe see https://tex.stackexchange.com/questions/555359/reading-lines-verbatim-into-a-sequence-variable
\XSIMfilewritestart{\jobnameNoQuotes-robExt-tmp-file-you-can-remove.tmp}%
}{%
\XSIMfilewritestop%
\ior_open:Nn \g__robExt_read_ior {\jobnameNoQuotes-robExt-tmp-file-you-can-remove.tmp}%
%% Put the file in l__robExt_tmp_contain_file_str
\str_clear_new:N \l__robExt_tmp_str%
\ior_str_map_inline:Nn \g__robExt_read_ior {%
\str_gput_right:Nx \l__robExt_tmp_str {\tl_to_str:n{##1}^^J}%
}%
\str_set_eq:cN {l__robExt_placeholder_#3_str} \l__robExt_tmp_str%
%% We apply the style, useful for instance to remove indentation
\def\robExtCurrentPlaceholderName{#3}%
\pgfqkeys{/robExt}{#2}%
\IfBooleanTF {#1} {} {
\robExtAddPlaceholderToList{#3}
%% Otherwise they will be lost when the environment ends
\robExtKeepaftergroup{l__robExt_placeholder_group_main_seq}%
}%
%% for other variable
\robExtKeepaftergroup{l__robExt_placeholder_#3_str}%
}%
\let\PlaceholderFromCode\RobExtPlaceholderFromCode
\let\endPlaceholderFromCode\endRobExtPlaceholderFromCode
\let\SetPlaceholderCode\RobExtPlaceholderFromCode
\let\endSetPlaceholderCode\endRobExtPlaceholderFromCode
\NewDocumentCommand{\robExtKeepPlaceholderAfterGroup}{m}{
\robExtKeepaftergroup{l__robExt_placeholder_#1_str}%
}
\let\keepPlaceholderAfterGroup\robExtKeepPlaceholderAfterGroup
%% Usage:
%% \placeholderPathFromFilename{MYLIBPATH}{mylib.py}
%% This will copy mylib.py in the cache, and set MYLIBPATH to the name of the file in the cache like
%% MYLIBPATH = robExt-abcmylib.py
%% Tested!
\NewDocumentCommand{\robExtPlaceholderPathFromFilename}{smm}{
\ior_open:Nn \g__robExt_read_ior {#3}
%% Put the file in l__robExt_tmp_contain_file_str
\str_clear_new:N \l__robExt_tmp_contain_file_str
\ior_str_map_inline:Nn \g__robExt_read_ior {
\str_put_right:Nx \l__robExt_tmp_contain_file_str {\tl_to_str:n{##1}^^J}
}
%% computes the new filename based on the md5
\str_set:Nx \l__robExt_tmp_filename_no_prefix_str {\robExt@pdfmdfivesum{\l__robExt_tmp_contain_file_str}#3}
%% Writes the content to the file
\robExtCheckIfPrefixFolderExists
\iow_open:Nx \g__robExt_write_iow {\robExtAddCachePathAndName{\l__robExt_tmp_filename_no_prefix_str}}
\iow_now:NV \g__robExt_write_iow \l__robExt_tmp_contain_file_str
\iow_close:N \g__robExt_write_iow
%% sets the template name to the relative path to the file
\str_set:cx { l__robExt_placeholder_#2_str } {\robExtPrefixFilename\l__robExt_tmp_filename_no_prefix_str}
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#2}}
}
\let\placeholderPathFromFilename\robExtPlaceholderPathFromFilename
%% Usage:
%% \placeholderFromFileContent{MYLIB}{mylib.py}
%% This will set MYLIB to the content of the file mylib.py
%% Tested!
\NewDocumentCommand{\robExtPlaceholderFromFileContent}{smm}{
\checkIfPlaceholderNameIsLegal{#2}
\ior_open:Nn \g__robExt_read_ior {#3}
%% Put the file in l__robExt_tmp_contain_file_str
\str_clear_new:N \l__robExt_tmp_contain_file_str
\ior_str_map_inline:Nn \g__robExt_read_ior {
\str_put_right:Nx \l__robExt_tmp_contain_file_str {\tl_to_str:n{##1}^^J}
}
%% sets the template name to the relative path to the file
\str_set_eq:cN { l__robExt_placeholder_#2_str } \l__robExt_tmp_contain_file_str
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#2}}
}
\let\placeholderFromFileContent\robExtPlaceholderFromFileContent
%% Usage:
%% \placeholderPathFromContent{MYLIBPATH}{some code}
%% This will copy "some code" in the cache, and set MYLIBPATH to the name of the file in the cache like
%% MYLIBPATH = robExt-abc.py
%% Tested!
\NewDocumentCommand{\robExtPlaceholderPathFromContent}{smO{.tex}m}{
\robExt_set_hash_robust:Nn \l__robExt_tmp_contain_file_str {#4}
%% computes the new filename based on the md5
\str_set:Nx \l__robExt_tmp_filename_no_prefix_str {\robExt@pdfmdfivesum{\l__robExt_tmp_contain_file_str}#3}
%% Writes the content to the file
\robExtCheckIfPrefixFolderExists
\iow_open:Nx \g__robExt_write_iow {\robExtAddCachePathAndName{\l__robExt_tmp_filename_no_prefix_str}}
\iow_now:NV \g__robExt_write_iow \l__robExt_tmp_contain_file_str
\iow_close:N \g__robExt_write_iow
%% sets the template name to the relative path to the file
\str_set:cx { l__robExt_placeholder_#2_str } {\robExtPrefixFilename\l__robExt_tmp_filename_no_prefix_str}
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#2}}
}
\let\placeholderPathFromContent\robExtPlaceholderPathFromContent
%% Usage:
%% \begin{PlaceholderPathFromCode}{mylibpath}
%% def blabla():
%% \end{PlaceholderPathFromCode}
%% This will copy "some code" in the cache, and set MYLIBPATH to the name of the file in the cache like
%% MYLIBPATH = robExt-abc.py
\NewDocumentEnvironment{RobExtPlaceholderPathFromCode}{sO{}O{defaultPlaceholderFromCodeStyle}m}{
\XSIMfilewritestart*{\jobnameNoQuotes-robExt-tmp-file-you-can-remove.tmp}
}{
\XSIMfilewritestop
\ior_open:Nn \g__robExt_read_ior {\jobnameNoQuotes-robExt-tmp-file-you-can-remove.tmp}
%% Put the file in \l__robExt_tmp_contain_file_str
\str_clear_new:N \l__robExt_tmp_contain_file_str
\ior_str_map_inline:Nn \g__robExt_read_ior {
\str_gput_right:Nx \l__robExt_tmp_contain_file_str {\tl_to_str:n{##1}^^J}
}
%% computes the new filename based on the md5
\str_set:Nx \l__robExt_tmp_filename_no_prefix_str {\robExt@pdfmdfivesum{\l__robExt_tmp_contain_file_str}#2}
%% Writes the content to the file
\robExtCheckIfPrefixFolderExists
\iow_open:Nx \g__robExt_write_iow {\robExtAddCachePathAndName{\l__robExt_tmp_filename_no_prefix_str}}
\iow_now:NV \g__robExt_write_iow \l__robExt_tmp_contain_file_str
\iow_close:N \g__robExt_write_iow
%% sets the template name to the relative path to the file
\str_set:cx { l__robExt_placeholder_#4_str } {\robExtPrefixFilename\l__robExt_tmp_filename_no_prefix_str}
%% We apply the style, useful for instance to remove indentation
\def\robExtCurrentPlaceholderName{#4}%
\pgfqkeys{/robExt}{#3}%
\IfBooleanTF {#1} {} {
\robExtAddPlaceholderToList{#4}
\robExtKeepaftergroup{l__robExt_placeholder_group_main_seq}
}
%% Otherwise they will be lost when the environment ends
\robExtKeepaftergroup{l__robExt_placeholder_#4_str}
}
\let\PlaceholderPathFromCode\RobExtPlaceholderPathFromCode
\let\endPlaceholderPathFromCode\endRobExtPlaceholderPathFromCode
% %%% Evaluate a string by replacing the placeholders until there is none left
% %%% the result will be in \robExtResult
\cs_set:Nn \__robExt_replace_until_impossible: {
\robExtDebugMessage{We~needed~to~run~replace_until_impossible}
% Try to replace directly if single placeholder
\str_if_exist:cTF { l__robExt_placeholder_ \l_robExt_result_str _str }{
\str_set_eq:Nc {\l_robExt_result_str}{ l__robExt_placeholder_ \l_robExt_result_str _str }
\str_set:Nn \l_robext_tmp_before {}
} {
\str_set_eq:NN \l_robext_tmp_before \l_robExt_result_str
\seq_map_inline:Nn \l__robExt_placeholder_group_main_seq {
\robExtDebugMessage{Trying ~ to ~ replace ##1^^J}
\str_if_exist:cTF { l__robExt_placeholder_##1_str } {
\str_replace_all:Nnv \l_robExt_result_str { ##1 } { l__robExt_placeholder_##1_str }
}{
\robExtDebugWarning{WARNING: ~ the ~ placeholder ~ ##1 ~ does ~ not ~ exist.}
}
}
}
% In quick mode, we first try to check if the string contains __ since by default all placeholders start with __
% If not, no need to do one more run
\cs_if_exist:NTF {\robExtPlaceholderOnlyWithUnderscores} {
\str_if_in:NnTF { \l_robExt_result_str } { __ } {
\str_compare:eNeTF \l_robext_tmp_before = \l_robExt_result_str {
% We converged
}{
% The strings are different: we restart
%\message{The strings are different, we restart: It ~ used ~ to ~ be ~\l_robext_tmp_before^^J^^J now it is^^J^^J \l_robExt_result_str}
\__robExt_replace_until_impossible:
}
}{
% We found no __ so we stop before even if a change was made
}
}{
% We compare the result
\str_compare:eNeTF \l_robext_tmp_before = \l_robExt_result_str {
% \str_set_eq:cN { l__robExt_placeholder_#1_str } \l_robExt_result_str
% \robExtAddPlaceholderToList{#1}
}{
% The strings are different: we restart
\__robExt_replace_until_impossible:
}
}
}
\NewDocumentCommand{\robExtGetPlaceholderInResult}{sO{}m}{
\robExt_set_hash_robust:Nn \l_robExt_result_str { #3 }
\robExtDebugMessage{Begin~to~evaluate~placeholder^^J~\l_robExt_result_str}
\__robExt_replace_until_impossible:
\robExtDebugMessage{Ended~the~replacement^^J~\l_robExt_result_str}
\tl_if_blank:nTF {#2} {} {
% To avoid infinite recursion later and allow concatenation to a placeholder that does not exists
% we remove the name of the placeholder at the end
\str_remove_all:Nn \l_robExt_result_str { #2 }
\str_set_eq:cN { l__robExt_placeholder_#2_str } \l_robExt_result_str
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#2}}
}
}
\let\getPlaceholderInResult\robExtGetPlaceholderInResult
%% like robExtGetPlaceholderInResult put assumes that there is a single placeholder name.
%% It is used mainly for efficiency as it avoids a loop on all placeholder.
\NewDocumentCommand{\robExtGetPlaceholderInResultFromSinglePlaceholder}{sO{}m}{
\cs_if_exist:cTF { l__robExt_placeholder_#3_str }{
\str_set_eq:Nc { \l_robExt_result_str }{ l__robExt_placeholder_#3_str }
}{
\msg_error:nnx{robExt}{placeholder not existing}{#3}
}
\robExtDebugMessage{Starting~to~replace~placeholder~#3~to~get~\l_robExt_result_str}
\__robExt_replace_until_impossible:
\robExtDebugMessage{Ended~up~with~\l_robExt_result_str}
\tl_if_blank:nTF {#2} {} {
% To avoid infinite recursion later and allow concatenation to a placeholder that does not exists
% we remove the name of the placeholder at the end
\str_remove_all:Nn \l_robExt_result_str { #2 }
\str_set_eq:cN { l__robExt_placeholder_#2_str } \l_robExt_result_str
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#2}}
}
}
\cs_set:Nn \__replace_from_list:N {
\str_set_eq:NN \l_robext_tmp_before \l_robExt_result_str
\clist_map_inline:Nn {#1} {
\str_if_exist:cTF { l__robExt_placeholder_##1_str }{
\str_replace_all:Nnv \l_robExt_result_str { ##1 } { l__robExt_placeholder_##1_str }
} { }
}
% We compare the result
\str_compare:eNeTF \l_robext_tmp_before = \l_robExt_result_str {
% \str_set_eq:cN { l__robExt_placeholder_#1_str } \l_robExt_result_str
% \robExtAddPlaceholderToList{#1}
}{
% The strings are different: we restart
\__replace_from_list:N { #1 }
}
}
\NewDocumentCommand{\robExtGetPlaceholderInResultReplaceFromList}{smO{}m}{
\robExt_set_hash_robust:Nn \l_robExt_result_str { #4 }
\clist_set:Nn \l__robExt_list_placeholder_replace_clist {#2}
\__replace_from_list:N \l__robExt_list_placeholder_replace_clist
\tl_if_blank:nTF {#3} {} {
\str_remove_all:Nn \l_robExt_result_str { #3 }
\str_set_eq:cN { l__robExt_placeholder_#3_str } \l_robExt_result_str
\IfBooleanTF {#1} {} {\robExtAddPlaceholderToList{#3}}
}
}
\let\getPlaceholderInResultReplaceFromList\robExtGetPlaceholderInResultReplaceFromList
% Otherwise we need latex3
\def\robExtResult{\l_robExt_result_str}
%%%% Same version, but replaces only one (useful sometimes)
\cs_set:Nn \__replace_n_times:n {
\str_set_eq:NN \l_robext_tmp_before \l_robExt_result_str
\seq_map_inline:Nn \l__robExt_placeholder_group_main_seq {
\str_replace_all:Nnv \l_robExt_result_str { ##1 } { l__robExt_placeholder_##1_str }
}
\int_compare:nTF { #1 > 1}{
\__replace_n_times:n {\int_eval:n {#1-1}}
}{}
}
\NewDocumentCommand{\robExtSetPlaceholderRec}{smm}{
\IfBooleanTF {#1} {
\robExtGetPlaceholderInResult*[#2]{#3}
} {
\robExtGetPlaceholderInResult[#2]{#3}
}
}
\let\setPlaceholderRec\robExtSetPlaceholderRec
\NewDocumentCommand{\robExtSetPlaceholderRecReplaceFromList}{smmm}{
\IfBooleanTF {#1} {
\robExtGetPlaceholderInResultReplaceFromList*{#2}[#3]{#4}
}{
\robExtGetPlaceholderInResultReplaceFromList{#2}[#3]{#4}
}
}
\let\setPlaceholderRecReplaceFromList\robExtSetPlaceholderRecReplaceFromList
\NewDocumentCommand{\robExtGetPlaceholder}{O{}m}{
\robExtGetPlaceholderInResult[#1]{#2}
\l_robExt_result_str
}
\let\getPlaceholder\robExtGetPlaceholder
%%% Evaluate a string by replacing the placeholders until there is none left
%%% the result will be in \robExtResult
\NewDocumentCommand{\robExtEvalPlaceholder}{m}{
\robExt_set_hash_robust:Nn \l_test_str {#1}
\robExtGetPlaceholderInResult{#1}
\tl_rescan:nv {} { l_robExt_result_str }
}
\let\evalPlaceholder\robExtEvalPlaceholder
%%% Evaluate a string by replacing the placeholders until there is none left
%%% the result will be in \robExtResult
\NewDocumentCommand{\robExtEvalPlaceholderReplaceFromList}{mm}{
\robExt_set_hash_robust:Nn \l_test_str {#2}
\robExtGetPlaceholderInResultReplaceFromList{#1}{#2}
\tl_rescan:nv {} { l_robExt_result_str }
}
\let\evalPlaceholderReplaceFromList\robExtEvalPlaceholderReplaceFromList
%%% Evaluate a placeholder by first trying some strings, then checking if a template is still present (if the
%%% mode is enabled), and then it continues normally. Mostly for performance reasons.
\NewDocumentCommand{\robExtEvalPlaceholderStartFromList}{mm}{
\robExt_set_hash_robust:Nn \l_test_str {#2}
\robExtGetPlaceholderInResultReplaceFromList{#1}{#2}
\cs_if_exist:NTF {\robExtPlaceholderOnlyWithUnderscores} {
%% there might be non-important placeholders that are replaced later:
\str_set_eq:NN \l__robExt_result_minus_str \l_robExt_result_str
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_OUTPUT_PREFIX__}
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_SOURCE_FILE__}
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_OUTPUT_PDF__}
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_WAY_BACK__}
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_CACHE_FOLDER__}
\str_if_in:NnTF { \l__robExt_result_minus_str } { __ } {
\__robExt_replace_until_impossible:
}{
}
}{ \__robExt_replace_until_impossible: }
\tl_rescan:nv {} { l_robExt_result_str }
}
\let\evalPlaceholderStartFromList\robExtEvalPlaceholderStartFromList
\NewDocumentCommand{\robExtEvalPlaceholderInplace}{m}{
\robExtGetPlaceholderInResult{#1}
\tl_set_rescan:Nnx \l__robExt_tmp_tl {} { \l_robExt_result_str }
\robExt_set_hash_robust:cx { l__robExt_placeholder_#1_str } { \l__robExt_tmp_tl }
}
\let\evalPlaceholderInplace\robExtEvalPlaceholderInplace
\NewDocumentCommand{\robExtEvalPlaceholderInplaceFromSinglePlaceholder}{m}{
\robExtGetPlaceholderInResultFromSinglePlaceholder{#1}
\tl_set_rescan:Nnx \l__robExt_tmp_tl {} { \l_robExt_result_str }
\robExt_set_hash_robust:cx { l__robExt_placeholder_#1_str } { \l__robExt_tmp_tl }
}
\let\evalPlaceholderInplaceFromSinglePlaceholder\robExtEvalPlaceholderInplaceFromSinglePlaceholder
% Sometimes two symbols ## (with a different catcode than a normal hash) are added instead of a single #
\NewDocumentCommand{\robExtPlaceholderDoubleNumberHashesInplace}{m}{
\str_replace_all:cxx { l__robExt_placeholder_#1_str } { \c_hash_str } { \c_hash_str \c_hash_str }
}
\let\placeholderDoubleNumberHashesInplace\robExtPlaceholderDoubleNumberHashesInplace
\NewDocumentCommand{\robExtPlaceholderHalveNumberHashesInplace}{m}{
\str_replace_all:cxx { l__robExt_placeholder_#1_str } { \c_hash_str \c_hash_str } { \c_hash_str }
}
\let\placeholderHalveNumberHashesInplace\robExtPlaceholderHalveNumberHashesInplace
\NewDocumentCommand{\robExtPlaceholderReplaceInplace}{mmm}{
\str_replace_all:cnn { l__robExt_placeholder_#1_str } { #2 } { #3 }
}
\let\placeholderReplaceInplace\robExtPlaceholderReplaceInplace
\NewDocumentCommand{\robExtPlaceholderReplaceInplaceEval}{mmm}{
\str_replace_all:cxx { l__robExt_placeholder_#1_str } { #2 } { #3 }
}
\let\placeholderReplaceInplaceEval\robExtPlaceholderReplaceInplaceEval
% \group_begin:
% \char_set_catcode_other:N \^^I
% Usage:
% \robExtPlaceholderRemoveSpacesUntil{__MY_PLACEHOLDER__}{>>>}
\group_begin:
\char_set_catcode_other:N \^^I
\cs_new_protected:Npn \__robExt_replace_tabs:N #1 {
\str_replace_all:Nnn #1 { ^^I } { ~ }
}
\group_end:
\NewDocumentCommand{\robExtPlaceholderRemoveSpacesUntil}{mO{1}m}{
%% Cut the string in lines
\seq_set_split_keep_spaces:Nnv \l_tmpa_seq {^^J} { l__robExt_placeholder_#1_str }
\str_clear:c { l__robExt_placeholder_#1_str }
%% We iterate over the lines
\seq_map_variable:NNn \l_tmpa_seq \l_tmpa_tl {
% \l_tmpa_tl contains the current line
\seq_set_split_keep_spaces:NnV \l_tmpb_seq {#3} \l_tmpa_tl
%% In any case, the first item must only contain spaces.
% The separator was present. Check that the line only contains spaces before the separator
\seq_get_left:NN \l_tmpb_seq \l_tmpb_tl
\__robExt_replace_tabs:N \l_tmpb_tl
%\str_replace_all:Nnn \l__robExt_tmp_str { ^^I } { ~ }
%\tl_replace_all:Nnn \l_tmpb_tl {^^I} {~} % we replace tabs with spaces, check if it works
% Save the size of the string
\int_set:Nn \l_tmpa_int {\str_count:N \l_tmpb_tl}
% we remove spaces
\tl_trim_spaces:N \l_tmpb_tl
\tl_if_empty:VTF \l_tmpb_tl {
\int_compare:nNnTF {\seq_count:N \l_tmpb_seq} > {1} {
% Empty line: we add it to the current placeholder
\str_put_right:cx { l__robExt_placeholder_#1_str } {
% the placeholder is a string, so I expect l_tmpa_tl to also be a string. No problem?
\str_range:Nnn \l_tmpa_tl {\l_tmpa_int + \str_count:n {#3} + 1 + #2} {-1} ^^J
}
} {
% No separator: it means the line is empty
\str_put_right:cn { l__robExt_placeholder_#1_str } {^^J}
}
}{
% The string was not completely empty: raise an error
\msg_error:nnxxx{robExt}{remove spaces until non spaces characters}{#1}{\l_tmpa_tl}{#3}
}
}
}
\let\placeholderRemoveSpacesUntil\robExtPlaceholderRemoveSpacesUntil
% Usage:
% \robExtPlaceholderPrependAllLines{__MY_PLACEHOLDER__}{ }
\NewDocumentCommand{\robExtPlaceholderPrependAllLines}{mm}{
%% Cut the string in lines
\seq_set_split_keep_spaces:Nnv \l_tmpa_seq {^^J} { l__robExt_placeholder_#1_str }
\str_clear:c { l__robExt_placeholder_#1_str }
%% We iterate over the lines
\seq_map_variable:NNn \l_tmpa_seq \l_tmpa_tl {
\str_put_right:cx { l__robExt_placeholder_#1_str } {
#2
\l_tmpa_tl ^^J}
}
}
\let\placeholderPrependAllLines\robExtPlaceholderPrependAllLines
%% https://tex.stackexchange.com/questions/709973/latex3-efficient-way-to-remove-spaces-in-front-of-a-command/710006?noredirect=1#comment1765909_710006
%% Modifies \l_tmpa_tl so that it contains the current number of spaces to remove
%% It also uses \l_tmpa_str
\group_begin:
\char_set_catcode_other:N \^^I
\cs_new_protected:Npn \__robExt_count_leading_whitespace:n #1
{
\str_set:Nn \l_tmpa_str {#1}
\str_replace_all:Nnn \l_tmpa_str { ^^I } { ~ }
\tl_if_blank:VF \l_tmpa_str
{
\int_set:Nn \l_tmpa_int
{
\int_min:nn
{ \l_tmpa_int }
{
\str_count_spaces:N \l_tmpa_str -
\str_count_spaces:e
{ \exp_last_unbraced:NV \use:n \l_tmpa_str {} }
}
}
}
}
\cs_generate_variant:Nn \__robExt_count_leading_whitespace:n { V }
\group_end:
\NewDocumentCommand{\robExtPlaceholderRemoveLeadingSpaces}{m}{
%% Cut the string in lines
\seq_set_split_keep_spaces:Nnv \l_tmpa_seq {^^J} { l__robExt_placeholder_#1_str }
%% Stores the number of spaces we can trim. Since we will take the minimum, we add infinity first
\int_set_eq:NN \l_tmpa_int \c_max_int
%% We iterate over the lines to find the minimum number of spaces to trim
\seq_map_variable:NNn \l_tmpa_seq \l_tmpa_tl {
% If the line is empty, let's just remove it:
\int_compare:nNnTF {\str_count_ignore_spaces:V \l_tmpa_tl} > {0} {
% The line contains also letters, let us count the number of spaces.
\__robExt_count_leading_whitespace:V \l_tmpa_tl
} {
% line contains only spaces, we don't care
}
}
%% We check if the string was not all empty (e.g. if the content is empty)
\int_compare:nNnTF {\l_tmpa_int} = {\c_max_int} {} {
\str_clear:c { l__robExt_placeholder_#1_str }
%% We iterate over the lines to recreate the appropriate placeholder
\int_set:Nn \l_tmpb_int {\seq_count:N \l_tmpa_seq}
\seq_map_indexed_inline:Nn \l_tmpa_seq {
\str_put_right:cx { l__robExt_placeholder_#1_str } {
\str_range:nnn {##2} {\l_tmpa_int + 1} {-1}
% We do not want to add ^^J for the very last line, otherwise it will create a new line everytime we call it
\int_compare:nNnTF {##1} = {\l_tmpb_int} {} {
^^J
}
}
}
}
}
\let\placeholderRemoveLeadingSpaces\robExtPlaceholderRemoveLeadingSpaces
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Placeholders groups %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% I used to put all placeholders into the same group, but it was quite inefficient (latex is really slow)
% Then I tried to hide this group but keep a single list of all items, but still automatically add items to it:
% also quite slow, and the list was not so useful anyway.
% In practice, however, we do not need to put the placeholders created by default in a group: we can just store them
% in a standalone way, and load them when needed. So now I create the concept of group: people can add placeholders
% to a group of placeholders to load them later if needed (e.g. to list all placeholders created by this library).
% The group named "main" will be the main one with imported groups, where stuff like "set placeholder rec" will search for the
% existing placeholders.
% The group named "preexisting" will contain the placeholders created by this library (useful mostly for
% debugging purpose).
\NewDocumentCommand{\robExtAddPlaceholdersToGroup}{mm}{
% Make sure we have a string here:
\robExt_set_hash_robust:Nn \l__robExt_tmp_str { #2 }
\seq_set_from_clist:NN \l__robExt_tmp_seq \l__robExt_tmp_str
\cs_if_exist:cTF {l__robExt_placeholder_group_#1_seq}{}{ \seq_clear_new:c {l__robExt_placeholder_group_#1_seq}}
% First we remove existing occurrences (useful to avoid listing the same macro more than once
% if we redefine a macro):
\seq_map_inline:Nn \l__robExt_tmp_seq {
\robExtDebugMessage{Adding ##1 to #1}
\seq_remove_all:cn {l__robExt_placeholder_group_#1_seq} {##1}
\seq_put_right:cn {l__robExt_placeholder_group_#1_seq} {##1}
}
}
\let\addPlaceholdersToGroup\robExtAddPlaceholdersToGroup
\NewDocumentCommand{\robExtEnsureGroupPlaceholdersExists}{m}{
\cs_if_exist:cTF {l__robExt_placeholder_group_#1_seq}{}{ \seq_clear_new:c {l__robExt_placeholder_group_#1_seq}}
}
\let\ensureGroupPlaceholdersExists\robExtEnsureGroupPlaceholdersExists
\NewDocumentCommand{\robExtAddPlaceholdersToGroupBefore}{mm}{
% Make sure we have a string here:
\robExt_set_hash_robust:Nn \l__robExt_tmp_str { #2 }
\seq_set_from_clist:NN \l__robExt_tmp_seq \l__robExt_tmp_str
\seq_reverse:N \l__robExt_tmp_seq
\cs_if_exist:cTF {l__robExt_placeholder_group_#1_seq}{}{ \seq_clear_new:c {l__robExt_placeholder_group_#1_seq}}
% First we remove existing occurrences (useful to avoid listing the same macro more than once
% if we redefine a macro):
\seq_map_inline:Nn \l__robExt_tmp_seq {
\seq_remove_all:cn {l__robExt_placeholder_group_#1_seq} {##1}
\seq_put_left:cn {l__robExt_placeholder_group_#1_seq} {##1}
}
}
\let\addPlaceholdersToGroupBefore\robExtAddPlaceholdersToGroupBefore
\NewDocumentCommand{\robExtRemovePlaceholdersFromGroup}{mm}{
% Make sure we have a string here:
\robExt_set_hash_robust:Nn \l__robExt_tmp_str { #2 }
\seq_set_from_clist:NN \l__robExt_tmp_seq \l__robExt_tmp_str
\seq_reverse:N \l__robExt_tmp_seq
\cs_if_exist:cTF {l__robExt_placeholder_group_#1_seq}{}{ \seq_clear_new:c {l__robExt_placeholder_group_#1_seq}}
% First we remove existing occurrences (useful to avoid listing the same macro more than once
% if we redefine a macro):
\seq_map_inline:Nn \l__robExt_tmp_seq {
\seq_remove_all:cn {l__robExt_placeholder_group_#1_seq} {##1}
}
}
\let\removePlaceholdersFromGroup\robExtRemovePlaceholdersFromGroup
\let\removePlaceholderFromGroup\robExtRemovePlaceholdersFromGroup
\NewDocumentCommand{\robExtClearGroupPlaceholders}{m}{
\seq_clear_new:c {l__robExt_placeholder_group_#1_seq}
}
\let\clearGroupPlaceholders\robExtClearGroupPlaceholders
\NewDocumentCommand{\robExtCopyGroupPlaceholders}{mm}{
\seq_set_eq:cc {l__robExt_placeholder_group_#1_seq} {l__robExt_placeholder_group_#2_seq}
}
\let\copyGroupPlaceholders\robExtCopyGroupPlaceholders
\NewDocumentCommand{\robExtAppendGroupPlaceholders}{mm}{
\seq_map_inline:cn {l__robExt_placeholder_group_#2_seq} {
\seq_remove_all:cn {l__robExt_placeholder_group_#1_seq} {##1}
\seq_put_right:cn {l__robExt_placeholder_group_#1_seq} {##1}
}
}
\let\appendGroupPlaceholders\robExtAppendGroupPlaceholders
\NewDocumentCommand{\robExtAppendBeforeGroupPlaceholders}{mm}{
\seq_eq:Nc \l__robExt_tmp_seq {l__robExt_placeholder_group_#2_seq}
\seq_reverse:N \l__robExt_tmp_seq
\seq_map_inline:Nn \l__robExt_tmp_seq {
\seq_remove_all:cn {l__robExt_placeholder_group_#1_seq} {##1}
\seq_put_left:cn {l__robExt_placeholder_group_#1_seq} {##1}
}
}
\let\appendBeforeGroupPlaceholders\robExtAppendBeforeGroupPlaceholders
\NewDocumentCommand{\robExtImportPlaceholdersFromGroup}{m}{
\robExtAppendGroupPlaceholders{main}{#1}
}
\let\importPlaceholdersFromGroup\robExtImportPlaceholdersFromGroup
\NewDocumentCommand{\robExtImportAllPlaceholders}{}{
\seq_map_inline:Nn \l__robExt_list_groups_seq {
\robExtImportPlaceholdersFromGroup{##1}
}
}
\let\importAllPlaceholders\robExtImportAllPlaceholders
%%%%
%%%% List groups, mostly for debugging purpose
%%%%
\seq_new:N \l__robExt_list_groups_seq % contains the list of all groups
%%%% \robExtRegisterGroupPlaceholders{bash} adds "bash" to the list of existing groups
\NewDocumentCommand{\robExtRegisterGroupPlaceholders}{m}{
\seq_if_exist:cTF {l__robExt_placeholder_group_#1_seq}{}{
\seq_clear_new:c {l__robExt_placeholder_group_#1_seq}
}
\seq_put_right:Nn \l__robExt_list_groups_seq {#1}
}
\let\registerGroupPlaceholders\robExtRegisterGroupPlaceholders
\let\newGroupPlaceholders\robExtRegisterGroupPlaceholders
\NewDocumentCommand{\robExtShowAllRegisteredGroupsAndPlaceholders}{s}{%
\seq_map_inline:Nn \l__robExt_list_groups_seq {%
\message{^^J- Group ##1: ^^J}%
\seq_map_inline:cn {l__robExt_placeholder_group_##1_seq} {%
\message{, ####1 }%
\IfBooleanTF{#1}{
\message{Content:\use:c{l__robExt_placeholder_####1_str}}
}{}
}%
\message{^^J}
}
\show\def
}
\let\showAllRegisteredGroupsAndPlaceholders\robExtShowAllRegisteredGroupsAndPlaceholders
\NewDocumentCommand{\robExtPrintAllRegisteredGroups}{s}{%
\seq_map_inline:Nn \l__robExt_list_groups_seq {%
\texttt{##1}\par
}
}
\let\printAllRegisteredGroups\robExtPrintAllRegisteredGroups
\NewDocumentCommand{\robExtPrintAllRegisteredGroupsAndPlaceholders}{s}{%
\seq_map_inline:Nn \l__robExt_list_groups_seq {%
- ~ Group \texttt{##1}:\par
\seq_map_inline:cn {l__robExt_placeholder_group_##1_seq} {%
Placeholder \texttt{####1}%
\IfBooleanTF {#1}{\robExtPrintPlaceholderNoReplacement{####1}}{\par}
}%
}
}
\let\printAllRegisteredGroupsAndPlaceholders\robExtPrintAllRegisteredGroupsAndPlaceholders
% The star version displays the content as well
\NewDocumentCommand{\robExtPrintGroupPlaceholders}{sm}{%
Content~ of~ group~ \texttt{#2}:\par%
\seq_map_inline:cn {l__robExt_placeholder_group_#2_seq} {%
-~Placeholder ~ \texttt{##1}\par \IfBooleanTF {#1}{\robExtPrintPlaceholderNoReplacement{##1}}{}
}%
}
\let\printGroupPlaceholders\robExtPrintGroupPlaceholders
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Dependencies %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\NewDocumentCommand{\robExtResetDependencies}{m}{
\seq_clear:N \l__robExt_dependencies_str
}
\NewDocumentCommand{\robExtAddDependency}{m}{
\file_if_exist:nTF {#1} {
\seq_put_left:Nx \l__robExt_dependencies_str {#1}
} {
\msg_error:nnx{robExt}{dependency does not exist}{#1}
}
}
\NewDocumentCommand{\robExtDebugDependency}{}{
\show\l__robExt_dependencies_str
}
% Useful when compiling commands
\NewDocumentCommand{\robExtSaveDependencies}{m}{
\seq_gset_eq:cN {#1} \l__robExt_dependencies_str
}
\NewDocumentCommand{\robExtRestoreDependencies}{m}{
\seq_set_eq:Nc \l__robExt_dependencies_str {#1}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Externalization %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% The idea is to populate a placeholder called __ROBEXT_TEMPLATE__ that will contain the file to generate
%% together with a placeholder called __ROBEXT_COMPILATION_COMMAND__ that will contain the command to compile the file
\NewDocumentCommand{\robExtSetCompilationCommand}{m}{
\robExtSetPlaceholderFirst{__ROBEXT_COMPILATION_COMMAND__} {#1}
}
\NewDocumentCommand{\robExtAddArgumentToCompilationCommand}{m}{
\robExtSetPlaceholderRec{__ROBEXT_COMPILATION_COMMAND__} {__ROBEXT_COMPILATION_COMMAND__ ~ "#1"}
}
%% Alias of robExtFinalFile to \robExtSourceFile, as I don't like anymore the name I chose
%\def\robExtSourceFile{\robExtFinalFile}
% First is name of sequence for only try (we only replace those and stop), second is name of sequence for first try (we first try with the sequence and
% then do a more normal thing), third is the name of the placeholder
\cs_set:Nn \try_to_evaluate_single_placeholder_without_going_to_replace_until_impossible:NNn {
%%%%% Take care of the compilation command: might seem a bit weird and convoluted, but this is needed to get really good efficiency
%%%%% since \__robExt_replace_until_impossible: is quite costly, we try to avoid it
\cs_if_exist:NTF {#1} { % We only replace fixed state of placeholders
\str_set_eq:Nc \l_robExt_result_str {l__robExt_placeholder_#3_str}
\seq_map_inline:Nn {#1} {
\robExtDebugMessage{replacing~##1~in~\l_robExt_result_str}
\str_replace_all:Nnv \l_robExt_result_str {##1} {l__robExt_placeholder_##1_str}
}
\robExtDebugMessage{Cool,~we~avoided~the~costly~replace~(only~try).}
}{
\cs_if_exist:NTF {#2} { % We start by replacing a fixed state of placeholders
\str_set_eq:Nc \l_robExt_result_str {l__robExt_placeholder_#3_str}
\robExtDebugMessage{We~start~by~replacing~a~fixed~state~of~placeholders}
\seq_map_inline:Nn {#2} {
\str_if_exist:cTF {l__robExt_placeholder_##1_str} {
\robExtDebugMessage{Replacing~##1~into~\use:c{l__robExt_placeholder_##1_str}}%
\str_replace_all:Nnv \l_robExt_result_str {##1} {l__robExt_placeholder_##1_str}
} {
\robExtDebugMessage{Warning: ~ trying~to~replace~##1~that~does~not~exist.}
}
}
%\show\l_robExt_result_str
%% Check if there is some more stuff to replace (possible only if we consider only templates with underscores
\cs_if_exist:NTF {\robExtPlaceholderOnlyWithUnderscores} {
% all templates have underscores
% first check if some non-important elements are here: for this we first remove them
\str_set_eq:NN \l__robExt_result_minus_str \l_robExt_result_str
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_OUTPUT_PREFIX__}
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_SOURCE_FILE__}
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_OUTPUT_PDF__}
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_WAY_BACK__}
\str_remove_all:Nn \l__robExt_result_minus_str {__ROBEXT_CACHE_FOLDER__}
% Now, we check if some __ are left:
\str_if_in:NnTF { \l__robExt_result_minus_str } { __ } {
% Some templates are left
\robExtDebugMessage{Still~some~templates~are~left.}
\__robExt_replace_until_impossible:
} {
% No more template: the end
\robExtDebugMessage{Cool,~we~avoided~the~costly~replace: \l_robExt_result_str.}
}
}{
% no way to know, must do it anyway
\robExtDebugMessage{Seems~like~not~all~templates~contain~underscore.~Too~bad,~it~will~be~slower}
\__robExt_replace_until_impossible:
}
}{
\robExtDebugMessage{No~advice~on~how~to~start:~doing~it~the~long~way}
\robExtGetPlaceholderInResultFromSinglePlaceholder{#3}
}
}
}
\NewDocumentCommand{\robExtOnlyPlaceholdersInCompilationCommand}{m}{
\seq_set_from_clist:Nn \robExtOnlyTryThisSequenceOnCompilationCommand {#1}
}
\let\onlyPlaceholdersInCompilationCommand\robExtOnlyPlaceholdersInCompilationCommand
\NewDocumentCommand{\robExtFirstPlaceholdersInCompilationCommand}{m}{
\seq_set_from_clist:Nn \robExtFirstTryThisSequenceOnCompilationCommand {#1}
}
\let\firstPlaceholdersInCompilationCommand\robExtFirstPlaceholdersInCompilationCommand
\NewDocumentCommand{\robExtOnlyPlaceholdersInTemplate}{m}{
\seq_set_from_clist:Nn \robExtOnlyTryThisSequenceOnTemplate {#1}
}
\let\onlyPlaceholdersInTemplate\robExtOnlyPlaceholdersInTemplate
\NewDocumentCommand{\robExtFirstPlaceholdersInTemplate}{m}{
\seq_set_from_clist:Nn \robExtFirstTryThisSequenceOnTemplate {#1}
}
\let\firstPlaceholdersInTemplate\robExtFirstPlaceholdersInTemplate
% for use outside of expl3 (pgf)
\def\robExtCurrentCompilationCommand{\l__robExt_current_compilation_command_str}
\NewDocumentCommand{\robExtGetNearlyFinalValueTemplateAndCompilationCommand}{}{
%% oututs the compilation in \l__robExt_current_compilation_command_str and the template in \l_robExt_result_str
%% This is for efficiency reasons: we only allow a few placeholders:
%% In compilation command:
%%
%% In template:
%% - __ROBEXT_MAIN_CONTENT__
%% - __ROBEXT_MAIN_CONTENT_ORIG__
%% - __ROBEXT_LATEX_PREAMBLE__
%% - __ROBEXT_OUTPUT_PREFIX__
%% - __ROBEXT_LATEX_WRITE_DEPTH_TO_OUT_FILE__
%% - __ROBEXT_LATEX_CREATE_OUT_FILE__
%% Others
%% - __ROBEXT_SOURCE_FILE__
%% - __ROBEXT_OUTPUT_PDF__
%% - __ROBEXT_WAY_BACK__
%% - __ROBEXT_CACHE_FOLDER__
\ifdefined\robExtDisablePlaceholders%
\robExtDebugMessage{You~have~disabled~placeholders~(this~is~likely~a~compiled~template)}%
\str_set_eq:NN \l__robExt_current_compilation_command_str \l__robExt_placeholder___ROBEXT_COMPILATION_COMMAND___str
\str_set_eq:NN \l_robExt_result_str \l__robExt_placeholder___ROBEXT_TEMPLATE___str%
\str_replace_all:Nnv \l_robExt_result_str {__ROBEXT_MAIN_CONTENT__} {l__robExt_placeholder___ROBEXT_MAIN_CONTENT___str}
\str_replace_all:Nnv \l_robExt_result_str {__ROBEXT_LATEX_PREAMBLE__} {l__robExt_placeholder___ROBEXT_LATEX_PREAMBLE___str}
\str_replace_all:Nnv \l_robExt_result_str {__ROBEXT_MAIN_CONTENT_ORIG__} {l__robExt_placeholder___ROBEXT_MAIN_CONTENT_ORIG___str}
\else
\ifdefined\robExtEnableImportMechanism\else\robExtImportAllPlaceholders\fi%
%%%%% Take care of the compilation command: might seem a bit weird and convoluted, but this is needed to get really good efficiency
%%%%% since \__robExt_replace_until_impossible: is quite costly, we try to avoid it
\try_to_evaluate_single_placeholder_without_going_to_replace_until_impossible:NNn \robExtOnlyTryThisSequenceOnCompilationCommand \robExtFirstTryThisSequenceOnCompilationCommand {__ROBEXT_COMPILATION_COMMAND__}
\str_set_eq:NN \l__robExt_current_compilation_command_str \l_robExt_result_str
%%%%% Take care of
\try_to_evaluate_single_placeholder_without_going_to_replace_until_impossible:NNn \robExtOnlyTryThisSequenceOnTemplate \robExtFirstTryThisSequenceOnTemplate {__ROBEXT_TEMPLATE__}
\fi
}
\NewDocumentCommand{\robExtSetBackCompilationCommandAndTemplate}{}{
\robExtPlaceholderFromString{__ROBEXT_COMPILATION_COMMAND__}{\l__robExt_current_compilation_command_str}%
\robExtPlaceholderFromString{__ROBEXT_TEMPLATE__}{\l_robExt_result_str}%
}
\NewDocumentCommand{\robExtWriteFile}{m}{
%%% First we get all dependencies stored in \l__robExt_dependencies_str to create a csv-like file:
\str_clear:N \l__robExt_dependencies_mdfive_str
\robExtGetNearlyFinalValueTemplateAndCompilationCommand
% We first add on the first line the compilation command, and on the second line the template file.
\str_set:Nx \l__robExt_dependencies_mdfive_str {command,\l__robExt_current_compilation_command_str^^J\robExt@pdfmdfivesum{\l_robExt_result_str ^^J},^^J} %% ^^J is a newline: LaTeX will automatically add a new line when writing the file
\seq_map_inline:Nn \l__robExt_dependencies_str {
\str_put_right:Nx \l__robExt_dependencies_mdfive_str {\file_mdfive_hash:n{##1},##1^^J} %% ^^J is a newline
}
%%
%% Compute the final hash (the hash of all dependencies, including the current picture that is on the first line):
%% The last newline is needed as the write operation automatically adds a newline.
\tl_set:Nx \robExtFinalHash {\robExt@pdfmdfivesum{\l__robExt_dependencies_mdfive_str^^J}}
% Might be useful to clean the previously compiled files, or change the MD5 sum, for instance if we do
% not want to recompile when the file changes
\robExtUseHookJustBeforeWritingFiles%
%% We add the figure in the list of files.
% Avoid writing the same command multiple times (might occur in environments that execute the
% same command multiple times like align etc)
\cs_if_exist:cTF {l__robExt_write_list_all_figures_\robExtAddPrefixName{\robExtFinalHash.tex}:}{}{
\iow_now:Nx \g__robExt_write_list_all_figures_iow {\robExtAddPrefixName{\robExtFinalHash.tex}}
\cs_gset:cn {l__robExt_write_list_all_figures_\robExtAddPrefixName{\robExtFinalHash.tex}:} {}
}
%% We can now set the placeholders, and recompute the final value of the file:
%% I was doing before \robExtPlaceholderFromContent + \robExtEvalPlaceholderInplace, but it is too slow
\str_set:Nx \l_tmp_output_prefix_str {\robExtAddPrefixName{\robExtFinalHash}}
%%% No need to re-evaluate from scratch the string,
\str_replace_all:Nnx \l_robExt_result_str {__ROBEXT_OUTPUT_PREFIX__}{\l_tmp_output_prefix_str}
\str_replace_all:Nnx \l_robExt_result_str {__ROBEXT_SOURCE_FILE__}{\l_tmp_output_prefix_str .tex}
\str_replace_all:Nnx \l_robExt_result_str {__ROBEXT_OUTPUT_PDF__}{\l_tmp_output_prefix_str .pdf}
\str_replace_all:Nnx \l_robExt_result_str {__ROBEXT_WAY_BACK__}{\robExtCacheFolderWayBack}
\str_replace_all:Nnx \l_robExt_result_str {__ROBEXT_CACHE_FOLDER__}{\robExtCacheFolder}
\file_if_exist:xTF{\robExtAddCachePathAndName{\robExtFinalHash.tex}}{
\robExtDebugInfo{The\space file\space \robExtAddCachePathAndName{\robExtFinalHash.tex} \space already\space exists.^^J}
}{
% arXiv removes the .pdf if the .tex is present... so we need to manually remove the .tex file.
\file_if_exist:xTF{\robExtAddCachePathAndName{\robExtFinalHash.pdf}}{
\robExtDebugInfo{The\space file\space \robExtAddCachePathAndName{\robExtFinalHash.pdf} \space already\space exists.^^J}
% Sometimes we still want to refer to the original tex file, for instance to input the source.
% So if a file .tex-backup exists, we copy it back to .tex:
\file_if_exist:xTF{\robExtAddCachePathAndName{\robExtFinalHash.tex-backup}} {
\robExtDebugInfo{We~copy~the~source~\robExtFinalHash.tex-backup~to~\robExtFinalHash.tex^^J}%
% First we read the file:
\ior_open:Nn \g__robExt_read_ior {\robExtAddCachePathAndName{\robExtFinalHash.tex-backup}}%
\iow_open:Nx \g__robExt_write_iow {\robExtAddCachePathAndName{\robExtFinalHash.tex}}
\ior_str_map_inline:Nn \g__robExt_read_ior {%
\iow_now:Nx \g__robExt_write_iow {\tl_to_str:n{##1}}%
}%
\iow_close:N \g__robExt_write_iow
\ior_close:N \g__robExt_read_ior
}{}
}{
\str_if_eq:VnTF { \l_robExt_result_str }{__ROBEXT_TEMPLATE__} {
\msg_error:nn{robExt}{forgot template}
}{
% Check if the output directory exists
\robExtCheckIfPrefixFolderExists
\iow_open:Nx \g__robExt_write_iow {\robExtAddCachePathAndName{\robExtFinalHash.deps}}
\iow_now:NV \g__robExt_write_iow \l__robExt_dependencies_mdfive_str
\iow_close:N \g__robExt_write_iow
%% Save the final file:
\iow_open:Nx \g__robExt_write_iow {\robExtAddCachePathAndName{\robExtFinalHash.tex}}
\iow_now:NV \g__robExt_write_iow \l_robExt_result_str
\iow_close:N \g__robExt_write_iow
\robExtDebugInfo{Source ~ saved ~ in ~ \robExtAddCachePathAndName{\robExtFinalHash.tex}.}
\ifdefined\robExtPrintSourceWhenSaving
\typeout{Content~of~the~deps~file~\robExtAddCachePathAndName{\robExtFinalHash.deps}:^^J}
\robExt_write_file_in_log:x {\robExtAddCachePathAndName{\robExtFinalHash.deps}}
\typeout{Content~of~the~source~file~\robExtAddCachePathAndName{\robExtFinalHash.tex}:^^J}
\robExt_write_file_in_log:x {\robExtAddCachePathAndName{\robExtFinalHash.tex}}
\fi
}
}
}
}
% https://tex.stackexchange.com/questions/133324/shell-escape-with-latex-3
% We need shell escape to work (but it's enabled by default on overleaf!)
% Think about the number of compilations.
\NewDocumentCommand{\robExtCompileFile}{m}{
\file_if_exist:xTF{\robExtAddCachePathAndName{\robExtFinalHash.pdf}}{
\cs_if_exist:NTF {\robExtForceRecompilation}{
% We cannot do an OR with file_if_exist since it is not expandable, hence this trick
\let\l__robExt_do_not_compile\undefined
}{
\def\l__robExt_do_not_compile{}
}
}{
\let\l__robExt_do_not_compile\undefined
}
\cs_if_exist:NTF {\l__robExt_do_not_compile}{
\robExtDebugInfo{No ~ need ~ to ~ recompile ~ \robExtAddCachePathAndName{\robExtFinalHash.pdf}^^J}
}{
% Used to know later if we have run a compilation command at that step or not
\cs_undefine:N \l__robExt_I_just_ran_a_compilation_command:
% No need to re-evaluate it
% \robExtGetPlaceholderInResultFromSinglePlaceholder{__ROBEXT_COMPILATION_COMMAND__}
\str_replace_all:Nnx \l__robExt_current_compilation_command_str {__ROBEXT_OUTPUT_PREFIX__}{\l_tmp_output_prefix_str}
\str_replace_all:Nnx \l__robExt_current_compilation_command_str {__ROBEXT_SOURCE_FILE__}{\l_tmp_output_prefix_str .tex}
\str_replace_all:Nnx \l__robExt_current_compilation_command_str {__ROBEXT_OUTPUT_PDF__}{\l_tmp_output_prefix_str .pdf}
\str_replace_all:Nnx \l__robExt_current_compilation_command_str {__ROBEXT_WAY_BACK__}{\robExtCacheFolderWayBack}
\str_replace_all:Nnx \l__robExt_current_compilation_command_str {__ROBEXT_CACHE_FOLDER__}{\robExtCacheFolder}
% Make sure this command is run from the cache folder
\ifdefined\robExtCacheFolder
\ifx\robExtCacheFolder\empty\else
\str_put_left:Nx \l__robExt_current_compilation_command_str {cd ~ \robExtCacheFolder \space && ~ }
\fi
\fi%
%%% We enable manual mode if we enabled "compile in parallel after=N" and we compiled more than N elements
% Check if we want to run stuff in parallel
\ifdefined\robExt@compile@parallel@after
% TODO: ****
\int_gset:Nn \g__robExt_number_figures_just_compiled_or_to_compile {\g__robExt_number_figures_just_compiled_or_to_compile + 1}
% We check if the number of figures that we already compiled is large enough to start parallel compilation
\int_compare:nNnTF {
\g__robExt_number_figures_just_compiled_or_to_compile} > {\robExt@compile@parallel@after
} {
\def\robExtManualMode
} {}
\fi
\ifdefined\robExtManualMode
\message{[robExt] Manual mode enabled: please, manually compile the images using \l__robExt_current_compilation_command_str or run 'bash \jobnameNoQuotes-\robExtAddPrefixName{compile-missing-figures.sh}'.}
% Avoid writing the same command multiple times (might occur in environments that execute the
% same command multiple times like align etc)
\cs_if_exist:cTF {l__robExt_missing_figure_\l__robExt_current_compilation_command_str :}{}{
\ifdefined\robExtDoNotRedirectOutputInManuallyCompileMissingFigures%
\iow_now:Nx \g__robExt_write_manually_compile_all_missing_figures_iow {\l__robExt_current_compilation_command_str}% a new line is automatically added
\else
\iow_now:Nx \g__robExt_write_manually_compile_all_missing_figures_iow {(~\l__robExt_current_compilation_command_str~)~>~\robExtAddCachePathAndName{\robExtFinalHash-compilation.log}~2>&1}% a new line is automatically added
\fi
\cs_gset:cn {l__robExt_missing_figure_\l__robExt_current_compilation_command_str :} {}
\ifdefined\robExt@compile@parallel@after
\gdef\robExt@compile@parallel@must@compile{}
\seq_gput_right:Nx \g__robExt_files_compiled_in_parallel {\robExtAddCachePathAndName{\robExtFinalHash}}
% Gets the line with the error for easier debugging
\cs_gset:cx {g__robExt_lines_error_\robExtAddCachePathAndName{\robExtFinalHash}:} {\msg_line_number:}
\ifdefined\robExtPrintWholeFile
\cs_gset:cx {g__robExt_print_whole_file_\robExtAddCachePathAndName{\robExtFinalHash}:} {}
\fi
\cs_gset:cx {g__robExt_compilation_command_\robExtAddCachePathAndName{\robExtFinalHash}:} {\l__robExt_current_compilation_command_str}
\fi
}
\else%
\bool_if:nTF { \sys_if_shell_unrestricted_p: || \cs_if_exist_p:N \robExtForceCompilation }
{
% For a better debugging, we create a new file:
\ifdefined\robExtDoNotRedirectOutput%
\sys_shell_now:x {\robExtPrefixAllCompilationCommands \l__robExt_current_compilation_command_str}%
\else
\sys_shell_now:x {\robExtPrefixAllCompilationCommands (~\l__robExt_current_compilation_command_str~)~>~\robExtAddCachePathAndName{\robExtFinalHash-compilation.log}~2>&1}%
\fi
\message{[robExt] We ~ will ~ start ~ the ~ compilation ~ using: ~ \l__robExt_current_compilation_command_str.}%
%% We notify that we are running the compilation command, this way it is possible to check later
%% if the file is not present because of some compilation error or because we are in manual mode
%% or fallback
\cs_set:Nn \l__robExt_I_just_ran_a_compilation_command: {}
}{
\ifdefined\robExtFallbackManualMode
% Avoid writing the same command multiple times (might occur in environments that execute the
% same command multiple times like align etc)
\cs_if_exist:cTF {l__robExt_missing_figure_\l__robExt_current_compilation_command_str :}{}{
\message{[robExt] Fallback to manual mode: please, manually compile the images using \l__robExt_current_compilation_command_str or run 'bash \jobnameNoQuotes-\robExtAddPrefixName{compile-missing-figures.sh}'.}
\ifdefined\robExtDoNotRedirectOutputInManuallyCompileMissingFigures%
\iow_now:Nx \g__robExt_write_manually_compile_all_missing_figures_iow {\l__robExt_current_compilation_command_str}% a new line is automatically added
\else
\iow_now:Nx \g__robExt_write_manually_compile_all_missing_figures_iow {(~\l__robExt_current_compilation_command_str~)~>~\robExtAddCachePathAndName{\robExtFinalHash-compilation.log}~2>&1}% a new line is automatically added
\fi
\cs_gset:cn {l__robExt_missing_figure_\l__robExt_current_compilation_command_str :} {}
\ifdefined\robExt@compile@parallel@after
\gdef\robExt@compile@parallel@must@compile{}
\seq_gput_right:Nx \g__robExt_files_compiled_in_parallel {\robExtAddCachePathAndName{\robExtFinalHash}}
\fi
}
\else
\ifdefined\robExtPrintSourceWhenSaving
\typeout{Compilation~command~that~we~would~have~run:^^J\l__robExt_current_compilation_command_str^^J}
\fi
\msg_error:nn{robExt}{need shell escape}
\fi
}
\fi
}
}
\robExtSetPlaceholder*{__ROBEXT_INCLUDE_COMMAND__}{%
\ifdefined\robExtDepth%
\raisebox{-\robExtDepth}{%
\includegraphics[__ROBEXT_INCLUDEGRAPHICS_OPTIONS__]{%
__ROBEXT_INCLUDEGRAPHICS_FILE__%
}}%
\else%
\includegraphics[__ROBEXT_INCLUDEGRAPHICS_OPTIONS__]{%
%\robExtAddCachePathAndName{\robExtFinalHash.pdf}%
__ROBEXT_INCLUDEGRAPHICS_FILE__%
}%
\fi
}
% These special placeholders are not loaded before for efficiency reasons.
\NewDocumentCommand{\robExtLoadSpecialPlaceholders}{}{
\robExtPlaceholderFromString {__ROBEXT_OUTPUT_PREFIX__}{\l_tmp_output_prefix_str}
\robExtPlaceholderFromStringExpanded {__ROBEXT_SOURCE_FILE__}{\l_tmp_output_prefix_str .tex}
\robExtPlaceholderFromStringExpanded {__ROBEXT_OUTPUT_PDF__}{\l_tmp_output_prefix_str .pdf}
\robExtPlaceholderFromStringExpanded {__ROBEXT_WAY_BACK__}{\robExtCacheFolderWayBack}
\robExtPlaceholderFromStringExpanded {__ROBEXT_CACHE_FOLDER__}{\robExtCacheFolder}
}
\def\robExtIncludeGraphicsArgs{}
%%% This command is not meant to be called by the end user. It will be called after the compilation to include
%%% the compiled file back into the original file.
\NewDocumentCommand{\robExtIncludeFile}{m}{%
% We can disable this loading for efficiency reasons
\ifdefined\robExtDoNotLoadSpecialPlaceholders\else\robExtLoadSpecialPlaceholders\fi%
\ifdefined\robExtIncludeCommandAdvanced%
\robExtIncludeCommandAdvanced%
\else%
{%
\file_if_exist:xTF{\robExtAddCachePathAndName{\robExtFinalHash.pdf}}{%
\file_if_exist:xTF{\robExtAddCachePathAndName{\robExtFinalHash-out.tex}}{%
\kern0pt%Without the kern, the next unskip would eat spaces before... and we don't want that. See also
% https://tex.stackexchange.com/questions/104034/when-is-it-good-practice-to-use-unskip
\input{\robExtAddCachePathAndName{\robExtFinalHash-out.tex}}\unskip% Otherwise if the file contains space it will be added here.
}{}%
%\robExtConfigure{run ~ before ~ include ~ command}%
\ifdefined\robExtIncludeCommand%
\robExtIncludeCommand%
\else%
\robExtEvalPlaceholderStartFromList{__ROBEXT_INCLUDE_COMMAND__,__ROBEXT_INCLUDEGRAPHICS_OPTIONS__,__ROBEXT_INCLUDEGRAPHICS_FILE__,__ROBEXT_LATEX_TRIM_LENGTH__}{__ROBEXT_INCLUDE_COMMAND__}%
\fi%
%\robExtConfigure{run ~ after ~ include ~ command}%
}{
% Check if we tried to run a compilation command at the previous step:
\cs_if_exist:NTF \l__robExt_I_just_ran_a_compilation_command: {
% We ran a compilation command but it failed: we print an error message
% We first check if an error file has been produced:
\file_if_exist:nTF {\robExtAddCachePathAndName{\robExtFinalHash-compilation.log}} {
% We print the error twice as some editor might display only the first error while in others
% it might be easier to see the last error. For instance, in emacs if we do not do that
% then it first prints the error message in a file in the cache (but it forgets the
% robustExternalize prefix)
% But first we get the lines containing the word error:
\robExt_get_errors_from_file:n {\robExtAddCachePathAndName{\robExtFinalHash-compilation.log}}%
% We print the message before the log because otherwise emacs is confused and tries to open the
% auxiliary cached file, and I don't want that.
\ifdefined\robExtHideFirstErrorMessage\else
\msg_error:nnxxxx{robExt}{missing compiled pdf parallel with log}{\robExtAddCachePathAndName{\robExtFinalHash}}{\msg_line_number:}{\l__robExt_current_compilation_command_str}{below~(you~might~need~to~press~ENTER~to~go~to~the~next~error)}
\fi
\message{--------~We~print~now~the~full~log~--------^^J}
\robExt_write_file_in_log:n {\robExtAddCachePathAndName{\robExtFinalHash-compilation.log}}%
\message{--------~End~of~the~full~log~--------^^J}
\msg_error:nnxxxx{robExt}{missing compiled pdf parallel with log}{\robExtAddCachePathAndName{\robExtFinalHash}}{\msg_line_number:}{\l__robExt_current_compilation_command_str}{above}
}{
\msg_error:nnxxx{robExt}{missing compiled pdf parallel}{\robExtAddCachePathAndName{\robExtFinalHash}}{\msg_line_number:}{\l__robExt_current_compilation_command_str}
}%
} { % We ran no compilation command: we print instead a placeholder depending on the context:
\cs_if_exist:NTF \robExt@compile@parallel@must@compile {
% we are supposed to compile the current picture in parallel, let us check if it will be possible:
\bool_if:nTF { \sys_if_shell_unrestricted_p: || \cs_if_exist_p:N \robExtForceCompilation} {
\robExtImagePlaceholderIfParallelCompilation
}{
\robExtImagePlaceholderIfManualMode
}
}{
\cs_if_exist:NTF \robExtManualMode {
\robExtImagePlaceholderIfManualMode
\message{[robExt] ~ You ~ are ~ in ~ manual ~ mode: ~ please ~ compile ~ yourself ~ \robExtAddCachePathAndName{\robExtFinalHash.tex} ~ or ~ use ~ the ~ bash ~ \jobnameNoQuotes-\robExtAddPrefixName{compile-missing-figures.sh}}
}{
\robExtImagePlaceholderIfFallbackMode
\message{[robExt] ~ You ~ are ~ falling ~ back ~ to ~ manual ~ mode: ~ please ~ compile ~ yourself ~ \robExtAddCachePathAndName{\robExtFinalHash.tex} ~ or ~ use ~ the ~ bash ~ \jobnameNoQuotes-\robExtAddPrefixName{compile-missing-figures.sh}}
}
}
}
}%
}%
\fi%
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Disabling externalization
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% It seems that when we disable externalization on \tikz, \tikz internally call \tikzpicture in a
% weird way (certainly some tikz magic), and as a result it does not manage to grab the end of the CacheMe
% environment. For this reason, by default, |disable externalization| will disable **all** commands
\seq_clear_new:N \l_commands_to_reset_seq % List of commands to reset by default when disable externalization is set
\NewDocumentCommand{\robExtAddToCommandResetList}{m}{
\seq_put_right:Nn \l_commands_to_reset_seq {#1}
}
\NewDocumentCommand{\robExtSetCommandResetList}{m}{
\seq_set_from_clist:Nn \l_commands_to_reset_seq {#1}
}
\seq_clear_new:N \l_environments_to_reset_seq % List of environments to reset by default when disable externalization is set
\NewDocumentCommand{\robExtAddToEnvironmentResetList}{m}{
\seq_put_right:Nn \l_environments_to_reset_seq {#1}
}
\NewDocumentCommand{\robExtSetEnvironmentResetList}{m}{
\seq_set_from_clist:Nn \l_environments_to_reset_seq {#1}
}
\NewDocumentCommand{\robExtDisableTikzpictureOverwrite}{}{%
\ifdefined\robExtTikzPictureOrig%
\let\tikzpicture\robExtTikzPictureOrig%
\let\endtikzpicture\endrobExtTikzPictureOrig%
\fi%
\ifdefined\robExtEnvironmentOrigName%
\expanded{\noexpand\DeclareEnvironmentCopy{\robExtEnvironmentOrigName}{robExtEnvironmentOrig\robExtEnvironmentOrigName}}%
\fi%
\ifdefined\robExtCommandOrigName%
\expandafter\DeclareCommandCopy\csname \robExtCommandOrigName\expandafter\endcsname\csname robExtCommandOrig\robExtCommandOrigName\endcsname%
\fi%
\ifdefined\robExtDoNotResetAllCommands\else%
\seq_map_inline:Nn \l_commands_to_reset_seq {
\cs_if_exist:cTF { robExtCommandOrig##1 } {
\expandafter\DeclareCommandCopy\csname ##1\expandafter\endcsname\csname robExtCommandOrig##1\endcsname%
} {}
}
\seq_map_inline:Nn \l_environments_to_reset_seq {
\cs_if_exist:cTF { robExtEnvironmentOrig##1 } {
\expanded{\noexpand\DeclareEnvironmentCopy{##1}{robExtEnvironmentOrig##1}}
} {}
}
\fi%
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Automatically forward elements
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% \robExtSetCodeToRunIfMacroPresent{\hello}{some code} will execute "some code" if \hello is present in
% the main content to build and if "auto forward" is enabled.
\NewDocumentCommand{\robExtSetCodeToRunIfMacroPresent}{mm}{
% we need to remove the leading space
% \str_set:Nx \l__robExt_tmp_str {\cs_to_str:N #1}
\expandafter\def \csname l__robExt_execute_if_macro_present\string#1\endcsname {#2}%
}
% \robExtRunCodeToRunForMacroPresent{\hello} will run the code that was configured when calling
% \robExtSetCodeToRunIfMacroPresent{\hello}{some code}
\NewDocumentCommand{\robExtRunCodeToRunForMacroPresent}{m}{
% \str_set:Nx \l__robExt_tmp_str {\cs_to_str:N ##1}
\cs_if_exist_use:c {l__robExt_execute_if_macro_present\string#1}
}
% \autoForwardMacro{\hello}[\firstMacroDeps,\secondMacroDeps] will be used to tell to "auto forward" to forward \hello, \firstMacroDeps, and \secondMacroDeps if \hello is present in the main content to build.
\NewDocumentCommand{\robExtConfigIfMacroPresent}{mm}{%
\robExtSetCodeToRunIfMacroPresent{#1}{%
\pgfkeysalso{#2}%
}%
}
\let\configIfMacroPresent\robExtConfigIfMacroPresent
\NewDocumentCommand{\robExtAutoForwardMacro}{mO{}}{%
\robExtConfigIfMacroPresent{#1}{
forward=#1,#2
}%a
}
\let\autoForwardMacro\robExtAutoForwardMacro
\NewDocumentCommand{\robExtLoadAutoForwardMacroConfig}{+m}{
%\str_set:Nx \l__robExt_tmp_str {\cs_to_str:N #1}
\cs_if_exist:cTF {l__robExt_execute_if_macro_present\string#1}{
% if the macro is present twice, we want to run \forward only once
\cs_if_exist:cTF {l__robExt_execute_if_macro_present_already_forwarded\string#1 :}{}{
\use:c {l__robExt_execute_if_macro_present\string#1}
% define it so that we do not import twice next time
\cs_set:cx {l__robExt_execute_if_macro_present_already_forwarded\string#1 :} {}
}
}{}
}
%% Old version: too inneficient
% \regex_const:Nn \l__robExt_macro_regex { \\[A-Za-z]+ }
% \NewDocumentCommand{\robExtAutoForward}{}{
% \seq_clear_new:N \l__robExt_matches_seq
% \regex_extract_all:NVN \l__robExt_macro_regex \l__robExt_placeholder___ROBEXT_MAIN_CONTENT_ORIG___str \l__robExt_matches_seq%
% \seq_map_inline:Nn \l__robExt_matches_seq {
% \robExtLoadAutoForwardMacroConfig{##1}
% }
% }
%% "auto forward"
\NewDocumentCommand{\robExtAutoForward}{}{
% some code will automatically add auto forward, but we don't want to run it twice.
\robExtDebugInfo{Running auto forward.}
\ifdefined\robExt@already@ran@autoforward\robExtDebugInfo{Auto forward already ran}\else
\ifdefined\robExtUserInputCacheMe\else
\msg_warning:nn{robExt}{auto forward not in cachemecode}
\tl_set_rescan:NnV \robExtUserInputCacheMe {} \l__robExt_placeholder___ROBEXT_MAIN_CONTENT_ORIG___str
\fi
\robExt@normalBraces\robExtUserInputCacheMe
\expandafter\robExt@getfromstringA\robExtUserInputCacheMe\robExt@getfromstringA%\robExt@getfromstringA is added to know when to stop
\def\robExt@already@ran@autoforward{}%
\fi
}
% https://tex.stackexchange.com/questions/700834/efficiently-program-search-of-macro-in-string/700844
\long\def\robExt@getfromstringA#1{\ifx#1\robExt@getfromstringA \else
%\ifcsname L:\string#1\endcsname \addto\listmacros{#1}\fi
\robExtLoadAutoForwardMacroConfig{#1}%
\expandafter\robExt@getfromstringA\fi
}
\def\robExt@normalBraces#1{{\catcode`{=12 \catcode`}=12 \everyeof={\endfile}%
\expandafter\robExt@normalBracesA\expandafter#1\scantokens\expandafter{#1}}}
\long\def\robExt@normalBracesA#1#2\endfile{\gdef#1{#2}}
%%% To automatically define and forward
\NewDocumentCommand{\newcommandAutoForward}{moomO{}}{
\IfNoValueTF{#2}{
\IfNoValueTF{#3}{
\newcommand{#1}{#4}
}{
\newcommand{#1}[#3]{#4}
}
}{
\IfNoValueTF{#3}{
\newcommand{#1}[#2]{#4}
}{
\newcommand{#1}[#2][#3]{#4}
}
}
\robExtAutoForwardMacro{#1}[#5]
}
%%% To automatically define and forward
\NewDocumentCommand{\renewcommandAutoForward}{moomO{}}{
\IfNoValueTF{#2}{
\IfNoValueTF{#3}{
\renewcommand{#1}{#4}
}{
\renewcommand{#1}[#3]{#4}
}
}{
\IfNoValueTF{#3}{
\renewcommand{#1}[#2]{#4}
}{
\renewcommand{#1}[#2][#3]{#4}
}
}
\robExtAutoForwardMacro{#1}[#5]
}
%%% To automatically define and forward
\NewDocumentCommand{\providecommandAutoForward}{moomO{}}{
\IfNoValueTF{#2}{
\IfNoValueTF{#3}{
\providecommand{#1}{#4}
}{
\providecommand{#1}[#3]{#4}
}
}{
\IfNoValueTF{#3}{
\providecommand{#1}[#2]{#4}
}{
\providecommand{#1}[#2][#3]{#4}
}
}
\robExtAutoForwardMacro{#1}[#5]
}
\NewDocumentCommand{\NewDocumentCommandAutoForward}{mmO{}m}{
\NewDocumentCommand{#1}{#2}{#4}
\robExtAutoForwardMacro{#1}[#3]
}
\NewDocumentCommand{\RenewDocumentCommandAutoForward}{mmO{}m}{
\RenewDocumentCommand{#1}{#2}{#4}
\robExtAutoForwardMacro{#1}[#3]
}
\NewDocumentCommand{\ProvideDocumentCommandAutoForward}{mmO{}m}{
\ProvideDocumentCommand{#1}{#2}{#4}
\robExtAutoForwardMacro{#1}[#3]
}
\NewDocumentCommand{\DeclareDocumentCommandAutoForward}{mmO{}m}{
\DeclareDocumentCommand{#1}{#2}{#4}
\robExtAutoForwardMacro{#1}[#3]
}
\NewDocumentCommand{\NewExpandableDocumentCommandAutoForward}{mmO{}m}{
\NewExpandableDocumentCommand{#1}{#2}{#4}
\robExtAutoForwardMacro{#1}[#3]
}
\NewDocumentCommand{\RenewExpandableDocumentCommandAutoForward}{mmO{}m}{
\RenewExpandableDocumentCommand{#1}{#2}{#4}
\robExtAutoForwardMacro{#1}[#3]
}
\NewDocumentCommand{\ProvideExpandableDocumentCommandAutoForward}{mmO{}m}{
\ProvideExpandableDocumentCommand{#1}{#2}{#4}
\robExtAutoForwardMacro{#1}[#3]
}
\NewDocumentCommand{\DeclareExpandableDocumentCommandAutoForward}{mmO{}m}{
\DeclareExpandableDocumentCommand{#1}{#2}{#4}
\robExtAutoForwardMacro{#1}[#3]
}
\NewDocumentCommand{\defAutoForward}{mO{}mO{}}{
\def#1#2{#3}%
\robExtAutoForwardMacro{#1}[#4]%
}
% \robExtGenericAutoForward[tikz][namespace]{string to match}[additional style to run]{
% code to run in cached file if string matches, and to always run in current folder if not using
% the star version
% }
\ExplSyntaxOff
\NewDocumentCommand{\definecolorAutoForward}{mmmO{}}{%
\definecolor{#1}{#2}{#3}%
\robExtConfigure{if matches word={#1}{forward color=#1,#4}}%
}
\NewDocumentCommand{\colorletAutoForward}{mmO{}}{%
\colorlet{#1}{#2}%
\robExtConfigure{if matches word={#1}{forward color=#1,#3}}%
}
\NewDocumentCommand{\robExtRunHereAndInPreambleOfCachedFiles}{O{latex}m}{%
#2%
\robExtConfigure{%
add to preset={#1}{%
add to preamble={#2},
},%
}%
}
\let\runHereAndInPreambleOfCachedFiles\robExtRunHereAndInPreambleOfCachedFiles
\NewDocumentCommand{\robExtGenericAutoForward}{sO{latex}O{}mO{}m}{%
\IfBooleanTF{#1}{}{#6}% We run the code right now
\robExt@set@hash@robust\zxTmpMacro{#6}%
\expanded{%
\noexpand\robExtConfigure{%
register word with namespace={#3}{#4}{%
/utils/exec={%
\noexpand\robExtAddBeforePlaceholder*{__ROBEXT_MAIN_CONTENT__}{%
\expandonce{\zxTmpMacro}%
}%
},%
#5%
},%
add to preset={#2}{%
auto forward words namespace={#3},%
}%
}%
}%
}
\let\genericAutoForward\robExtGenericAutoForward
% uses "if matches" instead of "if matches word"
\NewDocumentCommand{\robExtGenericAutoForwardStringMatch}{sO{latex}mO{}m}{%
\IfBooleanTF{#1}{}{#5}% We run the code right now
\robExt@set@hash@robust\zxTmpMacro{#5}%
\robExtConfigure{%
add to preset={#2}{%
if matches={#3}{
run command if externalization={
\expanded{%
\noexpand\robExtAddBeforePlaceholder*{__ROBEXT_MAIN_CONTENT__}{%
\expandonce{\zxTmpMacro}%
% #1%%
}%
}%
},#4%
}%
}%
}%
}
\let\genericAutoForwardStringMatch\robExtGenericAutoForwardStringMatch
\ExplSyntaxOn
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Automatically forward (regex-based)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% called by style "if matches".
% \robExtRegexMatches{my regex}{my style} will apply "my style" if "__ROBEXT_MAIN_CONTENT_ORIG__" matches
% "my regex".
\NewDocumentCommand{\robExtIfMatchesRegex}{mm}{
\regex_match:nVTF {#1} \l__robExt_placeholder___ROBEXT_MAIN_CONTENT_ORIG___str {\pgfkeysalso{#2}} {}
}
\NewDocumentCommand{\robExtIfMatchesString}{mmm}{
\str_if_in:NnTF \l__robExt_placeholder___ROBEXT_MAIN_CONTENT_ORIG___str {#1} {\pgfkeysalso{#2}} {\pgfkeysalso{#3}}
}
\NewDocumentCommand{\robExtIfPlaceholderMatchesString}{mmmm}{
\str_if_in:cnTF {l__robExt_placeholder_#1_str} {#2} {#3} {#4}
}
\let\ifPlaceholderMatchesString\robExtIfPlaceholderMatchesString
% \robExtRegisterWord {namespace} {word} {style}
\NewDocumentCommand{\robExtRegisterWord}{mmm}{
% \robExt_register_match_word:nnn {#1} {#2} {\pgfkeysalso{#3}}
\robExtRegisterWordCode{#1}{#2}{\pgfkeysalso{#3}}%
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Automatically forward (word-based)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% For efficiency reasons, we first extract in a single pass all words in the text (a word is basically [A-Za-z]+)
% and run a function on these words
%%% I tried this, but it is really slow (500x slower than if_string_matches, overall 20% increase of
%%% compilation time)
% % \__robExt_auto_forward_words:N \commandToRunOnEachWord \stringToSearchOn
% \cs_set:Nn \__robExt_auto_forward_words:NN {
% % \l_tmpa_str will contain the current word read so far
% \str_set:Nn \l_tmpa_str {}%
% \str_map_inline:Nn #2 {
% % \token_case_charcode:NnTF ##1 {} {} {}
% \__robExt_if_letter:nTF {##1} {
% \str_put_right:Nn \l_tmpa_str {##1}
% }{
% \str_if_empty:NTF \l_tmpa_str { } {
% % if the string is empty, we run the command on the string
% #1 \l_tmpa_str%
% \str_set:Nn \l_tmpa_str {}% we reset its value
% }
% }
% }
% }
% %% \__robExt_if_letter:nTF {char} {true} {false} tests if an element is a letter
% %% https://tex.stackexchange.com/a/700864/116348
% \prg_new_conditional:Npnn \__robExt_if_letter:n #1 { TF }
% {
% \bool_lazy_or:nnTF
% {
% \bool_lazy_and_p:nn
% { \int_compare_p:nNn { `#1 } > { `a - 1 } }
% { \int_compare_p:nNn { `#1 } < { `z + 1 } }
% }
% {
% \bool_lazy_and_p:nn
% { \int_compare_p:nNn { `#1 } > { `A - 1 } }
% { \int_compare_p:nNn { `#1 } < { `Z + 1 } }
% }
% \prg_return_true:
% \prg_return_false:
% }
% % \robExt_register_match_word {namespace that defaults to empty} {word} {code to run if word is present}
% \cs_set:Nn \robExt_register_match_word:nnn {
% \cs_set:cn {l__robExt_execute_if_word_present_#1_#2:} {#3}
% }
% % \robExt_try_to_execute_if_match_word:nn {namespace} {word}
% \cs_set:Nn \robExt_try_to_execute_if_match_word:nn {
% \cs_if_exist:cTF {l__robExt_execute_if_word_present_#1_#2:} {%
% \cs_if_exist:cTF {l__robExt_execute_if_word_present_#1_#2__already_forwarded:}{\message{Already forwarded}}{
% \use:c {l__robExt_execute_if_word_present_#1_#2:}%
% % define it so that we do not import twice next time
% \cs_set:cx {l__robExt_execute_if_word_present_#1_#2__already_forwarded:} {}
% }
% } { }
% }
% \cs_generate_variant:Nn \robExt_try_to_execute_if_match_word:nn { nV }
% \NewDocumentCommand{\robExtAutoForwardWords}{O{}}{
% \cs_set:Nn \__robExt_tmp_fct:N {
% \robExt_try_to_execute_if_match_word:nV {#1} ##1
% }
% \__robExt_auto_forward_words:NN \__robExt_tmp_fct:N \l__robExt_placeholder___ROBEXT_MAIN_CONTENT_ORIG___str
% }
% \robExtAutoForwardWords[namespace]
\NewDocumentCommand{\robExtAutoForwardWords}{m}{%
% needed or the original string will be changed
\let\robExt@tmpString\l__robExt_placeholder___ROBEXT_MAIN_CONTENT_ORIG___str%
\robExt@scanmacro@find@word{#1}\robExt@tmpString%
}
\ExplSyntaxOff
% Thanks a lot wipet for this optimized version!!
% https://tex.stackexchange.com/questions/701351/more-efficient-string-extraction-of-words/701441#701441
\def\robExtWordSeparators{{ };:."?!@+-/*,=\{\}[]\\'()&|~_^<>}
% Not for user, use \robExtAutoForwardWords
% \robExt@scanmacro@find@word{namespace}\stringToParse
\def\robExt@scanmacro@find@word#1#2{%
\def\robExt@namespace{#1}%
% we will change the lccode (basically ascii code) of some characters like +- to delimit what a word is
% supposed to be. Since we do not want to affect other stuff, we put it in a bgroup.
\bgroup \expandafter\robExt@set@to@comma\robExtWordSeparators\relax%
\ifdefined\robExtAdditionalCodeInScanMacroFindWord\robExtAdditionalCodeInScanMacroFindWord\fi%
% This seems to set the string in lower case, so fill or Fill or get triggered. But it is not what we want here.
% \lowercase\expandafter{\expandafter\gdef\expandafter#2\expandafter{#2}}%
\lowercase\expandafter{\expandafter\gdef\expandafter#2\expandafter{#2}}%
\edef#2{\detokenize\expandafter{#2}}%
% \message{\string#2: \meaning#2} % prints the modified format of the scanned macro
\expandafter\egroup%
\expandafter\robExt@wordscan@aux#2,\relax,%
}
\def\robExt@set@to@comma #1{\ifx\relax#1\else \lccode`#1=`, \expandafter\robExt@set@to@comma\fi}
\def\robExt@wordscan@aux#1,{\ifx\relax#1\empty\else%
%\message{{#1}} % prints each scanned "word"
\ifcsname robExt@action@to@run@on@word:\robExt@namespace:#1\endcsname \csname robExt@action@to@run@on@word:\robExt@namespace:#1\endcsname \fi%
\expandafter\robExt@wordscan@aux\fi%
}
%\robExtRegisterWordCode{namespace}{word}{code}
\def\robExtRegisterWordCode#1#2#3{\expandafter\gdef\csname robExt@action@to@run@on@word:\string#1:\string#2\endcsname{#3}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% We create interface into pgfkeys in order to allow easier creation of content via style
\pgfkeys{%
/robExt/.cd,%
% We create a default style that will be loaded (mostly for the user)
default style/.style={},%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Code to create new styles %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% The advantage of this over .append style is that you do not need to double the number of hashes
% don't know if there is a better solution.
% https://tex.stackexchange.com/questions/695432/latex3-latex-doubles-the-number-of-hashes-when-storing-them-in-string/695461
add to preset/.code 2 args={%
\robExtStrSetDoubleHash{\robExtTmpStr}{#2}%
% Sadly, \expanded{\noexpand } does not work, as I get extra {} around the def, creating a group
% so the simpler seems to use this library ^^
\robExtPlaceholderFromString{__ROBEXT_TMP__}{\robExtTmpStr}%
\robExtEvalPlaceholderReplaceFromList{__ROBEXT_TMP__}{%
\pgfkeys{%
/robExt/.cd,
#1/.append style={%
% Some styles run differently if inside a preset or not,
% like if matches word. This macro helps with detecting it.
/utils/exec={\def\robExtCurrentlyDefiningPreset{}},%
__ROBEXT_TMP__%
},%
}%
}%
\robExtRemovePlaceholder{__ROBEXT_TMP__}% let us clean our variables
\let\robExtCurrentlyDefiningPreset\undefined%
},
add before preset/.code 2 args={%
\robExtStrSetDoubleHash{\robExtTmpStr}{#2}%
% Sadly, \expanded{\noexpand } does not work, as I get extra {} around the def, creating a group
% so the simpler seems to use this library ^^
\robExtPlaceholderFromString{__ROBEXT_TMP__}{\robExtTmpStr}%
\robExtEvalPlaceholderReplaceFromList{__ROBEXT_TMP__}{%
\pgfkeys{%
/robExt/.cd,
#1/.prefix style={%
% Some styles run differently if inside a preset or not,
% like if matches word. This macro helps with detecting it.
/utils/exec={\def\robExtCurrentlyDefiningPreset{}},%
__ROBEXT_TMP__%
},%
}%
}%
\robExtRemovePlaceholder{__ROBEXT_TMP__}% let us clean our variables
\let\robExtCurrentlyDefiningPreset\undefined%
},
new preset/.code 2 args={%
\robExtStrSetDoubleHash{\robExtTmpStr}{#2}%
% Sadly, \expanded{\noexpand } does not work, as I get extra {} around the def, creating a group
% so the simpler seems to use this library ^^
\robExtPlaceholderFromString{__ROBEXT_TMP__}{\robExtTmpStr}%
% \robExtShowPlaceholder*{__ROBEXT_TMP__}
\robExtEvalPlaceholderReplaceFromList{__ROBEXT_TMP__}{%
\pgfkeys{%
/robExt/.cd,%
#1/.style={%
% Some styles run differently if inside a preset or not,
% like if matches word. This macro helps with detecting it.
/utils/exec={\def\robExtCurrentlyDefiningPreset{}},%
__ROBEXT_TMP__%
},%
}%
}%
\robExtRemovePlaceholder{__ROBEXT_TMP__}% let us clean our variables
\let\robExtCurrentlyDefiningPreset\undefined%
},
% new compiled preset={latex compiled}{
% code to compile in order to produce the expected strings
% }{
% code to initialize
% }
compile latex template/.style={
% to allow later addition to the preamble, we add a dummy value that we replace in a few lines
% same for main content and content orig that we do not want to replace.
add to placeholder={__ROBEXT_LATEX_PREAMBLE__}{__ROBEXT_LATEX_PREAMBLE_DUMMY__},
set placeholder={__ROBEXT_MAIN_CONTENT__}{__ROBEXT_MAIN_CONTENT_DUMMY__},
set placeholder={__ROBEXT_MAIN_CONTENT_ORIG__}{__ROBEXT_MAIN_CONTENT_ORIG_DUMMY__},
/utils/exec={%
\robExtGetNearlyFinalValueTemplateAndCompilationCommand%
\robExtSetBackCompilationCommandAndTemplate%
},
set placeholder={__ROBEXT_LATEX_PREAMBLE_DUMMY__}{__ROBEXT_LATEX_PREAMBLE__},
set placeholder={__ROBEXT_MAIN_CONTENT_DUMMY__}{__ROBEXT_MAIN_CONTENT__},
set placeholder={__ROBEXT_MAIN_CONTENT_ORIG_DUMMY__}{__ROBEXT_MAIN_CONTENT_ORIG__},
set placeholder rec replace from list={__ROBEXT_TEMPLATE__,__ROBEXT_LATEX_PREAMBLE_DUMMY__,__ROBEXT_MAIN_CONTENT_DUMMY__,__ROBEXT_MAIN_CONTENT_ORIG_DUMMY__}{__ROBEXT_TEMPLATE__}{__ROBEXT_TEMPLATE__},
% For the include command
set placeholder rec replace from list={__ROBEXT_INCLUDE_COMMAND__,__ROBEXT_INCLUDEGRAPHICS_OPTIONS__,__ROBEXT_INCLUDEGRAPHICS_FILE__,__ROBEXT_LATEX_TRIM_LENGTH__}{__ROBEXT_INCLUDE_COMMAND__}{__ROBEXT_INCLUDE_COMMAND__},
},
keep placeholder after group/.code={\robExtKeepPlaceholderAfterGroup{#1}},
new compiled preset/.code n args={3}{
\robExtStrSetDoubleHash{\robExtCodeToCompileStr}{#2}%
\robExtStrSetDoubleHash{\robExtCodeToRunStr}{#3}%
% Sadly, \expanded{\noexpand } does not work, as I get extra {} around the def, creating a group
% so the simpler seems to use this library ^^
\robExtPlaceholderFromString{__ROBEXT_TMP_CODE_TO_COMPILE__}{\robExtCodeToCompileStr}%
\robExtPlaceholderFromString{__ROBEXT_TMP_CODE_TO_RUN__}{\robExtCodeToRunStr}%
\robExtPlaceholderFromString{__ROBEXT_NAME_TEMPLATE_PREFIX__}{\robExtCodeToRunStr}%
% \robExtShowPlaceholder*{__ROBEXT_TMP__}
\robExtEvalPlaceholderReplaceFromList{__ROBEXT_TMP_CODE_TO_COMPILE__,__ROBEXT_TMP_CODE_TO_RUN__}{%
\robExtConfigure{%
#1-recompile/.code={%
\begingroup%
\robExtConfigure{
__ROBEXT_TMP_CODE_TO_COMPILE__,
}%
\robExtCopyPlaceholder*{__ROBEXT_COMPILED_#1_TEMPLATE__}{__ROBEXT_TEMPLATE__}%
\robExtKeepPlaceholderAfterGroup{__ROBEXT_COMPILED_#1_TEMPLATE__}%
\robExtCopyPlaceholder*{__ROBEXT_COMPILED_#1_COMPILATION_COMMAND__}{__ROBEXT_COMPILATION_COMMAND__}%
\robExtKeepPlaceholderAfterGroup{__ROBEXT_COMPILED_#1_COMPILATION_COMMAND__}%
\robExtCopyPlaceholder*{__ROBEXT_COMPILED_#1_INCLUDE_COMMAND__}{__ROBEXT_INCLUDE_COMMAND__}%
\robExtKeepPlaceholderAfterGroup{__ROBEXT_COMPILED_#1_INCLUDE_COMMAND__}%
\robExtRescanPlaceholderInVariableNoReplacement{robExtCompiledIncludeCommand#1}{__ROBEXT_INCLUDE_COMMAND__}%
\robExtKeepaftergroup{robExtCompiledIncludeCommand#1}%
\robExtKeepaftergroup{l__robExt_placeholder___ROBEXT_COMPILED_#1_INCLUDE_COMMAND___str}%
\robExtKeepaftergroup{l__robExt_placeholder___ROBEXT_COMPILED_#1_TEMPLATE___str}%
\robExtSaveDependencies{robExtCompiledDependencies#1}%
\endgroup%
},
#1-recompile,
#1/.style={
disable placeholders,
set placeholder={__ROBEXT_LATEX_PREAMBLE__}{},
add to preamble/.style={
add to placeholder={__ROBEXT_LATEX_PREAMBLE__}{####1},
},
/utils/exec={\robExtRestoreDependencies{robExtCompiledDependencies#1}},
custom include command={\csname robExtCompiledIncludeCommand#1\endcsname},
copy placeholder no import={__ROBEXT_TEMPLATE__}{__ROBEXT_COMPILED_#1_TEMPLATE__},
copy placeholder no import={__ROBEXT_COMPILATION_COMMAND__}{__ROBEXT_COMPILED_#1_COMPILATION_COMMAND__},
__ROBEXT_TMP_CODE_TO_RUN__
},%
}%
}%
\robExtRemovePlaceholder{__ROBEXT_TMP_CODE_TO_RUN__}% let us clean our variables
\robExtRemovePlaceholder{__ROBEXT_TMP_CODE_TO_COMPILE__}% let us clean our variables
},
in command/.style={
set placeholder={__ROBEXT_MAIN_CONTENT__}{__ROBEXT_MAIN_CONTENT_ORIG__},
},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Interface to change placeholders %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
remove placeholder/.code={\robExtRemovePlaceholder{#1}},
remove placeholders/.style={
remove placeholder/.list={#1},
},
set main content/.style={
set placeholder={__ROBEXT_MAIN_CONTENT_ORIG__}{#1}
},
copy placeholder/.code 2 args={\robExtCopyPlaceholder{#1}{#2}},
copy placeholder no import/.code 2 args={\robExtCopyPlaceholder*{#1}{#2}},
set placeholder/.code 2 args={\robExtSetPlaceholder{#1}{#2}},
% do not import the placeholder (useful for efficiency reasons)
set placeholder no import/.code 2 args={\robExtSetPlaceholder*{#1}{#2}},
set placeholder first/.code 2 args={\robExtSetPlaceholderFirst*{#1}{#2}},
set placeholder rec/.code 2 args={\robExtSetPlaceholderRec{#1}{#2}},
set placeholder rec keep after group/.code 2 args={\robExtSetPlaceholderRec{#1}{#2}\robExtKeepPlaceholderAfterGroup{#1}},
set placeholder rec no import/.code 2 args={\robExtSetPlaceholderRec*{#1}{#2}},
set placeholder rec replace from list/.code n args={3}{\robExtSetPlaceholderRecReplaceFromList{#1}{#2}{#3}},
set placeholder rec replace from list no import/.code n args={3}{\robExtSetPlaceholderRecReplaceFromList*{#1}{#2}{#3}},
set placeholder eval/.code 2 args={\robExtSetPlaceholderRec{#1}{#2}\robExtEvalPlaceholderInplace{#1}},
set placeholder eval no import/.code 2 args={\robExtSetPlaceholderRec*{#1}{#2}\robExtEvalPlaceholderInplace{#1}},
set placeholder eval replace from list/.code n args={3}{\robExtSetPlaceholderRecReplaceFromList{#1}{#2}{#3}\robExtEvalPlaceholderInplace{#2}},
set placeholder eval replace from list no import/.code n args={3}{\robExtSetPlaceholderRecReplaceFromList*{#1}{#2}{#3}\robExtEvalPlaceholderInplace{#2}},
eval placeholder/.code={\robExtEvalPlaceholder{#1}},
eval placeholder replace from list/.code 2 args={\robExtEvalPlaceholderReplaceFromList{#1}{#2}},
set placeholder from content/.code 2 args={\robExtPlaceholderFromContent{#1}{#2}},
set placeholder from content no import/.code 2 args={\robExtPlaceholderFromContent*{#1}{#2}},
add to placeholder/.code 2 args={\robExtAddToPlaceholder{#1}{#2}},
add to placeholder no import/.code 2 args={\robExtAddToPlaceholderNoImport{#1}{#2}},
add to placeholder no space/.code 2 args={\robExtAddToPlaceholder*{#1}{#2}},
add to placeholder no space no import/.code 2 args={\robExtAddToPlaceholderNoImport*{#1}{#2}},
add before placeholder/.code 2 args={\robExtAddBeforePlaceholder{#1}{#2}},
add before placeholder no space/.code 2 args={\robExtAddBeforePlaceholder*{#1}{#2}},
add before placeholder no import/.code 2 args={\robExtAddBeforePlaceholderNoImport{#1}{#2}},
add before placeholder no space no import/.code 2 args={\robExtAddBeforePlaceholderNoImport*{#1}{#2}},
set placeholder path from filename/.code 2 args={\robExtPlaceholderPathFromFilename{#1}{#2}},
set placeholder path from filename no import/.code 2 args={\robExtPlaceholderPathFromFilename*{#1}{#2}},
set placeholder from file content/.code 2 args={\robExtPlaceholderFromFileContent{#1}{#2}},
set placeholder from file content no import/.code 2 args={\robExtPlaceholderFromFileContent*{#1}{#2}},
set placeholder path from content/.code n args={3}{\robExtPlaceholderPathFromContent{#1}[#3]{#2}},
set placeholder path from content no import/.code n args={3}{\robExtPlaceholderPathFromContent*{#1}[#3]{#2}},
eval placeholder in place/.code={\robExtEvalPlaceholderInplace{#1}},
placeholder halve number hashes in place/.code={\robExtPlaceholderHalveNumberHashesInplace{#1}},
placeholder double number hashes in place/.code={\robExtPlaceholderDoubleNumberHashesInplace{#1}},
placeholder replace in place/.code n args={3}{\robExtPlaceholderReplaceInplace{#1}{#2}{#3}},
placeholder replace in place eval/.code n args={3}{\robExtPlaceholderReplaceInplaceEval{#1}{#2}{#3}},
placeholder prepend all lines/.code 2 args={\robExtPlaceholderPrependAllLines{#1}{#2}},
prepend all lines/.style={
placeholder prepend all lines={\robExtCurrentPlaceholderName}{#1},
},
placeholder remove spaces until/.code 2 args={\robExtPlaceholderRemoveSpacesUntil{#1}{#2}},
remove spaces until/.style={
placeholder remove spaces until={\robExtCurrentPlaceholderName}{#1},
},
placeholder strictly remove spaces until/.code 2 args={\robExtPlaceholderRemoveSpacesUntil{#1}[0]{#2}},
strictly remove spaces until/.style={
placeholder remove spaces until nospace={\robExtCurrentPlaceholderName}{#1},
},
placeholder remove leading spaces/.code={\robExtPlaceholderRemoveLeadingSpaces{#1}},
remove leading spaces/.code={\robExtPlaceholderRemoveLeadingSpaces{\robExtCurrentPlaceholderName}},
remove leading spaces if not disabled/.code={\ifdefined\robExtDoNotRemoveLeadingSpaces\else\robExtPlaceholderRemoveLeadingSpaces{\robExtCurrentPlaceholderName}\fi},
do not remove leading spaces/.code={\def\robExtDoNotRemoveLeadingSpaces{}},
% Interface to set template
set template/.style={
set placeholder first={__ROBEXT_TEMPLATE__}{#1},
},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Interface for optimization %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% It is important to create an efficient code (otherwise the library can becomes pointless)
% and it can help a lot if we give some advices to the system that evaluates placeholders, notably on
% the replacement order it should follow:
%%%% "only placeholders" says "use only these placeholders in this template/compilation command/... in that order
%%%% while "first placeholders" says "start with these placeholders, if it is not enough, continue as usual
only placeholders in compilation command/.code={\robExtOnlyPlaceholdersInCompilationCommand{#1}},
first placeholders in compilation command/.code={\robExtFirstPlaceholdersInCompilationCommand{#1}},
only placeholders in template/.code={\robExtOnlyPlaceholdersInTemplate{#1}},
first placeholders in template/.code={\robExtFirstPlaceholdersInTemplate{#1}},
only placeholders in include command/.code={\robExtOnlyPlaceholdersInIncludeCommand{#1}},
first placeholders in include command/.code={\robExtFirstPlaceholdersInIncludeCommand{#1}},
%%%%%%%%%%%%%
%%% Debug %%%
%%%%%%%%%%%%%
more logs/.code={\def\robExtDebugMessage##1{\message{^^J[robExt] ##1}}\def\robExtDebugInfo##1{^^J[robExt] ##1}},
less logs/.code={\def\robExtDebugMessage##1{}\def\robExtDebugInfo##1{}},
show placeholder/.code={\robExtShowPlaceholder{#1}},
show placeholders/.code={\robExtShowPlaceholders},
show placeholders contents/.code={\robExtShowPlaceholdersContents},
print imported placeholders except default/.code={\robExtPrintAllPlaceholdersExceptDefaults},
print imported placeholders/.code={\robExtPrintAllPlaceholders},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Optimizations %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
do not load special placeholders/.code={\let\robExtDoNotLoadSpecialPlaceholders\undefined},
load special placeholders/.code={\def\robExtDoNotLoadSpecialPlaceholders{}},
do not load special placeholders,
%% Replace very little placeholders, basically only __ROBEXT_MAIN_CONTENT__, __ROBEXT_MAIN_CONTENT_ORIG__,
%% and the default (prefix, source, output, wayback, cache folder)
disable placeholders/.code={\def\robExtDisablePlaceholders{}},
enable placeholders/.code={\let\robExtDisablePlaceholders\undefined},
%% If all placeholders have __, this option will speed up the compilation time
all placeholders have underscores/.code={\def\robExtPlaceholderOnlyWithUnderscores{}},
not all placeholders have underscores/.code={\let\robExtPlaceholderOnlyWithUnderscores\undefined},
%% not sure if I want users to disable import mechanism as they might use dirty things
enable import mechanism/.code={\def\robExtEnableImportMechanism{}},
disable import mechanism/.code={\let\robExtEnableImportMechanism\undefined},
disable optimizations/.style={
not all placeholders have underscores,
disable import mechanism,
},
enable optimizations/.style={
all placeholders have underscores,
enable import mechanism,
},
enable optimizations,
%%%%%%%%%%%%%%
%%% Groups %%%
%%%%%%%%%%%%%%
clear imported placeholders/.code={\robExtClearImportedPlaceholders},
remove imported placeholder/.code={\robExtRemoveImportedPlaceholder{#1}},
remove imported placeholders/.style={
remove imported placeholder/.list={#1},
},
print group placeholders/.code={\robExtPrintGroupPlaceholders{#1}},
new group placeholders/.code={\robExtRegisterGroupPlaceholders{#1}},
add placeholder to group/.code 2 args={\robExtAddPlaceholdersToGroup{#1}{#2}},
add placeholders to group/.code 2 args={\robExtAddPlaceholdersToGroup{#1}{#2}},
remove placeholder from group/.code 2 args={\robExtRemovePlaceholdersFromGroup{#1}{#2}},
remove placeholders from group/.code 2 args={\robExtRemovePlaceholdersFromGroup{#1}{#2}},
copy group placeholders/.code 2 args={\robExtCopyGroupPlaceholders{#1}{#2}},
append group placeholders/.code 2 args={\robExtAppendGroupPlaceholders{#1}{#2}},
append before group placeholders/.code 2 args={\robExtAppendGroupPlaceholders{#1}{#2}},
import placeholders from group/.code={\robExtImportPlaceholdersFromGroup{#1}},
import all placeholders/.code={\robExtImportAllPlaceholders},
% ok this is a different kind of group, here latex group { }
import placeholder/.code={\robExtImportPlaceholder{#1}},
import all placeholders/.code={\robExtImportAllPlaceholders},
import placeholders/.style={
import placeholder/.list={#1},
},
import placeholder first/.code={\robExtImportPlaceholderFirst{#1}},
import placeholders first/.code={\robExtAddPlaceholdersToListFirst{#1}},
import placeholders from group/.code={\robExtImportPlaceholdersFromGroup{#1}},
print all registered groups/.code={\robExtPrintAllRegisteredGroups},
print all registered groups and placeholders/.code={\robExtPrintAllRegisteredGroupsAndPlaceholders},
show all registered groups/.code={\robExtShowAllRegisteredGroupsAndPlaceholders},
show all registered groups and placeholders/.code={\robExtShowAllRegisteredGroupsAndPlaceholders},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Configure dependencies %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Auxiliary command:
dependenciesList/.code={\robExtAddDependency{#1}},
% Usage like: dependencies={input_externalize.tex,input_b.tex}
% They should be relative to the main file when using the subfolder option.
dependencies/.style={
/utils/exec={\robExtResetDependencies{}},
dependenciesList/.list={#1}
},
add dependencies/.style={
dependenciesList/.list={#1}
},
reset dependencies/.code={\robExtResetDependencies{}},
%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Compilation command %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% People might want to force the compilation even if shell_unrestricted is false, notably if they
% allow commands like mkdir/cd/pdflatex to run in restricted mode.
force compilation/.code={\def\robExtForceCompilation{}},
do not force compilation/.code={\let\robExtForceCompilation\undefined},
% Recompile the file even if it has already been compiled (we do NOT clean the file as we do not feel
% safe to remove files from this code, but this can also be done by the user hooking into
recompile/.code={\def\robExtForceRecompilation{}},
do not recompile/.code={\let\robExtForceRecompilation\undefined},
set compilation command/.code={\robExtSetCompilationCommand{#1}},
% like "set compilation command" but moves the "__ROBEXT_OUTPUT_PDF__-tmp" to "__ROBEXT_OUTPUT_PDF__" if
% there is no error.
set compilation command move if no error/.style={
set compilation command/.expanded={#1 && \robExtMv\space"__ROBEXT_OUTPUT_PDF__-tmp" "__ROBEXT_OUTPUT_PDF__"}
},
set compilation command create if no error/.code={\robExtSetCompilationCommand{#1 && echo "" > __ROBEXT_OUTPUT_PDF__}},
do not redirect compilation output/.code={\def\robExtDoNotRedirectOutput{}\def\robExtDoNotRedirectOutputInManuallyCompileMissingFigures{}},
print whole file in error message/.code={\def\robExtPrintWholeFile{}},
do not print whole file in error message/.code={\let\robExtPrintWholeFile\undefined},
prefix log message with/.code={\def\robExtPrefixLogMessage{#1}},
spacing between lines in log/.code={\def\robExtMessageWithPrefixNumberLines{#1}},
% This adds a ! in front of the code if \robExtPrintWholeFile is defined
logs should show as errors/.style={prefix log message with={!\space}},
texstudio/.style={
logs should show as errors,
spacing between lines in log={^^J^^J(sorry, these 4 empty lines are ugly but needed for texstudio to consider them as separate errors, customize me "with spacing between lines in log")^^J},
},
remove line number/.code={\def\robExtRemoveLineNumber{}}, % Removes the l. if the log error line starts with l.42 or it will disturb emacs.
do not remove line number/.code={\let\robExtRemoveLineNumber\undefined},
remove line number,
nb lines after error to show/.code={\def\robExtLinesAfterError{#1}},
add argument to compilation command/.code={\robExtAddArgumentToCompilationCommand{#1}},
add arguments to compilation command/.style={
add argument to compilation command/.list={#1}
},
% This adds arguments like add key value to compilation command={mykey=myvalue} will add to the
% compilation command two arguments: "mykey" "myvalue"
% This is useful for scripts that are called like myscript key1 arg1 key2 arg2 key3 arg3, which is a
% simple way to pass multiple arguments to a script like a python script
add key value argument to compilation command/.code args={#1=#2}{\robExtAddArgumentToCompilationCommand{#1}\robExtAddArgumentToCompilationCommand{#2}},
add key and file argument to compilation command aux/.style args={#1=#2}{
add key value argument to compilation command={{#1}={\ifdefined\robExtCacheFolderWayBack\robExtCacheFolderWayBack\fi#2}},
},
add key and file argument to compilation command/.style={
add key and file argument to compilation command aux/.list={#1},
add dependencies={#1},
},
%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Inclusion command %%%
%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Configure the command to include the compiled file back into the main file
% By default, include command does a bit of logic before running the actual command, notably to
% input the -out.tex file in order to pass information from the compiled file to the current file.
% If you want to do everything by yourself, use:
custom include command advanced/.code={\def\robExtIncludeCommandAdvanced{#1}},
% The default include command includes the pdf, making sure it is raised depending on its depth,
% but you can override it:
custom include command/.code={\def\robExtIncludeCommand{#1}},
include command is input/.style={
custom include command={#1\input{\robExtAddCachePathAndName{\robExtFinalHash.pdf}}},
},
include command is input/.default={},
% Sometimes (gnuplot), we need to include an image located in the cache folder:
% https://tex.stackexchange.com/questions/171587/append-entries-to-an-existing-graphicspath
add cache to graphicspath/.code={%
\appto\Ginput@path{\robExtCacheFolder}%
},%
%% This is needed notably if the cached elements are nested, like the include command uses itself a tikz picture
%% etc cached via \cacheTikz... It it hard to reset everything efficiently (like we might not want to reset
%% all compilation commands etc), so you can add here stuff that might need to be restored later.
reset/.code={%
\let\robExtIncludeCommandAdvanced\undefined%
\let\robExtIncludeCommand\undefined%
\let\robExtPrintWholeFile\undefined%
\setPlaceholder{__ROBEXT_INCLUDEGRAPHICS_FILE__}{\robExtAddCachePathAndName{\robExtFinalHash.pdf}}%
},
%% Use this when we do not want to include anything (e.g. the video will be processed later in the chain):
do not include pdf/.style={
custom include command={}%
},
%% If you do or do not want to ask latex to run the compilation commands itself (for instance for security
%% reasons, you can use these commands and run the command manually later):
enable manual mode/.code={\def\robExtManualMode{}},
disable manual mode/.code={\let\robExtManualMode\undefined},
enable fallback to manual mode/.code={\def\robExtFallbackManualMode{}},
disable fallback to manual mode/.code={\let\robExtFallbackManualMode\undefined},
% For the arxiv, we need to rename the source .tex into .tex-backup or it will be removed
backup source for arxiv/.code={\def\robExtEnableBackupSource{}},
do not backup source for arxiv/.code={\let\robExtEnableBackupSource\undefined},
rename backup files for arxiv/.code={\robExtRenameBackupFilesForArxiv{#1}},
rename backup files for arxiv/.default={robExt-arxiv-files-to-rename.txt},
% print in the log the file. Useful to debug in arxiv where we do not have any access to the logs
print source when saving/.code={\def\robExtPrintSourceWhenSaving{}},
copy file to cache/.code={\robExtCopyFileToCache{#1}},
%% Arguments to include graphics
include graphics args/.code={\def\robExtIncludeGraphicsArgs{#1}},
%% The role of this command is to set \l_robExt_result_str, that will contain the final string.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Configuration of the cache %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% For support of --output-directory
set output directory/.code={\def\robExtOutputDirectory{#1}},
%% Configure the prefix (default to "robExt-")
set filename prefix/.code={\def\robExtPrefixFilename{#1}},
% first argument is subfolder, second is how to get from subfolder to the folder containing the source:
% set subfolder and way back={robustExternal/}{../}
% synonyme, "cache folder" is prefered over ""
set subfolder and way back/.code 2 args={\def\robExtCacheFolder{#1}\def\robExtCacheFolderWayBack{#2}},
set cache folder and way back/.code 2 args={\def\robExtCacheFolder{#1}\def\robExtCacheFolderWayBack{#2}},
no cache folder/.style={set cache folder and way back={}{}},
% By default we put everything in robustExternalize
% Change this before starting to cache any library, and if you change it mid-document, be aware
% that you will not be able to refer to elements in the old folder.
set subfolder and way back={robustExternalize/}{../},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Enable externalization for tikz %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
cache tikz/.code={\robExtExternalizeAllTikzpictures{}},
do not cache tikz/.code={\robExtDoNotExternalizeAllTikzpictures{}},
cache tikz 2 args/.code 2 args={\robExtExternalizeAllTikzpictures[#1][#2]{}},
cache tikz 3 args/.code n args={3}{\robExtExternalizeAllTikzpictures[#1][#2][3]{}},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Disable externalization %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Note: this does not work reliably for now
%% TODO: fix this!
disable externalization/.code={\def\robExtDisableExternalization{}},
de/.style={disable externalization},
disable externalization now/.code={\robExtDisableTikzpictureOverwrite\def\robExtDisableExternalization{}},
enable externalization/.code={\let\robExtDisableExternalization\undefined},
% Useful to wrap, for instance, text
command if no externalization/.code={},
command if no externalization/.code={\robExtDisableTikzpictureOverwrite\evalPlaceholder{__ROBEXT_MAIN_CONTENT__}},
print verbatim if no externalization/.style={
command if no externalization/.code={%
\robExtPrintPlaceholder{__ROBEXT_MAIN_CONTENT__}%
},
},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Forward macros if externalization is enabled %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
command if externalization/.code={\def\robExt@we@are@already@in@command@if@externalization{}},
% Doing "command if externalization/.append code={}" is not always good since we don't always know if the style
% will always be run before running "command if externalization". In particular this arrives when using
% something like "if matches word={mypink}{forward color=mypink}"
run command if externalization/.code={
\ifdefined\robExt@we@are@already@in@command@if@externalization%
% we are already running command if externalization
#1%
\else%
\pgfkeysalso{command if externalization/.append code={#1}}%
\fi%
},
fw/.style={forward=#1},
run code before main content if externalization enabled/.code={},
% run code before main content if externalization enabled/.code={
% \message{aaa #1}
% \def\zx@tmp{#1}%
% \message{xxx}
% \show\zx@tmp%
% \scantokens{%
% \pgfkeysalso{
% command if externalization/.append code={%
% \expanded{%
% \noexpand\robExtAddBeforePlaceholder*{__ROBEXT_MAIN_CONTENT__}{%
% % \expandonce{\zx@tmp}%
% #1%%
% }%
% }%
% }%
% }%
% },
% },
forward/.style={%
run command if externalization={%
\robExtGetCommandDefinitionInMacro{#1}%
\expanded{%
\noexpand\robExtAddBeforePlaceholder*{__ROBEXT_MAIN_CONTENT__}{%
\expandonce{\robExtDefinitionCommand}%
}%
}%
},
},
forward at letter/.style={%
run command if externalization={%
\robExtGetCommandDefinitionInMacro{#1}%
\expanded{%
\noexpand\robExtAddBeforePlaceholder*{__ROBEXT_MAIN_CONTENT__}{%
\noexpand\makeatletter%
\expandonce{\robExtDefinitionCommand}%
\noexpand\makeatother%
}%
}%
},
},
forward eval/.style={%
run command if externalization={%
\expanded{%
\noexpand\robExtAddBeforePlaceholder*{__ROBEXT_MAIN_CONTENT__}{%
\noexpand\def\noexpand#1{#1}%
}%
}%
},%
},
forward counter/.style={%
run command if externalization={%
\expanded{%
\noexpand\robExtAddBeforePlaceholder*{__ROBEXT_MAIN_CONTENT__}{%
\noexpand\makeatletter%
% Make sure the counter exists:
\noexpand\ifcsname c@#1\noexpand\endcsname\noexpand\else\noexpand\newcounter{#1}\noexpand\fi%
\noexpand\setcounter{#1}{\the\value{#1}}%
\noexpand\makeatother
}%
}%
},%
},%
% Useful when we want to force the value of a forwarded counter. Useful for arXiv, e.g.
% if the page number is not the good one.
forward counter force value/.style 2 args={%
run command if externalization={%
\expanded{%
\noexpand\robExtAddBeforePlaceholder*{__ROBEXT_MAIN_CONTENT__}{%
\noexpand\makeatletter%
% Make sure the counter exists:
\noexpand\ifcsname c@#1\noexpand\endcsname\noexpand\else\noexpand\newcounter{#1}\noexpand\fi%
\noexpand\setcounter{#1}{#2}%
\noexpand\makeatother
}%
}%
},%
},%
forward counter back/.style={
add to preamble={
\AtEndDocument{% Execute at end of file: we propagate back the value of the counter
\immediate\write\writeRobExt{\string\setcounter{#1}{\the\value{#1}}}%
}
},
},
forward counter and back/.style={
forward counter=#1,
forward counter back=#1,
},
forward color/.style={
run command if externalization={%
\extractcolorspecs{#1}{\zx@tmp@model}{\zx@tmp@cmd}%
\expanded{%
\noexpand\robExtAddBeforePlaceholder*{__ROBEXT_MAIN_CONTENT__}{%
\noexpand\definecolor{#1}{\zx@tmp@model}{\zx@tmp@cmd}%
}%
}%
},%
},%
%% This will try to automatically forward some macros. For this, you must first define
%%
auto forward only macros/.code={\robExtAutoForward},
auto forward/.code={\robExtAutoForward\pgfkeysalso{auto forward words}},
auto forward color/.style 2 args={%
add to preset={#1}{%
if matches word={#2}{forward color=#2},
},
},
load auto forward macro config/.code={\robExtLoadAutoForwardMacroConfig{#1}},
%% Auto forward words
auto forward words namespace/.code={%
\ifdefined\robExt@autoforward@enabled\else%
\pgfkeysalso{command if externalization/.append code={%
\ifdefined\robExt@autoforward@enabled%
\expanded{\noexpand\robExtAutoForwardWords{\robExt@autoforward@enabled}}%
\fi%
}%
}%
\fi%
\def\robExt@autoforward@enabled{#1}%
},%%% todo: finish
auto forward words/.style={auto forward words namespace={}},
%% This will
if matches regex/.code 2 args={\robExtIfMatchesRegex{#1}{#2}},
if matches/.code 2 args={\robExtIfMatchesString{#1}{#2}{}},
if matches else/.code n args={3}{\robExtIfMatchesString{#1}{#2}{#3}},
if matches word/.code 2 args={%
\robExtRegisterWord{}{#1}{#2}%
% If ran inside a preset, we want to enable it, otherwise we enable it on the latex preset
\ifdefined\robExtCurrentlyDefiningPreset%
\pgfkeysalso{auto forward words}%
\else%
\pgfkeysalso{/robExt/latex/.append style={auto forward words}}%
\fi%
},
% more efficient since we can register the words before the preset, but make sure to call `auto forward words namespace`.
% You can use an empty namespace.
register word with namespace/.code n args={3}{%
\robExtRegisterWord{#1}{#2}{#3}%
},
register word/.style 2 args={%
register word with namespace={}{#1}{#2},%
},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Run code before/after inclusion %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% todo: make sure that commands can be added instead of replaced
execute before each externalization/.code={\def\robExtExecuteBefore{#1}},
execute after each externalization/.code={\def\robExtExecuteAfter{#1}},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Get the name of the produced file for later use %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Here, we provide a way to put the prefixed name into a new global macro
%%% Use like 'name output=VideoA'. This creates a few macros like:
%%% \blenderpointNamedOutputFilenameVideoA containing thehashthatisusedforthename
%%% \blenderpointNamedOutputPrVideoA containing thehashthatisusedforthename
%% See \robExtGetNamedOutputFilename to get them with \robExtGetNamedOutputFullPath
name output/.code={%
\def\robExtExecuteNamedOutput{%
\expandafter\xdef\csname #1\endcsname{\robExtPrefixFilename\robExtFinalHash}%
\expandafter\xdef\csname #1InCache\endcsname{\robExtAddCachePathAndName{\robExtFinalHash}}%
}%
},
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Compile in parallel automatically
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
compile in parallel/.style={compile in parallel after=#1},
compile in parallel/.default=0,
compile in parallel after/.code={%
\gdef\robExt@compile@parallel@after{#1}%
},
compile in parallel after/.default=0,
disable compile in parallel/.code={\let\robExt@compile@parallel@after\undefined},
if windows/.code={%
\robExtIfWindowsTF{\pgfkeysalso{zx@tmp@key/.estyle={\unexpanded{#1}},zx@tmp@key}}{}%
},
if unix/.code={%
\robExtIfWindowsTF{}{\pgfkeysalso{zx@tmp@key/.estyle={\unexpanded{#1}},zx@tmp@key}}%
},
compile in parallel command/.store in=\robExt@compile@parallel@command,
% Different backends to compile in parallel
compile in parallel with gnu parallel/.style={
compile in parallel command={parallel --jobs #1 :::: '\jobnameNoQuotes-\robExtAddPrefixName{compile-missing-figures.sh}'},
},
compile in parallel with gnu parallel/.default={200\%},
compile in parallel with xargs/.style={
% Use " instead of ' as Windows does not consider ' as a valid surrounding for strings
compile in parallel command={xargs -t -I "{}" -P #1 \robExtParallelShell\space "{}" < "\jobnameNoQuotes-\robExtAddPrefixName{compile-missing-figures.sh}"},
},
% With 0 it spawns as many shells at possible, i.e. it compiles all 200 pictures at the same time, making the
% system a laggy, and it is actually *slower* (2:05mn vs 1:15mn) than when using 16 threads.
compile in parallel with xargs/.default={16},
% By default, we compile with xargs and 16 processes:
compile in parallel with xargs,
}
\ExplSyntaxOn
\int_new:N \g__robExt_number_figures_just_compiled_or_to_compile
\int_set:Nn \g__robExt_number_figures_just_compiled_or_to_compile {0}
% Will hold the list of input files to compile in parallel. This is needed to
% check at the end to find the processes that failed to build.
\seq_new:N \g__robExt_files_compiled_in_parallel
\AfterLastShipout{
% Check if we want to run stuff in parallel
\ifdefined\robExt@compile@parallel@after
% Check if we must compile
\ifdefined\robExt@compile@parallel@must@compile
% Check if we can compile
\bool_if:nTF { \sys_if_shell_unrestricted_p: || \cs_if_exist_p:N \robExtForceCompilation}
{
% Check if a command was provided
\ifdefined\robExtParallelShell\else
\sys_if_platform_windows:TF {
\def\robExtParallelShell{cmd~/C}
}{
\def\robExtParallelShell{sh~-c}
}
\fi
% We close the file or reading it is empty
\iow_close:N \g__robExt_write_manually_compile_all_missing_figures_iow
\msg_warning:nnx{robExt}{rerun because parallel}{\robExt@compile@parallel@command}
\message{\robExt@compile@parallel@command}
% Note that we do not save this into a file because it might help to see in real time where we are in
% the compilation process. This also means that we get poorer error message if this specific
% command fails in texstudio since it only prints the log file and not the whole terminal...
% Since this should anyway be quite rare (user has not xargs installed basically), this should be fine.
\sys_shell_now:x {\robExtPrefixAllCompilationCommands\robExt@compile@parallel@command}
% We check if some files failed to compile
\seq_map_inline:Nn \g__robExt_files_compiled_in_parallel {
\file_if_exist:xTF{#1.pdf}{}{
\file_if_exist:nTF {#1-compilation.log} {
% We print the error twice as some editor might display only the first error while in others
% it might be easier to see the last error. For instance, in emacs if we do not do that
% then it first prints the error message in a file in the cache (but it forgets the
% robustExternalize prefix)
% But first we get the lines containing the word error:
\let\robExtPrintWholeFile\undefined
\cs_if_exist:cTF {g__robExt_print_whole_file_#1:} {\def\robExtPrintWholeFile{}} {}
\robExt_get_errors_from_file:n {#1-compilation.log}%
% We print the message before the log because otherwise emacs is confused and tries to open the
% auxiliary cached file, and I don't want that.
\ifdefined\robExtHideFirstErrorMessage\else
\msg_error:nnxxxx{robExt}{missing compiled pdf parallel with log}{#1}{\use:c {g__robExt_lines_error_#1:}}{\use:c {g__robExt_compilation_command_#1:}}{below~(you~might~need~to~press~ENTER~to~go~to~the~next~error)}
\fi
\message{--------~We~print~now~the~full~log~--------^^J}
\robExt_write_file_in_log:n {#1-compilation.log}%
\message{--------~End~of~the~full~log~--------^^J}
\msg_error:nnxxxx{robExt}{missing compiled pdf parallel with log}{#1}{\use:c {g__robExt_lines_error_#1:}}{\use:c {g__robExt_compilation_command_#1:}}{above}
\let\robExtPrintWholeFile\undefined
}{
\msg_error:nnxxx{robExt}{missing compiled pdf parallel}{#1}{\use:c {g__robExt_lines_error_#1:}}{\use:c {g__robExt_compilation_command_#1:}}
}
}
}
} {
\ifdefined\robExtFallbackManualMode
\msg_warning:nn{robExt}{enabled parallel no shell escape}
\else
\msg_error:nn{robExt}{need shell escape parallel}
\fi
}
\fi
\fi
% \robExtBackupSource moves the source .tex into .tex-backup, needed for the arXiv website that removes the
% .pdf if there is a .tex file.
% We need to run it at the very end, as if we move it right after a cacheMe, we cannot refer anymore to the
% input file of this block later.
\iow_close:N \g__robExt_write_list_all_figures_iow
\ifdefined\robExtEnableBackupSource%
\ior_open:Nn \g__robExt_read_ior {\jobnameNoQuotes-\robExtAddPrefixName{all-figures.txt}}%
\ior_str_map_inline:Nn \g__robExt_read_ior {%
\robExtBackupSource{#1}%
}%
\ior_close:N \g__robExt_read_ior
\fi%
}
\ExplSyntaxOff
% Not really made for the end user
% It assumes that __ROBEXT_COMPILATION_COMMAND__ and __ROBEXT_TEMPLATE__ is set
\NewDocumentCommand{\robExtEvaluateCompileAndInclude}{}{%
\ifdefined\robExtDisableExternalization%
\pgfkeys{%
/robExt/.cd,
command if no externalization,
}%
\else%
\pgfkeys{%
/robExt/.cd,
command if externalization,
}%
\ifdefined\robExtExecuteBefore\robExtExecuteBefore\fi%
\robExtWriteFile{}%
\robExtCompileFile{}%
\robExtIncludeFile{}%
\ifdefined\robExtExecuteNamedOutput\robExtExecuteNamedOutput\fi%
\ifdefined\robExtExecuteAfter\robExtExecuteAfter\fi%
\fi%
\robExtDebugInfo{Finished to include the file.}%
}
%% #1: Arguments, #2: content to externalize
\NewDocumentCommand{\robExtCacheMe}{O{}+m}{%
{% Group
%% We store the input in a non-string element for efficiently implementing "auto forward"
\edef\robExtUserInputCacheMe{\unexpanded{#2}}% \unexpanded is needed if the macro contains a #1
\pgfkeys{%
/robExt/.cd,%
%% This is needed notably if the cached elements are nested, like the include command uses itself a tikz
%% picture etc cached via \cacheTikz... It it hard to reset everything efficiently (like we might not
%% want to reset all compilation commands etc), so you can add here stuff that might need to be restored
%% later.
reset,
set placeholder={__ROBEXT_MAIN_CONTENT_ORIG__}{#2},%
default style,%
#1,
}%
\robExtEvaluateCompileAndInclude%
}%
}
\let\cacheMe\robExtCacheMe
%% #1: Arguments, #2: content to externalize
\NewDocumentEnvironment{RobExtCacheMe}{m+b}{%
\robExtCacheMe[#1]{#2}%
}{}
\let\CacheMe\RobExtCacheMe
\let\endCacheMe\endRobExtCacheMe
\NewDocumentEnvironment{RobExtCacheMeCode}{m}{%
\RobExtPlaceholderFromCode{__ROBEXT_MAIN_CONTENT_ORIG__}%
}{%
\endRobExtPlaceholderFromCode%
\pgfkeys{%
/robExt/.cd,%
#1,%
}%
\robExtEvaluateCompileAndInclude%
}
\let\CacheMeCode\RobExtCacheMeCode
\let\endCacheMeCode\endRobExtCacheMeCode
\NewDocumentEnvironment{RobExtCacheMeNoContent}{+b}{%
\robExtCacheMe[#1]{}%
}{}
\let\CacheMeNoContent\RobExtCacheMeNoContent
\let\endCacheMeNoContent\endRobExtCacheMeNoContent
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Forward a macro %%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% In this section, we provide commands to forward a macro, like
%% \def\myMacro{stuff}
%% \cacheMe<forward=\myMacro>
%% Note that this will only be forwarded if externalization is enabled (otherwise the macro already exists)
%% I published this code in this answer, thanks to egreg for some pointers
%% https://tex.stackexchange.com/questions/697120/how-to-extract-the-definition-of-a-macro-to-write-in-a-file-for-instance
\ExplSyntaxOn
\cs_new:Nn \__robExt_getCommandDefinitionFromXparse:N
{
\str_clear_new:N \l__robExt_definition_str
\GetDocumentCommandArgSpec { #1 }
\str_set:Nx \l__robExt_definition_str {
\c_backslash_str DeclareDocumentCommand
\c_left_brace_str
\c_backslash_str \cs_to_str:N #1
\c_right_brace_str
\c_left_brace_str \ArgumentSpecification \c_right_brace_str
\c_left_brace_str
\cs_replacement_spec:c { \cs_to_str:N #1 ~ code }
\c_right_brace_str
}%
}
\cs_new:Nn \__robExt_getCommandDefinitionFromDef:N
{
\str_clear_new:N \l__robExt_definition_str
\str_set:Nx \l__robExt_definition_str {
\c_backslash_str def \c_backslash_str \cs_to_str:N #1
\cs_argument_spec:c { \cs_to_str:N #1 }
\c_left_brace_str
\cs_replacement_spec:c { \cs_to_str:N #1 }
\c_right_brace_str
}%
}
\def\robExt@extractDefaultNewcommand#1#2#3#4{
#4%
}%
\cs_new:Nn \__robExt_getCommandDefinitionFromNewcommand:N
{
\str_clear_new:N \l__robExt_definition_str
\str_clear_new:N \l__robExt_tmp
%% \l__robExt_tmp_str will look like [#1]#2#3#4#5#6#7:
\str_set:Nx \l__robExt_tmp_str {\cs_argument_spec:c { \c_backslash_str \cs_to_str:N #1 }}%
%% We remove the brackets:
\str_replace_all:Nnn \l__robExt_tmp_str {[} {}
\str_replace_all:Nnn \l__robExt_tmp_str {]} {}
\str_set:Nx \l__robExt_definition_str {
%% Make sure the command does not exist
\c_backslash_str providecommand \c_backslash_str \cs_to_str:N #1 \c_left_brace_str \c_right_brace_str
%% this line will be like "\newdocumentcommand{\mymacro}"
\c_backslash_str renewcommand \c_left_brace_str \c_backslash_str \cs_to_str:N #1 \c_right_brace_str
%% It must have at least one argument, since elements without optional arguments are turned into \def
%% Moreover, macros can't have more than 1 argument, so it will be easier to parse, we just need the last digit
[\str_range:Nnn \l__robExt_tmp_str {-1} {-1}] %% <- this is the number of mandatory arguments
[\expandafter \robExt@extractDefaultNewcommand #1 ] %% <- this is the value of the default argument
\c_left_brace_str
\cs_replacement_spec:c { \c_backslash_str \cs_to_str:N #1 }
\c_right_brace_str
}%
}
% \robExtGetCommandDefinitionInMacro{\myMacro} will populate
% \l__robExt_definition_str and \robExtDefinitionCommand
% with a string to execute to write it properly.
\NewDocumentCommand{\robExtGetCommandDefinitionInMacro}{m}{
\cs_if_exist:NTF #1 {
\cs_if_exist:cTF {\cs_to_str:N #1 ~ code}{
% xparse-based definition
\__robExt_getCommandDefinitionFromXparse:N #1%
} {
\cs_if_exist:cTF {\c_backslash_str \cs_to_str:N #1}{
% \newcommand-based definition
\__robExt_getCommandDefinitionFromNewcommand:N #1
}{
% \def-based definition
\__robExt_getCommandDefinitionFromDef:N #1
}
}
}{
\msg_error:nn{robExt}{The~macro~#1~does~not~exist}
}
\let\robExtDefinitionCommand\l__robExt_definition_str
}
\NewDocumentCommand{\robExtGetCommandDefinition}{m}{%
\robExtGetCommandDefinitionInMacro{#1}%
\l__robExt_definition_str%
}
\ExplSyntaxOff
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% Default presets %%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% We create here a few presets and placeholders useful later
%%%%%%
%%%%%% Group "special characters"
%%%%%%
%%%% Generic placeholders, practical to escape stuff
\robExtClearGroupPlaceholders{main}
\ExplSyntaxOn
\robExtPlaceholderFromString{__ROBEXT_LEFT_BRACE__}{\c_left_brace_str}
\robExtPlaceholderFromString{__ROBEXT_RIGHT_BRACE__}{\c_right_brace_str}
\robExtPlaceholderFromString{__ROBEXT_BACKSLASH__}{\c_backslash_str}
\robExtPlaceholderFromString{__ROBEXT_HASH__}{\c_hash_str}
\robExtPlaceholderFromString{__ROBEXT_PERCENT__}{\c_percent_str}
\robExtPlaceholderFromString{__ROBEXT_UNDERSCORE__}{\c_underscore_str}
\ExplSyntaxOff
\robExtCopyGroupPlaceholders{special characters}{main}
\robExtRegisterGroupPlaceholders{special characters}
%%%%%%
%%%%%% Group "latex"
%%%%%%
\robExtClearGroupPlaceholders{main}
\begin{RobExtPlaceholderFromCode}{__ROBEXT_LATEX__}
\documentclass[__ROBEXT_LATEX_OPTIONS__]{__ROBEXT_LATEX_DOCUMENT_CLASS__}
__ROBEXT_LATEX_PREAMBLE__
% most packages must be loaded before hyperref
% so we typically want to load hyperref here
__ROBEXT_LATEX_PREAMBLE_HYPERREF__
% some packages must be loaded after hyperref
__ROBEXT_LATEX_PREAMBLE_AFTER_HYPERREF__
\begin{document}%
__ROBEXT_LATEX_MAIN_CONTENT_WRAPPED__
\end{document}
\end{RobExtPlaceholderFromCode}
\robExtSetPlaceholder{__ROBEXT_LATEX_OPTIONS__}{}
\robExtSetPlaceholder{__ROBEXT_LATEX_DOCUMENT_CLASS__}{standalone}
\robExtSetPlaceholder{__ROBEXT_LATEX_PREAMBLE__}{}
\robExtSetPlaceholder{__ROBEXT_LATEX_PREAMBLE_HYPERREF__}{}
\robExtSetPlaceholder{__ROBEXT_LATEX_PREAMBLE_AFTER_HYPERREF__}{}
\robExtSetPlaceholder{__ROBEXT_LATEX_TRIM_LENGTH__}{30cm}
\begin{RobExtPlaceholderFromCode}{__ROBEXT_LATEX_MAIN_CONTENT_WRAPPED__}
__ROBEXT_LATEX_CREATE_OUT_FILE__%
\newsavebox\boxRobExt%
\begin{lrbox}{\boxRobExt}%
__ROBEXT_MAIN_CONTENT__%
\end{lrbox}%
\usebox{\boxRobExt}%
__ROBEXT_LATEX_WRITE_DEPTH_TO_OUT_FILE__%
\end{RobExtPlaceholderFromCode}
\begin{RobExtPlaceholderFromCode}{__ROBEXT_LATEX_CREATE_OUT_FILE__}
%% We save the height/depth of the content by using a savebox:
\newwrite\writeRobExt%
\immediate\openout\writeRobExt=\jobname-out.tex%
\end{RobExtPlaceholderFromCode}
\begin{RobExtPlaceholderFromCode}{__ROBEXT_LATEX_WRITE_DEPTH_TO_OUT_FILE__}
\immediate\write\writeRobExt{%
\string\def\string\robExtWidth{\the\wd\boxRobExt}%
\string\def\string\robExtHeight{\the\ht\boxRobExt}%
\string\def\string\robExtDepth{\the\dp\boxRobExt}%
}%
\end{RobExtPlaceholderFromCode}
%% Compilation commands
\robExtSetPlaceholder{__ROBEXT_LATEX_COMPILATION_COMMAND__}{__ROBEXT_LATEX_ENGINE__ __ROBEXT_LATEX_COMPILATION_COMMAND_OPTIONS__ "__ROBEXT_SOURCE_FILE__"}
\robExtSetPlaceholder{__ROBEXT_LATEX_COMPILATION_COMMAND_OPTIONS__}{-halt-on-error -interaction=nonstopmode}
\robExtSetPlaceholder{__ROBEXT_LATEX_ENGINE__}{pdflatex}
\robExtConfigure{
%%%% Allow users to specify which command to try (or try first) when replacing placeholders. Order matters.
%%%% it looks ugly, but it is mostly for performance improvements.
first placeholders latex/.style={
first placeholders in compilation command={__ROBEXT_LATEX_COMPILATION_COMMAND__,__ROBEXT_LATEX_ENGINE__,__ROBEXT_LATEX_COMPILATION_COMMAND_OPTIONS__},
first placeholders in template={__ROBEXT_LATEX__,__ROBEXT_LATEX_OPTIONS__,__ROBEXT_LATEX_DOCUMENT_CLASS__,__ROBEXT_LATEX_PREAMBLE__,__ROBEXT_LATEX_PREAMBLE_HYPERREF__,__ROBEXT_LATEX_PREAMBLE_AFTER_HYPERREF__,__ROBEXT_LATEX_MAIN_CONTENT_WRAPPED__,__ROBEXT_LATEX_TRIM_LENGTH__,__ROBEXT_LATEX_CREATE_OUT_FILE__,__ROBEXT_LATEX_WRITE_DEPTH_TO_OUT_FILE__,__ROBEXT_MAIN_CONTENT__,__ROBEXT_MAIN_CONTENT_ORIG__},
},
only placeholders latex/.style={
only placeholders in compilation command={__ROBEXT_LATEX_COMPILATION_COMMAND__,__ROBEXT_LATEX_ENGINE__,__ROBEXT_LATEX_COMPILATION_COMMAND_OPTIONS__},
only placeholders in template={__ROBEXT_LATEX__,__ROBEXT_LATEX_OPTIONS__,__ROBEXT_LATEX_DOCUMENT_CLASS__,__ROBEXT_LATEX_PREAMBLE__,__ROBEXT_LATEX_PREAMBLE_HYPERREF__,__ROBEXT_LATEX_PREAMBLE_AFTER_HYPERREF__,__ROBEXT_LATEX_MAIN_CONTENT_WRAPPED__,__ROBEXT_LATEX_TRIM_LENGTH__,__ROBEXT_LATEX_CREATE_OUT_FILE__,__ROBEXT_LATEX_WRITE_DEPTH_TO_OUT_FILE__,__ROBEXT_MAIN_CONTENT__,__ROBEXT_MAIN_CONTENT_ORIG__},
},
dvi to ps/.style={
add to placeholder/.expanded={__ROBEXT_COMPILATION_COMMAND__}{ && dvips "__ROBEXT_OUTPUT_PREFIX__.dvi" && \robExtMv\space"__ROBEXT_OUTPUT_PREFIX__.ps" "__ROBEXT_OUTPUT_PDF__"},
},
% some useful presets
latex/.style={
enable placeholders,
% This way it appears before orig in the list: more efficient
import placeholders from group={latex},
%/utils/exec={\message{I am in the latex style!!!!!!}},
set placeholder={__ROBEXT_MAIN_CONTENT__}{__ROBEXT_MAIN_CONTENT_ORIG__},
import placeholder={__ROBEXT_MAIN_CONTENT_ORIG__},
first placeholders latex,
set template={__ROBEXT_LATEX__},
set compilation command={__ROBEXT_LATEX_COMPILATION_COMMAND__},
%% Configure the latex compilation engine
add to compilation command options/.style={
add to placeholder={__ROBEXT_LATEX_COMPILATION_COMMAND_OPTIONS__}{##1},
},
use latexmk/.style={
set placeholder={__ROBEXT_LATEX_ENGINE__}{latexmk},
},
use latex and dvi/.style={
set placeholder={__ROBEXT_LATEX_ENGINE__}{latex},
dvi to ps,
},
use lualatex/.style={
set placeholder={__ROBEXT_LATEX_ENGINE__}{lualatex},
},
use xelatex/.style={
set placeholder={__ROBEXT_LATEX_ENGINE__}{xelatex},
},
set latex options/.style={
set placeholder={__ROBEXT_LATEX_OPTIONS__}{##1},
},
add to latex options/.style={
add to placeholder no space={__ROBEXT_LATEX_OPTIONS__}{,##1},
},
set documentclass/.style={
set placeholder={__ROBEXT_LATEX_DOCUMENT_CLASS__}{##1},
},
set preamble/.style={
set placeholder={__ROBEXT_LATEX_PREAMBLE__}{##1},
},
add to preamble/.style={
add to placeholder={__ROBEXT_LATEX_PREAMBLE__}{##1},
},
add before main content/.style={
add before placeholder no space={__ROBEXT_MAIN_CONTENT__}{##1},
},
add before preamble/.style={
add before placeholder={__ROBEXT_LATEX_PREAMBLE__}{##1},
},
set preamble hyperref/.style={
set placeholder={__ROBEXT_LATEX_PREAMBLE_HYPERREF__}{##1},
},
add to preamble hyperref/.style={
add to placeholder={__ROBEXT_LATEX_PREAMBLE_HYPERREF__}{##1},
},
set preamble after hyperref/.style={
set placeholder={__ROBEXT_LATEX_PREAMBLE_AFTER_HYPERREF__}{##1},
},
add to preamble after hyperref/.style={
add to placeholder={__ROBEXT_LATEX_PREAMBLE_AFTER_HYPERREF__}{##1},
},
do not wrap code/.style={
set placeholder={__ROBEXT_LATEX_MAIN_CONTENT_WRAPPED__}{__ROBEXT_MAIN_CONTENT__},
},
add to includegraphics options={trim=__ROBEXT_LATEX_TRIM_LENGTH__ __ROBEXT_LATEX_TRIM_LENGTH__ __ROBEXT_LATEX_TRIM_LENGTH__ __ROBEXT_LATEX_TRIM_LENGTH__},
add to latex options={margin=__ROBEXT_LATEX_TRIM_LENGTH__},
do not add margins/.style={
set placeholder={__ROBEXT_LATEX_TRIM_LENGTH__}{0cm}
},
},
% U
tikz/.style={
latex,
add to preamble={\usepackage{tikz}},
},
tikzpicture/.style={
tikz,
set placeholder no import={__ROBEXT_MAIN_CONTENT__}{\begin{tikzpicture}__ROBEXT_MAIN_CONTENT_ORIG__\end{tikzpicture}},
},
}
\robExtCopyGroupPlaceholders{latex}{main}
\robExtRegisterGroupPlaceholders{latex}
%%%%%%
%%%%%% Group "python"
%%%%%%
\robExtClearGroupPlaceholders{main}
\begin{RobExtPlaceholderFromCode}{__ROBEXT_PYTHON__}
__ROBEXT_PYTHON_IMPORT__
__ROBEXT_PYTHON_MAIN_CONTENT_WRAPPED__
\end{RobExtPlaceholderFromCode}
\robExtSetPlaceholder{__ROBEXT_PYTHON_IMPORT__}{}
\begin{RobExtPlaceholderFromCode}{__ROBEXT_PYTHON_MAIN_CONTENT_WRAPPED__}
# This file will be loaded in latex. Useful to pass data to the main document
f_out_write = open("__ROBEXT_OUTPUT_PREFIX__-out.tex", "w")
import os
import sys
def write_to_out(text):
"""Write to the -out.tex file that is loaded by default"""
f_out_write.write(text)
def parse_args():
args = {}
if len(sys.argv) % 2 == 0:
print("Error: the number of arguments must be even, as tuples of name and value")
exit(1)
for i in range(0,len(sys.argv)-1,2):
args[sys.argv[i+1]] = sys.argv[i+2]
return args
def get_cache_folder():
'''
Path of the cache folder. Warning: this works only when the python script
is located in this cache folder (that should be true when it's called from LaTeX)
'''
return os.path.abspath(os.path.dirname(sys.argv[0]))
def get_file_base():
'''
Outputs the base of the files (i.e. something like robExt-somehash, without any extension)
'''
return os.path.splitext(os.path.basename(sys.argv[0]))[0] # __file__ does not work as it refers to the library
def get_current_script():
'''
Outputs the path of the current script
'''
return os.path.abspath(sys.argv[0]) # __file__ does not work as it refers to the library
def get_filename_from_extension(extension):
'''
If you want to create a file with extension 'extension' (with the appropriate base name), this command
is for you. For instance get_filename_from_extension(".mp4") would return something like
robExt-somehash.mp4
the extension can also be like get_filename_from_extension("-out.tex") etc.
'''
return os.path.join(get_cache_folder(), get_file_base() + extension)
def get_verbatim_output():
'''Returns the path to -out.txt that is read by verbatim output'''
return get_filename_from_extension("-out.txt")
def get_pdf_output():
'''Returns the path to -out.txt that is read by verbatim output'''
return get_filename_from_extension(".pdf")
def finished_with_no_error():
'''
Call this at the end of your script. This creates the path of the final pdf file that should be
created (otherwise robust-externalize will think that the compilation failed)
'''
if not os.path.exists(get_filename_from_extension(".pdf")):
# we create a nearly empty pdf (not empty or arxiv will remove it)
with open(get_filename_from_extension(".pdf"), 'w') as f:
f.write("ok")
### Starting main content
__ROBEXT_MAIN_CONTENT__
### Ending main content
__ROBEXT_PYTHON_FINISHED_WITH_NO_ERROR__
f_out_write.close()
\end{RobExtPlaceholderFromCode}
% It is annoying to manually call finished_with_no_error(), but it is handy to be able to disable it.
\begin{RobExtPlaceholderFromCode}{__ROBEXT_PYTHON_FINISHED_WITH_NO_ERROR__}
finished_with_no_error()
\end{RobExtPlaceholderFromCode}
%% On windows, python3 does not exist, and python points to python3. On linux, it seems to depend, at least on
%% my system it points to python3 as well.
\robExtSetPlaceholder{__ROBEXT_PYTHON_EXEC__}{python}
\robExtConfigure{
python/.style={
enable placeholders,
print whole file in error message,
import placeholders={__ROBEXT_PYTHON__,__ROBEXT_PYTHON_IMPORT__,__ROBEXT_PYTHON_MAIN_CONTENT_WRAPPED__,__ROBEXT_PYTHON_FINISHED_WITH_NO_ERROR__,__ROBEXT_PYTHON_EXEC__},
set compilation command={__ROBEXT_PYTHON_EXEC__ "__ROBEXT_SOURCE_FILE__"},
set template={__ROBEXT_PYTHON__},
print verbatim if no externalization,
force python3/.style={
set placeholder={__ROBEXT_PYTHON_EXEC__}{python3}
},
add import/.style={
add to placeholder no space={__ROBEXT_PYTHON_IMPORT__}{##1^^J},
},
remove leading spaces if not disabled,
}
}
\robExtCopyGroupPlaceholders{python}{main}
\robExtRegisterGroupPlaceholders{python}
%%%%%%
%%%%%% Group "python print code result"
%%%%%%
\robExtClearGroupPlaceholders{main}
%% A style to print both the code and the result:
\begin{RobExtPlaceholderFromCode}{__ROBEXT_PYTHON_PRINT_CODE_RESULT_TEMPLATE_BEFORE__}
# File where print("bla") should be redirected
# get_filename_from_extension("-foo.txt") will give you the path of the file
# in the cache that looks like robExt-somehash-foo.txt
print_file = open(get_filename_from_extension("-print.txt"), "w")
sys.stdout = print_file
# This code will read the current code, and extract the lines between
# that starts with "### CODESTARTSHERE" and "### CODESTOPSHERE", and will write
# it into the *-code.text (we do not want to print all these functions in
# the final code)
with open(get_filename_from_extension("-code.txt"), "w") as f:
# The current script has extension .tex
with open(get_current_script(), "r") as script:
should_write = False
for line in script:
if line.startswith("### CODESTARTSHERE"):
should_write = True
elif line.startswith("### CODESTOPSHERE"):
should_write = False
elif "HIDEME" in line:
pass
else:
if should_write:
f.write(line)
### CODESTARTSHERE
\end{RobExtPlaceholderFromCode}
\begin{RobExtPlaceholderFromCode}{__ROBEXT_PYTHON_PRINT_CODE_RESULT_TEMPLATE_AFTER__}
### CODESTOPSHERE
print_file.close()
\end{RobExtPlaceholderFromCode}
\robExtSetPlaceholder{__ROBEXT_PYTHON_TCOLORBOX_PROPS__}{colback=red!5!white,colframe=red!75!black}
\robExtSetPlaceholder{__ROBEXT_PYTHON_CODE_MESSAGE__}{}
\robExtSetPlaceholder{__ROBEXT_PYTHON_RESULT_MESSAGE__}{Output:}
\robExtSetPlaceholder{__ROBEXT_PYTHON_LSTINPUT_STYLE__}{breakindent=.5\textwidth, frame=single, breaklines=true, style=pythonhighlight-style}
\robExtConfigure{
python print code and result/.style={
python,
import placeholders={__ROBEXT_PYTHON_PRINT_CODE_RESULT_TEMPLATE_BEFORE__,__ROBEXT_PYTHON_PRINT_CODE_RESULT_TEMPLATE_AFTER__,__ROBEXT_PYTHON_TCOLORBOX_PROPS__,__ROBEXT_PYTHON_CODE_MESSAGE__,__ROBEXT_PYTHON_RESULT_MESSAGE__,__ROBEXT_PYTHON_LSTINPUT_STYLE__},
add before placeholder no space={__ROBEXT_MAIN_CONTENT__}{__ROBEXT_PYTHON_PRINT_CODE_RESULT_TEMPLATE_BEFORE__},
add to placeholder no space={__ROBEXT_MAIN_CONTENT__}{__ROBEXT_PYTHON_PRINT_CODE_RESULT_TEMPLATE_AFTER__},
set title/.style={
set placeholder={__MY_TITLE__}{##1},
},
set title={Python code},
custom include command={
% Useful to replace __MY_TITLE__:
%% To print a nicer error message, we check if the user uses the default style
\robExtIfPlaceholderMatchesString{__ROBEXT_PYTHON_LSTINPUT_STYLE__}{pythonhighlight-style}{%
\ifcsname lststy@pythonhighlight-style$\endcsname%
\else%
\ifcsname lststy@mypython$\endcsname%
\lstdefinestyle{pythonhighlight-style}{style=mypython}%
\else%
\robExtMsgError{needtoloadpythonhighlight}%
\fi%
\fi%
}{}%
\evalPlaceholder{
\begin{tcolorbox}[title=__MY_TITLE__,__ROBEXT_PYTHON_TCOLORBOX_PROPS__]
__ROBEXT_PYTHON_CODE_MESSAGE__%
\lstinputlisting[__ROBEXT_PYTHON_LSTINPUT_STYLE__]{\robExtAddCachePathAndName{\robExtFinalHash-code.txt}}
__ROBEXT_PYTHON_RESULT_MESSAGE__%
\verbatiminput{\robExtAddCachePathAndName{\robExtFinalHash-print.txt}}
\end{tcolorbox}
}
},
},
}
\robExtCopyGroupPlaceholders{python print code result}{main}
\robExtRegisterGroupPlaceholders{python print code result}
%%%%%%
%%%%%% Group "verbatim"
%%%%%%
\robExtClearGroupPlaceholders{main}
%% Inspired by
%% https://tex.stackexchange.com/questions/259247/rescaling-gnuplottex-to-fit-in-subfigure/259271
\ExplSyntaxOn
\DeclareExpandableDocumentCommand{\robExtLenToCm}{ O{cm} m }
{
\dim_to_decimal_in_unit:nn { #2 } { 1 #1 } #1
}
\DeclareExpandableDocumentCommand{\robExtLenToCmNoUnit}{ O{cm} m }
{
\dim_to_decimal_in_unit:nn { #2 } { 1 #1 }
}
\ExplSyntaxOff
\let\lenToCm\robExtLenToCm
\let\lenToCmNoUnit\robExtLenToCmNoUnit
\robExtConfigure{
verbatim text/.style={
enable placeholders,
set template={__ROBEXT_MAIN_CONTENT__},
custom include command={\evalPlaceholder{\verbatiminput{\robExtAddCachePathAndName{\robExtFinalHash.tex}}}},
%% Apparently this works on windows as well https://stackoverflow.com/questions/1702762/how-can-i-create-an-empty-file-at-the-command-line-in-windows
%% We do not want it to be empty or arXiv will remove the file.
set compilation command={echo "ok" > __ROBEXT_OUTPUT_PDF__},
},
verbatim text no include/.style={
verbatim text,
custom include command={\evalPlaceholder{%
\xdef\robExtPathToInput{\robExtAddCachePathAndName{\robExtFinalHash.tex}}%
}%
}%
},
}
\robExtCopyGroupPlaceholders{verbatim}{main}
\robExtRegisterGroupPlaceholders{verbatim}
%%%%%%
%%%%%% Group "gnuplot"
%%%%%%
% We clean the "main" group, that we will copy later to gnuplot.
\robExtClearGroupPlaceholders{main}
\begin{PlaceholderFromCode}*{__GNUPLOT_TEMPLATE__}
set terminal __ROBEXT_GNUPLOT_TERMINAL__
set output "__ROBEXT_GNUPLOT_OUTPUTFILE__"
set loadpath "__ROBEXT_WAY_BACK__"
__ROBEXT_GNUPLOT_PRELUDE__
__ROBEXT_MAIN_CONTENT__
\end{PlaceholderFromCode}
\begin{PlaceholderFromCode}{__ROBEXT_GNUPLOT_PRELUDE__}
\end{PlaceholderFromCode}
\robExtCopyGroupPlaceholders{gnuplot}{main}
\robExtRegisterGroupPlaceholders{gnuplot}
\robExtConfigure{
new preset={gnuplot}{
enable placeholders,
print whole file in error message,
import placeholders from group={gnuplot},
set compilation command={gnuplot -c "__ROBEXT_SOURCE_FILE__" && echo "ok" > "__ROBEXT_OUTPUT_PDF__"},
set terminal/.style={
set placeholder={__ROBEXT_GNUPLOT_TERMINAL__}{#1},
},
% The extension is important. Notably, the cairolatex expects a .tex.
set filename/.style={
set placeholder={__ROBEXT_GNUPLOT_OUTPUTFILE__}{__ROBEXT_OUTPUT_PREFIX__-gnuplot-#1},
set placeholder={__ROBEXT_INCLUDEGRAPHICS_FILE__}{\robExtAddCachePathAndName{\robExtFinalHash-gnuplot-#1}},
},
pdf terminal/.style={
set terminal={pdf #1},
set filename={.pdf},
},
pdf terminal/.default={},
pdf terminal,
is tex output/.style={
set filename={.tex},
add cache to graphicspath, % At least in cairolatex, we first need to input a file that includegraphics an image in the cache.
custom include command={\message{We will input the file \robExtAddCachePathAndName{\robExtFinalHash-gnuplot-.tex}}\input{\robExtAddCachePathAndName{\robExtFinalHash-gnuplot-.tex}}},
},
tikz terminal/.style={
set terminal={tikz #1},
is tex output,
},
tikz terminal/.default={},
% Warning: you cannot define this option with the name "tikz" if you try to nest elements, for instance
% if the include command calls an \input that calls a tikz picture, that is cached by \cacheTikz and therefore
% call the "tikz" preset that is now changed.
cairolatex terminal/.style={
set terminal={cairolatex #1},
is tex output,
},
cairolatex terminal/.default={},
set template={__GNUPLOT_TEMPLATE__},
print verbatim if no externalization,
},
}
% Like \gpgetvar but do not produce an error if the variable does not exist yet
% Fix https://github.com/leo-colisson/robust-externalize/issues/17
\ExplSyntaxOn
\NewDocumentCommand{\robExtGpgetvar}{m}{%
\ifcsname gp@var@#1\endcsname%
\gpgetvar{#1}%
\else%
\msg_warning:nnx{robExt}{gpgetvar recompilation needed}{#1}%
\emph{Please~recompile~to~load~ variable:~}%
\texttt{\tl_to_str:n {#1}}%
\fi%
}
% Is useful if you want a number even if there is an error https://github.com/leo-colisson/robust-externalize/issues/17#issuecomment-1862556771
% It must be expandable.
\NewExpandableDocumentCommand{\robExtGpgetvarNb}{sO{404}m}{%
\ifcsname gp@var@#3\endcsname%
\gpgetvar{#3}%
\else% We cannot print a waring or siunit will complain.
\sys_if_engine_luatex:TF{\directlua{texio.write_nl('Warning:~the~variable~#3~used~in~robExtGpgetvarNb~does~not~exist~yet.~Please~recompile.')}}{}#2%
\fi%
}
\ExplSyntaxOff
%%%%%%
%%%%%% Group "bash"
%%%%%%
\robExtClearGroupPlaceholders{main}
\begin{RobExtPlaceholderFromCode}{__ROBEXT_BASH_TEMPLATE__}
# Quit if there is an error
set -e
outputTxt="__ROBEXT_OUTPUT_PREFIX__-out.txt"
outputTex="__ROBEXT_OUTPUT_PREFIX__-out.tex"
outputPdf="__ROBEXT_OUTPUT_PDF__"
__ROBEXT_MAIN_CONTENT__
# Create the pdf file to certify that no compilation error occured
echo "ok" > "${outputPdf}"
\end{RobExtPlaceholderFromCode}
\robExtSetPlaceholder{__ROBEXT_BASH_SHELL__}{bash}
\robExtConfigure{
bash/.style={
enable placeholders,
print whole file in error message,
import placeholders={__ROBEXT_BASH_TEMPLATE__,__ROBEXT_BASH_SHELL__},
set compilation command={__ROBEXT_BASH_SHELL__ "__ROBEXT_SOURCE_FILE__"},
set template={__ROBEXT_BASH_TEMPLATE__},
print verbatim if no externalization,
}
}
\robExtCopyGroupPlaceholders{bash}{main}
\robExtRegisterGroupPlaceholders{bash}
%%%%%%
%%%%%% Group "web image", to download images automatically
%%%%%%
\robExtClearGroupPlaceholders{main}
% \begin{RobExtPlaceholderFromCode}{__ROBEXT_BASH_TEMPLATE__}
% # Quit if there is an error
% set -e
% outputTxt="__ROBEXT_OUTPUT_PREFIX__-out.txt"
% outputTex="__ROBEXT_OUTPUT_PREFIX__-out.tex"
% outputPdf="__ROBEXT_OUTPUT_PDF__"
% __ROBEXT_MAIN_CONTENT__
% # Create the pdf file to certify that no compilation error occured
% echo "ok" > "${outputPdf}"
% \end{RobExtPlaceholderFromCode}
\robExtConfigure{
web image/.style={
enable placeholders,
print whole file in error message,
wget/.style={
set compilation command move if no error={wget "__ROBEXT_MAIN_CONTENT__" -O "__ROBEXT_OUTPUT_PDF__-tmp"},
},
curl/.style={
set compilation command move if no error={curl "__ROBEXT_MAIN_CONTENT__" -L -o "__ROBEXT_OUTPUT_PDF__-tmp"},
},
if unix={
wget
},
if windows={
curl
},
set template={},
}
}
\NewDocumentCommand{\robExtIncludegraphicsWeb}{D<>{}O{}m}{\robExtCacheMe[web image,set includegraphics options={#2},#1]{#3}}
\let\includegraphicsWeb\robExtIncludegraphicsWeb
\robExtCopyGroupPlaceholders{web image}{main}
\robExtRegisterGroupPlaceholders{web image}
%%%%%%
%%%%%% Group "default", available in all styles
%%%%%%
\robExtClearGroupPlaceholders{main}
% This additional level of indirection is made to allow an easier wrapping
% of \begin{tikzpicture} ... \end{tikzpicture} for instance.
% The original behavior (modify __ROBEXT_MAIN_CONTENT__ directly)
% was not really practical as if you use both |tikz| and |\cacheCommand|, it would wrap
% the environment twice.
\robExtSetPlaceholder{__ROBEXT_MAIN_CONTENT__}{__ROBEXT_MAIN_CONTENT_ORIG__}
\setPlaceholder{__ROBEXT_INCLUDEGRAPHICS_OPTIONS__}{}
\setPlaceholder{__ROBEXT_INCLUDEGRAPHICS_FILE__}{\robExtAddCachePathAndName{\robExtFinalHash.pdf}}
\robExtConfigure{
% Allow nice optimizations
all placeholders have underscores,
set includegraphics options/.style={
set placeholder no import={__ROBEXT_INCLUDEGRAPHICS_OPTIONS__}{#1},
},
add to includegraphics options/.style={
add to placeholder no space no import={__ROBEXT_INCLUDEGRAPHICS_OPTIONS__}{,#1},
},
set placeholder no import={__ROBEXT_VERBATIM_COMMAND__}{\verbatiminput},
% We expect the program to write in __ROBEXT_OUTPUT_PREFIX__-out.txt
verbatim output/.style={
import placeholders={__ROBEXT_VERBATIM_COMMAND__},
custom include command={%
\evalPlaceholder{%
__ROBEXT_VERBATIM_COMMAND__{__ROBEXT_CACHE_FOLDER____ROBEXT_OUTPUT_PREFIX__-out.txt}%
}%
},
},
% Mostly for debugging purpose
print command and source/.style={
enable manual mode,
custom include command advanced={%
\evalPlaceholder{%
Command: (run in folder \texttt{__ROBEXT_CACHE_FOLDER__})
\robExtPrintPlaceholder{__ROBEXT_COMPILATION_COMMAND__}
Dependencies:
\verbatiminput{__ROBEXT_CACHE_FOLDER____ROBEXT_OUTPUT_PREFIX__.deps}%
Source (in \texttt{__ROBEXT_CACHE_FOLDER____ROBEXT_OUTPUT_PREFIX__.tex}):
\verbatiminput{__ROBEXT_CACHE_FOLDER____ROBEXT_OUTPUT_PREFIX__.tex}%
}%
},
},
debug/.style={
print command and source
},
}
\robExtCopyGroupPlaceholders{default}{main} % we do not reset it as it will be the default imported group
\robExtRegisterGroupPlaceholders{default}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%% Automatically cache environments, tikzpicture etc %%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%
%%%%%% Parse tikz
%%%%%%
%% The parser is mostly taken from
%% https://github.com/sasozivanovic/memoize/blob/master/memoize-tikz.tex
\newtoks\robExt@temptoks
\def\robExt@apptotoks#1#2{\expandafter#1\expandafter{\the#1#2}}
\def\robExt@tikz{%
\robExt@temptoks={}%
\robExt@tikz@anim%
}
\def\robExt@tikz@anim{%
\pgfutil@ifnextchar[{\robExt@tikz@opt}{%
\pgfutil@ifnextchar:{\robExt@tikz@anim@a}{%
\robExt@tikz@code}}%]
}%
\def\robExt@tikz@anim@a#1=#2{%
\robExt@apptotoks\robExt@temptoks{#1={#2}}%
\robExt@tikz@anim%
}%
\def\robExt@tikz@opt[#1]{%
\robExt@apptotoks\robExt@temptoks{[#1]}%
\robExt@tikz@code%
}
\def\robExt@tikz@code{%
\pgfutil@ifnextchar\bgroup\robExt@tikz@braced\robExt@tikz@single%
}
\def\robExt@tikz@braced#1{\robExt@apptotoks\robExt@temptoks{{#1}}\robExt@tikz@done}
\def\robExt@tikz@single#1;{\robExt@apptotoks\robExt@temptoks{#1;}\robExt@tikz@done}
\def\robExt@tikz@done{%
\edef\robExt@marshal{%
\noexpand\robExtRescanHashRobust{\noexpand\robExtCacheMe[\expandonce{\robExt@tmp@orig@args}, \expandonce{\robExt@tmp@args}]{%
\noexpand\tikz\the\robExt@temptoks%
}%
}%
}%
\robExt@marshal%
}
\NewDocumentCommand{\robExtExternalizeAllTikzpictures}{O{tikz}O{tikzpicture}O{<>}}{%
\robExtDoNotExternalizeAllTikzpictures%
\robExtCacheEnvironment[#3]{tikzpicture}{#2}%
% Tikz has a more complicated parsing system
\DeclareCommandCopy{\robExtCommandOrigtikz}{\tikz}% we save the function for later reset
\robExtAddToCommandResetList{tikz}% we add it to the list of functions to reset
\DeclareDocumentCommand{\tikz}{D#3{}}{%
\def\robExtCommandOrigName{tikz}% we specify the name of the current environment
\edef\robExt@tmp@args{\detokenize{##1}}% we specify the name of the current environment
\edef\robExt@tmp@orig@args{\detokenize{#1}}% we specify the name of the current environment
% we start the parsing
\robExt@tikz%
}%
}
\let\cacheTikz\robExtExternalizeAllTikzpictures
\NewDocumentCommand{\robExtDoNotExternalizeAllTikzpictures}{}{%
\ifdefined\robExtCommandOrigtikz%
\DeclareCommandCopy{\tikz}{\robExtCommandOrigtikz}%
\let\robExtCommandOrigtikz\undefined
\fi%
\ifdefined\robExtEnvironmentOrigtikzpicture%
\DeclareEnvironmentCopy{tikzpicture}{robExtEnvironmentOrigtikzpicture}%
\let\robExtEnvironmentOrigtikzpicture\undefined
\fi%
}
\let\doNotCacheTikz\robExtDoNotExternalizeAllTikzpictures
%% The cached version
\DeclareDocumentEnvironment{tikzpictureC}{D<>{}O{}O{}}{%
\begin{CacheMe}{tikzpicture,#1}[#2]%
}{\end{CacheMe}}%
%%%%%%
%%%%%% Cache tikzit
%%%%%%
\NewDocumentCommand{\robExtCacheTikzit}{O{tikzit}}{%
\ifdefined\robExtCommandOrigtikzfig\else%
\DeclareCommandCopy{\robExtCommandOrigtikzfig}{\tikzfig}%
\fi%
\robExtAddToCommandResetList{tikzfig}%
\DeclareDocumentCommand{\tikzfig}{D<>{}m}{%
% tikzit tries first ##2.tikz and fallsback to figures/##2.tikz otherwise.
\IfFileExists{##2.tikz}{\def\robExtTikzfigFile{##2}}{\def\robExtTikzfigFile{figures/##2}}%
\expanded{\noexpand\cacheMe[
#1,
add dependencies/.expanded={\robExtTikzfigFile.tikz},
##1,
command if no externalization/.code={\noexpand\robExtDisableTikzpictureOverwrite\noexpand\robExtCommandOrigtikzfig{##2}},
]{\noexpand\tikzfig{__ROBEXT_WAY_BACK__\robExtTikzfigFile}}}%
}%
}
\let\cacheTikzit\robExtCacheTikzit
\NewDocumentCommand{\robExtCacheTikzitWithStyle}{O{tikzit}m}{%
\robExtCacheTikzit[#1]%
\robExtConfigure{
% First, we copy the tikzit-related files to the cache:
copy file to cache={tikzit.sty},
copy file to cache={#2},
% We create the tikzit preset loaded by default:
new preset={#1}{
latex,
add to preamble={
\usepackage{tikzit}
\input{#2}
},
},
}%
}
\let\cacheTikzitWithStyle\robExtCacheTikzitWithStyle
%%%%%%
%%%%%% The generic functions
%%%%%%
%% Usage: \robExtCacheEnvironment{myenv}
\NewDocumentCommand{\robExtCacheEnvironment}{O{<>}mm}{%
%% We need to save the original environment to avoid infinite recursion if we disable externalization
%% https://tex.stackexchange.com/questions/695391/why-isnt-my-environment-restored/695398
\ifcsname robExtEnvironmentOrig#2\endcsname\PackageWarning{Your are trying to cache an environment #2 that seems to be already cached... Expect weird things to happen.}{}\fi
\DeclareEnvironmentCopy{robExtEnvironmentOrig#2}{#2}%
\robExtAddToEnvironmentResetList{#2}%
\DeclareDocumentEnvironment{#2}{D#1{}}{%
\def\robExtEnvironmentOrigName{#2}%
\CacheMe{%
#3,%
set placeholder no import={__ROBEXT_MAIN_CONTENT__}{\begin{#2}__ROBEXT_MAIN_CONTENT_ORIG__\end{#2}},%
##1%
}%
}{\endCacheMe}%
}
\let\cacheEnvironment\robExtCacheEnvironment
% \robExtShowPlaceholder{__ROBEXT_MAIN_CONTENT__}
% \robExtShowPlaceholdersContents
%%%% Borrowed and adapted from https://github.com/sasozivanovic/memoize/blob/master/xparse-arglist.sty
%%%% see also https://tex.stackexchange.com/questions/695662/automatically-wrap-a-macro/695734
%% The idea of the library is that it builds a string like
%% [#2]<#3>{#4}
%% in order to generate something like
%% \NewDocumentCommand{\myfunction}{D<>{}O{coucou}D<>{yes}m}
%% {
%% \cacheMe[#1]{\myfunction[#2]<#3>{#4}}
%% }
%%%% _________________________________________________________________
\def\robExtArgumentList{%
\expandafter\robExt@arglist\expandafter0\ArgumentSpecification.%
}
\def\robExt@arglist#1#2{%
\ifcsname robExt@arglist@#2\endcsname
\csname robExt@arglist@#2\expandafter\expandafter\expandafter\endcsname
\else
\expandafter\robExt@arglist@error
\fi
\expandafter{\the\numexpr#1+1\relax}%
}
% \robExt@arglist@...: #1 = the argument number
\def\robExt@arglist@m#1{\noexpand\unexpanded{{#####1}}\robExt@arglist{#1}}
\def\robExt@arglist@r#1#2#3{\noexpand\unexpanded{#2#####1#3}\robExt@arglist{#1}}
\def\robExt@arglist@R#1#2#3#4{\noexpand\unexpanded{#2#####1#3}\robExt@arglist{#1}}
\def\robExt@arglist@v#1{{Handled commands with verbatim arguments are not
supported}\robExt@arglist{#1}} % error
\def\robExt@arglist@b#1{{This is not the way to handle
environment}\robExt@arglist{#1}} % error
\def\robExt@arglist@o#1{\noexpand\unexpanded{[#####1]}\robExt@arglist{#1}}
\def\robExt@arglist@d#1#2#3{\noexpand\unexpanded{#2#####1#3}\robExt@arglist{#1}}
\def\robExt@arglist@O#1#2{\noexpand\unexpanded{[#####1]}\robExt@arglist{#1}}
% \def\robExt@arglist@O#1#2{\noexpand\unexpanded{[#####1]}\robExt@arglist{#1}}
\def\robExt@arglist@D#1#2#3#4{\noexpand\unexpanded{#2#####1#3}\robExt@arglist{#1}}
\def\robExt@arglist@s#1{\noexpand\IfBooleanT{#####1}{*}\robExt@arglist{#1}}
\def\robExt@arglist@t#1#2{\noexpand\IfBooleanT{#####1}{#2}\robExt@arglist{#1}}
\csdef{robExt@arglist@+}#1{\expandafter\robExt@arglist\expandafter{\the\numexpr#1-1\relax}}%
\csdef{robExt@arglist@.}#1{}
% e,E: Embellishments are not supported.
% > Argument processors are not supported. And how could they be?
\def\robExt@arglist@error#1.{{Unknown argument type}}
%%%% _________________________________________________________________
%% Since the very first occurrence of D is not forwarded to the function
%% we discard it:
%% The first argument of \robExt@arglist@D is the number of the argument
%% so #2#####1#3 reads as #2 = <, #### = #, #1 = > #3 = default value that can be discarded since it is already part of the argument spec.
\def\robExt@arglist@D#1#2#3#4{%
\noexpand\unexpanded{%
\ifnum #1=1 % If it is the first argument D, then we do not add anything to the argument string
\else #2#####1#3\fi}\robExt@arglist{#1}%
}
%% Usage: \robExtCacheCommand[delimiters]{name command}[argument specification]{style}
%% Inspired and modified from
%% https://github.com/sasozivanovic/memoize/blob/81d960aa547148bdb38fea89917eda1476c9bace/memoize.sty#L744
\NewDocumentCommand{\robExtCacheCommand}{O{<>}mom}{%
%% We get the specification of the command, like "O{}mm"
\IfNoValueTF{#3}{%
\expandafter\GetDocumentCommandArgSpec\csname #2\endcsname%
}{%
\def\ArgumentSpecification{#3}%
}%
\edef\ArgumentSpecification{D#1{}\ArgumentSpecification}%
\robExtAddToCommandResetList{#2}%
% We copy the original definition for later (if externalization is disabled)
\expandafter\DeclareCommandCopy\csname robExtCommandOrig#2\expandafter\endcsname\csname #2\endcsname%
\edef\robExt@marshal{%
\noexpand\DeclareDocumentCommand%
\expandonce{\csname #2\endcsname}%
{\expandonce{\ArgumentSpecification}}%
{%
\noexpand\def\noexpand\robExtCommandOrigName{#2}%
% todo: add a hook for users setup; prevent user from changing \MemoizeWrapper?
\edef\noexpand\robExt@marshal{%
\noexpand\noexpand\noexpand\robExtRescanHashRobust{\noexpand\noexpand\noexpand\robExtCacheMe[\detokenize{#4}, \noexpand\detokenize{####1}]{%
\noexpand\noexpand\expandonce{\csname #2\endcsname}%
\robExtArgumentList%
}%
}%
}%
% For debug
%\noexpand\show\noexpand\robExt@marshal%
\noexpand\robExt@marshal%
}%
}%
% for debug
%\show\robExt@marshal%
\robExt@marshal%
}
\let\cacheCommand\robExtCacheCommand