summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/latex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2023-09-06 20:16:21 +0000
committerKarl Berry <karl@freefriends.org>2023-09-06 20:16:21 +0000
commit1baf6c82b4305aa9a57963737c34592730e8fae8 (patch)
treec407e105363d1fed0bdeebc848a1f55db20e5c0c /Master/texmf-dist/tex/latex
parentab73056b3e49dc7d02167b961cbc92d353237e0d (diff)
robust-externalize (6sep23)
git-svn-id: svn://tug.org/texlive/trunk@68194 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/latex')
-rw-r--r--Master/texmf-dist/tex/latex/robust-externalize/robust-externalize.sty1302
1 files changed, 1302 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/latex/robust-externalize/robust-externalize.sty b/Master/texmf-dist/tex/latex/robust-externalize/robust-externalize.sty
new file mode 100644
index 00000000000..38f8c6ded53
--- /dev/null
+++ b/Master/texmf-dist/tex/latex/robust-externalize/robust-externalize.sty
@@ -0,0 +1,1302 @@
+\ProvidesPackage{robust-externalize}[1.0 Cache anything (tikz, latex, python) in a robust, efficient and pure way.]
+% todo: understand why python scripts with raise NameError("42") do not make latex crash.
+
+\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
+
+%% 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.
+% - create pre-made settings for tikz, tikz-cd, ...
+% - check compatibility with windows
+% - write documentation
+
+%%% Under the hood, this library is quite simple: each picture must, somehow, provide:
+%% - \l_robExt_final_file LaTeX3 string containing the content of the final file
+%% -
+%% Then, the library will hash everything to create a unique name (of the content, the template code, and the set of dependency filenames),
+%% it will create a file "MD5.tex" containing the pre-template+content+post-template, and it will compile it.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%% Utils %%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% Utils:
+% 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
+}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%% SCRIPTS %%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% Create scripts to remove useless files:
+%%% Note that we don't override the script if it exists on purpose (the user might have changed it to fits his needs)
+\begin{filecontents}[noheader]{robExt-remove-old-figures.py}
+#!/usr/bin/env python3
+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}
+
+\ExplSyntaxOn
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%% Paths %%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\def\robExtPrefixFilename{robExt-}
+
+\NewExpandableDocumentCommand{\robExtAddCachePathAndName}{m}{%
+ \ifdefined\robExtCacheFolder%
+ \robExtCacheFolder%
+ \fi\robExtPrefixFilename#1%
+}
+
+\NewExpandableDocumentCommand{\robExtAddCachePath}{m}{%
+ \ifdefined\robExtCacheFolder%
+ \robExtCacheFolder%
+ \fi#1%
+}
+
+
+\NewDocumentCommand{\robExtCheckIfPrefixFolderExists}{}{
+ % Check if the output directory exists
+ \ifdefined\robExtCacheFolder
+ \sys_if_shell_unrestricted:TF{
+ \ifdefined\robExtDoNotMkdirFolder\else
+ \ifdefined\robExtManualMode
+ \message{If ~ you ~ get~ an~ error,~ make ~ sure ~ to ~ create ~ the ~ folder ~ \robExtCacheFolder.}
+ \else
+ \sys_shell_now:x {mkdir ~ -p ~ \robExtCacheFolder}
+ \fi
+ \fi
+ }{
+ \message{If ~ you ~ get~ an~ error,~ make ~ sure ~ to ~ enable ~ pdflatex ~ -shell-escape ~ or ~ to ~ manually ~ create ~ the ~ folder ~ \robExtCacheFolder.}
+ }
+ \fi
+}
+
+\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 %%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\cs_generate_variant:Nn\seq_remove_all:Nn { NV }
+\cs_generate_variant:Nn\tl_rescan:nn { nv }
+\cs_generate_variant:Nn\tl_set_rescan:Nnn { Nnv }
+%\cs_generate_variant:Nn\tl_set_rescan:cnn { cnv }
+\cs_generate_variant:Nn \iow_now:Nn { NV }
+\cs_generate_variant:Nn \iow_now:Nn { Nx }
+\cs_generate_variant:Nn \iow_open:Nn { Nx }
+\cs_generate_variant:Nn \ior_str_get:NN { Nc }
+\cs_generate_variant:Nn \str_replace_all:Nnn { NnV }
+\cs_generate_variant:Nn \str_replace_all:Nnn { Nnx }
+\cs_generate_variant:Nn \str_replace_all:Nnn { Nnv }
+\cs_generate_variant:Nn \file_if_exist:nTF { xTF }
+\cs_generate_variant:Nn \str_set:Nn { NV }
+
+%% 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
+%% Create a file robExt-all-figures.txt with the list of .tex files
+\iow_open:Nx \g_robExt_write_list_all_figures {\jobname-\robExtAddPrefixName{all-figures.txt}}
+\iow_new:N \g_robExt_write_manually_compile_all_missing_figures
+\iow_open:Nx \g_robExt_write_manually_compile_all_missing_figures {\jobname-\robExtAddPrefixName{compile-missing-figures.sh}}
+% Contains the template:
+\str_new:N \l_robExt_template
+\str_new:N \l_robExt_final_file
+
+% Contains the list of dependency files (useful to compute the final md5sum)
+\seq_new:N \l_robExt_dependencies
+% 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
+% Contains the current compilation command (including the name of the file to compile).
+\str_new:N \l_robExt_currentCompilationCommand
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%% 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_placeholders_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_placeholders_seq
+
+\NewDocumentCommand{\robExtShowPlaceholder}{sm}{
+ \message{Placeholder ~ #2 ~ contains:^^J~ \use:c{l_robExt_placeholder_#2_str}}
+ \IfBooleanTF{#1}{\cs_show:c { l_robExt_placeholder_#2_str }}{}
+}
+
+\NewDocumentCommand{\robExtShowPlaceholders}{s}{
+ \message{List ~ of ~ placeholders:}
+ \seq_map_inline:Nn \l_robExt_placeholders_seq {\message{##1,}}
+ \IfBooleanTF{#1}{\cs_show:N \l_robExt_placeholders_seq}{}
+}
+
+\NewDocumentCommand{\robExtShowPlaceholdersContents}{s}{
+ \message{List ~ of ~ placeholders:}
+ \seq_map_inline:Nn \l_robExt_placeholders_seq {\robExtDebugPlaceholder{##1}}
+ \IfBooleanTF{#1}{\cs_show:N \l_robExt_placeholders_seq}{}
+}
+
+\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} { \par }
+ \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} { \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_placeholders_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
+
+\NewDocumentCommand{\robExtPrintAllPlaceholders}{}{
+ List ~ of ~ placeholders:\\
+ \seq_map_inline:Nn \l_robExt_placeholders_seq {- ~ Placeholder ~ called ~ \texttt{\tl_to_str:n {##1}} ~ contains: \robExtPrintPlaceholderNoReplacement{##1}}
+}
+\let\printAllPlaceholders\robExtPrintAllPlaceholders
+
+
+\NewDocumentCommand{\robExtEvalPlaceholderNoReplacement}{m}{
+ % \scantokens{\use:c{l_robExt_placeholder_#1_str}}
+ % 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
+ %\exp_args:Nx \scantokens {\use:c{l_robExt_placeholder_#1_str}}
+ \tl_rescan:nv {}{ l_robExt_placeholder_#1_str }
+ % \tl_rescan:nc { } { l_robExt_placeholder_#1_str }
+}
+\let\evalPlaceholderNoReplacement\robExtEvalPlaceholderNoReplacement
+
+\NewDocumentCommand{\robExtGetPlaceholderNoReplacement}{m}{
+ \str_use:c { l_robExt_placeholder_#1_str }
+}
+\let\getPlaceholderNoReplacement\robExtGetPlaceholderNoReplacement
+
+
+% Make sure that the placeholder is in the list \l_robExt_placeholders_seq.
+% This should automatically be called by other tools
+\NewDocumentCommand{\robExtAddPlaceholderToList}{m}{
+ % Make sure we have a string here:
+ \str_set: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_placeholders_seq \l_robExt_tmp_str
+ \seq_put_left:NV \l_robExt_placeholders_seq \l_robExt_tmp_str
+}
+
+\NewDocumentCommand{\robExtRemovePlaceholder}{m}{
+ % This seems to be required to ensure catcodes are correct before removing the elements:
+ \str_set:Nn \l_robExt_tmp_str { #1 }
+ \seq_remove_all:NV \l_robExt_placeholders_seq \l_robExt_tmp_str
+ \expandafter \let \csname l_robExt_placeholder_#1_str \endcsname \undefined
+}
+
+
+%% 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!
+\NewDocumentCommand{\robExtPlaceholderFromContent}{mm}{
+ \str_set:cn { l_robExt_placeholder_#1_str } {#2}
+ \robExtAddPlaceholderToList{#1}
+}
+\let\placeholderFromContent\robExtPlaceholderFromContent
+\let\robExtSetPlaceholder\robExtPlaceholderFromContent
+\let\setPlaceholder\robExtPlaceholderFromContent
+
+\NewDocumentCommand{\robExtCopyPlaceholder}{mm}{
+ \str_set_eq:cc { l_robExt_placeholder_#1_str } { l_robExt_placeholder_#2_str }
+ \robExtAddPlaceholderToList{#1}
+}
+\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 } {
+ \str_set: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
+ }{
+ \str_set:cn { l_robExt_placeholder_#2_str } {#3}
+ \robExtAddPlaceholderToList{#2}
+ }
+}
+\let\addToPlaceholder\robExtAddToPlaceholder
+
+%% 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 } {
+ \str_set: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
+ }{
+ \str_set:cn { l_robExt_placeholder_#2_str } {#3}
+ \robExtAddPlaceholderToList{#2}
+ }
+}
+\let\addBeforePlaceholder\robExtAddBeforePlaceholder
+
+
+% 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}{m}{%
+ % % 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{\jobname-robExt-tmp-file-you-can-remove.tmp}%
+}{%
+ \XSIMfilewritestop%
+ \ior_open:Nn \g_robExt_read_ior {\jobname-robExt-tmp-file-you-can-remove.tmp}%
+ %% Put the file in l_robExt_tmp_contain_file
+ \str_clear_new:N \l_robExt_tmp%
+ \ior_str_map_inline:Nn \g_robExt_read_ior {%
+ \str_gput_right:Nx \l_robExt_tmp {\tl_to_str:N{##1}^^J}%
+ }%
+ \str_set_eq:cN {l_robExt_placeholder_#1_str} \l_robExt_tmp%
+ \robExtAddPlaceholderToList{#1}%
+ %% Otherwise they will be lost when the environment ends
+ \robExtKeepaftergroup{l_robExt_placeholders_seq}%
+ %% for other variable
+ \robExtKeepaftergroup{l_robExt_placeholder_#1_str}%
+}%
+\let\PlaceholderFromCode\RobExtPlaceholderFromCode
+\let\endPlaceholderFromCode\endRobExtPlaceholderFromCode
+\let\SetPlaceholderCode\RobExtPlaceholderFromCode
+\let\endSetPlaceholderCode\endRobExtPlaceholderFromCode
+
+%% 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}{mm}{
+ \ior_open:Nn \g_robExt_read_ior {#2}
+ %% Put the file in l_robExt_tmp_contain_file
+ \str_clear_new:N \l_robExt_tmp_contain_file
+ \ior_str_map_inline:Nn \g_robExt_read_ior {
+ \str_put_right:Nx \l_robExt_tmp_contain_file {\tl_to_str:N{##1}^^J}
+ }
+ %% computes the new filename based on the md5
+ \str_clear_new:N \l_robExt_tmp_filename
+ \str_set:Nx \l_robExt_tmp_filename_no_prefix {\pdfmdfivesum{\l_robExt_tmp_contain_file}#2}
+ %% Writes the content to the file
+ \robExtCheckIfPrefixFolderExists
+ \iow_open:Nx \g_robExt_write_iow {\robExtAddCachePathAndName{\l_robExt_tmp_filename_no_prefix}}
+ \iow_now:NV \g_robExt_write_iow \l_robExt_tmp_contain_file
+ \iow_close:N \g_robExt_write_iow
+ %% sets the template name to the relative path to the file
+ \str_set:cx { l_robExt_placeholder_#1_str } {\robExtPrefixFilename\l_robExt_tmp_filename_no_prefix}
+ \robExtAddPlaceholderToList{#1}
+}
+\let\placeholderPathFromFilename\robExtPlaceholderPathFromFilename
+
+%% Usage:
+%% \placeholderFromFileContent{MYLIB}{mylib.py}
+%% This will set MYLIB to the content of the file mylib.py
+%% Tested!
+\NewDocumentCommand{\robExtPlaceholderFromFileContent}{mm}{
+ \ior_open:Nn \g_robExt_read_ior {#2}
+ %% Put the file in l_robExt_tmp_contain_file
+ \str_clear_new:N \l_robExt_tmp_contain_file
+ \ior_str_map_inline:Nn \g_robExt_read_ior {
+ \str_put_right:Nx \l_robExt_tmp_contain_file {\tl_to_str:N{##1}^^J}
+ }
+ %% sets the template name to the relative path to the file
+ \str_set_eq:cN { l_robExt_placeholder_#1_str } \l_robExt_tmp_contain_file
+ \robExtAddPlaceholderToList{#1}
+}
+\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}{mO{.tex}m}{
+ \str_set:Nn \l_robExt_tmp_contain_file {#3}
+ %% computes the new filename based on the md5
+ \str_clear_new:N \l_robExt_tmp_filename
+ \str_set:Nx \l_robExt_tmp_filename_no_prefix {\pdfmdfivesum{\l_robExt_tmp_contain_file}#2}
+ %% Writes the content to the file
+ \robExtCheckIfPrefixFolderExists
+ \iow_open:Nx \g_robExt_write_iow {\robExtAddCachePathAndName{\l_robExt_tmp_filename_no_prefix}}
+ \iow_now:NV \g_robExt_write_iow \l_robExt_tmp_contain_file
+ \iow_close:N \g_robExt_write_iow
+ %% sets the template name to the relative path to the file
+ \str_set:cx { l_robExt_placeholder_#1_str } {\robExtPrefixFilename\l_robExt_tmp_filename_no_prefix}
+ \robExtAddPlaceholderToList{#1}
+}
+\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}{O{}m}{
+ \XSIMfilewritestart*{\jobname-robExt-tmp-file-you-can-remove.tmp}
+}{
+ \XSIMfilewritestop
+ \ior_open:Nn \g_robExt_read_ior {\jobname-robExt-tmp-file-you-can-remove.tmp}
+ %% Put the file in \l_robExt_tmp_contain_file
+ \str_clear_new:N \l_robExt_tmp_contain_file
+ \ior_str_map_inline:Nn \g_robExt_read_ior {
+ \str_gput_right:Nx \l_robExt_tmp_contain_file {\tl_to_str:N{##1}^^J}
+ }
+ %% computes the new filename based on the md5
+ \str_clear_new:N \l_robExt_tmp_filename
+ \str_set:Nx \l_robExt_tmp_filename_no_prefix {\pdfmdfivesum{\l_robExt_tmp_contain_file}#1}
+ %% Writes the content to the file
+ \robExtCheckIfPrefixFolderExists
+ \iow_open:Nx \g_robExt_write_iow {\robExtAddCachePathAndName{\l_robExt_tmp_filename_no_prefix}}
+ \iow_now:NV \g_robExt_write_iow \l_robExt_tmp_contain_file
+ \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}
+ \robExtAddPlaceholderToList{#2}
+ %% Otherwise they will be lost when the environment ends
+ \robExtKeepaftergroup{l_robExt_placeholders_seq}
+ %% for other variable
+ \robExtKeepaftergroup{l_robExt_placeholder_#2_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 \__replace_until_impossible:N {
+ \str_set_eq:NN \l_robext_tmp_before \l_robExt_result_str
+ \seq_map_inline:Nn \l_robExt_placeholders_seq {
+ \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_until_impossible:N { }
+ }
+}
+
+\NewDocumentCommand{\robExtGetPlaceholderInResult}{O{}m}{
+ \str_set:Nn \l_robExt_result_str { #2 }
+ \__replace_until_impossible:N { }
+ \tl_if_blank:nTF {#1} {} {
+ % 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 { #1 }
+ \str_set_eq:cN { l_robExt_placeholder_#1_str } \l_robExt_result_str
+ \robExtAddPlaceholderToList{#1}
+ }
+}
+\let\getPlaceholderInResult\robExtGetPlaceholderInResult
+
+% Otherwise we need latex3
+\def\robExtResult{\l_robExt_result_str}
+
+\NewDocumentCommand{\robExtSetPlaceholderRec}{mm}{
+ \robExtGetPlaceholderInResult[#1]{#2}
+}
+\let\setPlaceholderRec\robExtSetPlaceholderRec
+
+\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}{
+ \str_set:Nn \l_test_str {#1}
+ \robExtGetPlaceholderInResult{#1}
+ \tl_rescan:nv {} { l_robExt_result_str }
+}
+\let\evalPlaceholder\robExtEvalPlaceholder
+
+\NewDocumentCommand{\robExtEvalPlaceholderInplace}{m}{
+ \robExtGetPlaceholderInResult{#1}
+ \tl_set_rescan:Nnx \l_robExt_tmp_tl {} { \l_robExt_result_str }
+ \str_set:cx { l_robExt_placeholder_#1_str } { \l_robExt_tmp_tl }
+}
+\let\evalPlaceholderInplace\robExtEvalPlaceholderInplace
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%% Dependencies %%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\NewDocumentCommand{\robExtResetDependencies}{m}{
+ \seq_clear:N \l_robExt_dependencies
+}
+
+\NewDocumentCommand{\robExtAddDependency}{m}{
+ \seq_put_left:Nx \l_robExt_dependencies {#1}
+}
+
+\NewDocumentCommand{\robExtDebugDependency}{}{
+ \show\l_robExt_dependencies
+}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%% 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}{
+ \robExtSetPlaceholder{__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}
+
+%%% \l_robExt_final_file must contain before calling this function the content of the final file.
+%%% \l_robExt_dependencies must contain the extensions (list).
+%%% \l_robExt_currentCompilationCommand contains the compilation command to use.
+%%% Note that we do note parse them as input to allow more flexibility on the way the user
+%%% defines them, and to limit issues with expansion.
+\NewDocumentCommand{\robExtWriteFile}{m}{
+ %%% First we get all dependencies stored in \l_robExt_dependencies to create a csv-like file:
+ \str_clear:N \l_robExt_dependencies_mdfive
+ %% Make sure to remove these placeholders as they should not be replaced.
+ %% Not that we cannot just give them their final value here, as it cannot yet be determined without
+ %% first computing the md5 hash.
+ \robExtRemovePlaceholder{__ROBEXT_SOURCE_FILE__}
+ \robExtRemovePlaceholder{__ROBEXT_OUTPUT_PDF__}
+ \robExtRemovePlaceholder{__ROBEXT_OUTPUT_PREFIX__}
+ \setPlaceholder{__ROBEXT_WAY_BACK__}{\robExtCacheFolderWayBack}
+ \robExtEvalPlaceholderInplace{__ROBEXT_WAY_BACK__}
+ \setPlaceholder{__ROBEXT_CACHE_FOLDER__}{\robExtCacheFolder}
+ \robExtEvalPlaceholderInplace{__ROBEXT_CACHE_FOLDER__}
+ %%% We rescan the string in order to evaluate stuff like \myframes into "12,45,56".
+ \robExtGetPlaceholderInResult{__ROBEXT_COMPILATION_COMMAND__}
+ \ifdefined\robExtDoNotRescanFirstTime
+ \str_set_eq:NN \l_robExt_currentCompilationCommand \l_robExt_result_str
+ \else
+ \tl_set_rescan:Nnx \l_robExt_currentCompilationCommand {} { \l_robExt_result_str }
+ \fi%
+ %% We get the template
+ \robExtGetPlaceholderInResult{__ROBEXT_TEMPLATE__}
+ \str_set_eq:NN \l_robExt_final_file_minus_hash_str \l_robExt_result_str
+ % 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 {command,\l_robExt_currentCompilationCommand^^J\pdfmdfivesum{\l_robExt_final_file_minus_hash_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_put_right:Nx \l_robExt_dependencies_mdfive {\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 {\pdfmdfivesum{\l_robExt_dependencies_mdfive^^J}}
+ %% We add the figure in the list of files.
+ \iow_now:Nx \g_robExt_write_list_all_figures {\robExtAddPrefixName{\robExtFinalHash.tex}^^J}
+ %% We can now set the placeholders, and recompute the final value of the file:
+ \robExtPlaceholderFromContent{__ROBEXT_SOURCE_FILE__}{\robExtAddPrefixName{\robExtFinalHash.tex}}
+ \robExtEvalPlaceholderInplace{__ROBEXT_SOURCE_FILE__}
+ \robExtPlaceholderFromContent{__ROBEXT_OUTPUT_PDF__}{\robExtAddPrefixName{\robExtFinalHash.pdf}}
+ \robExtEvalPlaceholderInplace{__ROBEXT_OUTPUT_PDF__}
+ \robExtPlaceholderFromContent{__ROBEXT_OUTPUT_PREFIX__}{\robExtAddPrefixName{\robExtFinalHash}}
+ \robExtEvalPlaceholderInplace{__ROBEXT_OUTPUT_PREFIX__}
+ \robExtGetPlaceholderInResult{__ROBEXT_TEMPLATE__}
+ \str_set_eq:NN \l_robExt_final_file_str \l_robExt_result_str
+ \file_if_exist:xTF{\robExtAddCachePathAndName{\robExtFinalHash.tex}}{
+ \message{The\space file\space \robExtAddCachePathAndName{\robExtFinalHash.tex} \space already\space exists.^^J}
+ }{
+ \str_if_empty:NTF \l_robExt_final_file_str {
+ \PackageError{robExt}{You ~ are ~ writing ~ an ~ empty ~ tex ~ file, ~ that ~ will ~ fail ~ to ~ compile. ~ Make ~ sure ~ you ~ defined ~ a ~ 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
+ \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_final_file_str
+ \iow_close:N \g_robExt_write_iow
+ \message{Source ~ saved ~ in ~ \robExtAddCachePathAndName{\robExtFinalHash.tex}.}
+ }
+ }
+}
+
+% 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}}{
+ \message{No ~ need ~ to ~ recompile ~ \robExtAddCachePathAndName{\robExtFinalHash.pdf}^^J}
+ }{
+ \robExtGetPlaceholderInResult{__ROBEXT_COMPILATION_COMMAND__}
+ \ifdefined\robExtDoNotRescanSecondTime
+ \str_set_eq:NN \l_robExt_finalCompilationCommand \l_robExt_result_str
+ \else
+ \tl_set_rescan:Nnx \l_robExt_finalCompilationCommand {} { \l_robExt_result_str }
+ \fi%
+ % Make sure this command is run from the cache folder
+ \ifdefined\robExtCacheFolder
+ \str_put_left:Nx \l_robExt_finalCompilationCommand {cd ~ \robExtCacheFolder \space && ~ }
+ \fi
+ \ifdefined\robExtManualMode
+ \message{[robExt] Manual mode enabled: please, manually compile the images using \l_robExt_finalCompilationCommand or run 'bash \jobname-\robExtAddPrefixName{compile-missing-figures.sh}'.}
+ \iow_now:Nx \g_robExt_write_manually_compile_all_missing_figures {\l_robExt_finalCompilationCommand^^J}
+ \else
+ \sys_if_shell_unrestricted:TF{
+ \message{[robExt] We ~ will ~ start ~ the ~ compilation using: ~ \l_robExt_finalCompilationCommand.}
+ \sys_shell_now:x {\l_robExt_finalCompilationCommand} % The ~ are used in ExplSyntaxOn to add space
+ }{
+ \PackageError{robExt}{You ~ need ~ to ~ compile ~ with ~ shell-escape ~ as ~ in: ~ "pdflatex ~ -shell-escape ~ yourfile.tex" ~ to ~ be ~ able ~ to ~ compile ~ automatically ~ the ~ figures}{}
+ }
+ \fi
+ }
+}
+
+\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}{%
+ \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.
+ }{}%
+ \ifdefined\robExtIncludeCommand%
+ \robExtIncludeCommand%
+ \else%
+ \evalPlaceholder{%
+ \ifdefined\robExtDepth%
+ \raisebox{-\robExtDepth}{%
+ \includegraphics[__ROBEXT_INCLUDEGRAPHICS_OPTIONS__]{%
+ __ROBEXT_INCLUDEGRAPHICS_FILE__%
+ }}%
+ \else%
+ \includegraphics[__ROBEXT_INCLUDEGRAPHICS_OPTIONS__]{%
+ \robExtAddCachePathAndName{\robExtFinalHash.pdf}}%
+ }%
+ \fi%
+ \fi%
+ }{
+ \ifdefined\robExtManualMode
+ \framebox[\linewidth]
+ {
+ \begin{minipage}{\linewidth}
+ \textbf{Draft ~ Mode: ~ you ~ are ~ in ~ manual ~ mode: ~ please ~ compile ~ \robExtAddCachePathAndName{\robExtFinalHash.tex} ~ via ~ ``\l_robExt_finalCompilationCommand'' ~ or ~ call ~ ``bash ~ \jobname-\robExtAddPrefixName{compile-missing-figures.sh'' ~ to ~ compile ~ all ~ missing ~ figures.}}
+ \end{minipage}
+ }
+ %\fbox{\textbf{Draft ~ Mode: ~ you ~ are ~ in ~ manual ~ mode:}\par\textbf{ ~ please ~ compile ~ \robExtAddCachePathAndName{\robExtFinalHash.tex} ~ or ~ use ~ \ifdefined\robExtCacheFolder cd \robExtCacheFolder; \fi bash ~ \jobname-\robExtAddPrefixName{compile-missing-figures.sh}}}
+ \message{[robExt] ~ You ~ are ~ in ~ manual ~ mode: ~ please ~ compile ~ yourself ~ \robExtAddCachePathAndName{\robExtFinalHash.tex} ~ or ~ use ~ the ~ bash ~ \jobname-\robExtAddPrefixName{compile-missing-figures.sh}}
+ \else
+ \PackageError{robExt}{For ~ an ~ unknown ~ reason ~ the ~ pdf ~ file ~ \robExtAddCachePathAndName{\robExtFinalHash.pdf} ~ is ~ not ~ present. ~ The ~ compilation ~ command ~ certainly ~ failed, ~ see ~ logs ~ above.}{}
+ \fi
+ }
+ }%
+ \fi%
+}
+
+
+\ExplSyntaxOff
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% 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={},
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %%% Interface to change placeholders %%%
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ set placeholder/.code 2 args={\robExtSetPlaceholder{#1}{#2}},
+ set main content/.style={
+ set placeholder={__ROBEXT_MAIN_CONTENT__}{#1}
+ },
+ copy placeholder/.code 2 args={\robExtCopyPlaceholder{#1}{#2}},
+ set placeholder rec/.code 2 args={\robExtSetPlaceholderRec{#1}{#2}},
+ set placeholder eval/.code 2 args={\robExtSetPlaceholderRec{#1}{#2}\robExtEvalPlaceholderInplace{#1}},
+ set placeholder from content/.code 2 args={\robExtPlaceholderFromContent{#1}{#2}},
+ add to placeholder/.code 2 args={\robExtAddToPlaceholder{#1}{#2}},
+ add to placeholder no space/.code 2 args={\robExtAddToPlaceholder*{#1}{#2}},
+ add before placeholder/.code 2 args={\robExtAddBeforePlaceholder{#1}{#2}},
+ add before placeholder no space/.code 2 args={\robExtAddBeforePlaceholder*{#1}{#2}},
+ set placeholder path from filename/.code 2 args={\robExtPlaceholderPathFromFilename{#1}{#2}},
+ set placeholder from file content/.code 2 args={\robExtPlaceholderFromFileContent{#1}{#2}},
+ set placeholder path from content/.code n args={3}{\robExtPlaceholderPathFromContent{#1}[#3]{#2}},
+ eval placeholder in place/.code={\robExtEvalPlaceholderInplace{#1}},
+ % Interface to set template
+ set template/.style={
+ set placeholder={__ROBEXT_TEMPLATE__}{#1},
+ },
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %%% 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 %%%
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%
+ set compilation command/.code={\robExtSetCompilationCommand{#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}},
+ %% 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},
+ %% 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 %%%
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %% 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/}{../}
+ set subfolder and way back/.code 2 args={\def\robExtCacheFolder{#1}\def\robExtCacheFolderWayBack{#2}},
+ % 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/}{../},
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %%% Disable externalization %%%
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %% Note: this does not work reliably for now
+ %% TODO: fix this!
+ disable externalization/.code={\def\robExtDisableExternalization{}},
+ enable externalization/.code={\let\robExtDisableExternalization\undefined},
+ % Useful to wrap, for instance, text
+ 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__}%
+ },
+ },
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %%% 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/.style={name output with ext={#1}{.pdf}},
+ % name input/.style={name output with ext={#1}{.tex}},
+ % % like name output but adds the extension like name output with ext={VideoA}{.mp4}
+ % name output with ext/.code 2 args={%
+ % \robExtSetPlaceholder{__#1__}{\robExtPrefixFilename\robExtFinalHash#2}
+ % \robExtEvalPlaceholderInplace{__#1__}
+ % \robExtSetPlaceholder{__#1_FULL_PATH__}{\robExtAddCachePathAndName{\robExtFinalHash#2}}
+ % \robExtEvalPlaceholderInplace{__#1_FULL PATH__}
+ % },
+ name output/.code={%
+ \def\robExtExecuteNamedOutput{%
+ \expandafter\xdef\csname #1\endcsname{\robExtPrefixFilename\robExtFinalHash}%
+ \expandafter\xdef\csname #1InCache\endcsname{\robExtAddCachePathAndName{\robExtFinalHash}}%
+ }%
+ },
+ %% Todo: for this to work, be will need to make the definition either global (not trivial, need to define
+ %% a new list of global placeholders), or at least go past the groups
+}
+
+% 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%
+ \ifdefined\robExtExecuteBefore\robExtExecuteBefore\fi%
+ \robExtWriteFile{}%
+ \robExtCompileFile{}%
+ \robExtIncludeFile{}%
+ \ifdefined\robExtExecuteNamedOutput\robExtExecuteNamedOutput\fi%
+ \ifdefined\robExtExecuteAfter\robExtExecuteAfter\fi%
+ \fi%
+}
+
+%% #1: Arguments, #2: content to externalize
+\NewDocumentCommand{\robExtCacheMe}{O{}m}{%
+ {% Group
+ \pgfkeys{%
+ /robExt/.cd,
+ set placeholder={__ROBEXT_MAIN_CONTENT__}{#2},
+ default style,
+ #1,
+ }%
+ \robExtEvaluateCompileAndInclude%
+ }%
+}
+\let\cacheMe\robExtCacheMe
+
+%% #1: Arguments, #2: content to externalize
+% \NewDocumentEnvironment{robExtern}{O{}+b}{%
+% \robExt[#1]{#2}%
+% }{}
+
+\NewDocumentEnvironment{RobExtCacheMe}{m+b}{%
+ \robExtCacheMe[#1]{#2}%
+}{}
+\let\CacheMe\RobExtCacheMe
+\let\endCacheMe\endRobExtCacheMe
+
+\NewDocumentEnvironment{RobExtCacheMeCode}{m}{%
+ \RobExtPlaceholderFromCode{__ROBEXT_MAIN_CONTENT__}%
+}{%
+ \endRobExtPlaceholderFromCode%
+ \pgfkeys{%
+ /robExt/.cd,
+ #1,
+ }%
+ \robExtEvaluateCompileAndInclude%
+}
+\let\CacheMeCode\RobExtCacheMeCode
+\let\endCacheMeCode\endRobExtCacheMeCode
+
+\NewDocumentEnvironment{RobExtCacheMeNoContent}{+b}{%
+ \robExtCacheMe[#1]{}%
+}{}
+\let\CacheMeNoContent\RobExtCacheMeNoContent
+\let\endCacheMeNoContent\endRobExtCacheMeNoContent
+
+\NewDocumentCommand{\robExtConfigure}{m}{%
+ \pgfkeys{
+ /robExt/.cd,#1%Do not add a space before the #1!
+ }%
+}
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%% Default presets %%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% We create here a few presets and placeholders useful later
+
+%%%% Available in all styles
+\robExtConfigure{
+ set includegraphics options/.style={
+ set placeholder={__ROBEXT_INCLUDEGRAPHICS_OPTIONS__}{#1},
+ },
+ add to includegraphics options/.style={
+ add to placeholder no space={__ROBEXT_INCLUDEGRAPHICS_OPTIONS__}{,#1},
+ },
+ set placeholder={__ROBEXT_VERBATIM_COMMAND__}{\verbatiminput},
+ % We expect the program to write in __ROBEXT_OUTPUT_PREFIX__-out.txt
+ verbatim output/.style={
+ custom include command={%
+ \evalPlaceholder{%
+ __ROBEXT_VERBATIM_COMMAND__{__ROBEXT_CACHE_FOLDER____ROBEXT_OUTPUT_PREFIX__-out.txt}%
+ }%
+ },
+ }
+}
+
+%%%% For LaTeX codes
+
+\setPlaceholder{__ROBEXT_LATEX_OPTIONS__}{}
+\setPlaceholder{__ROBEXT_DOCUMENT_CLASS__}{standalone}
+\setPlaceholder{__ROBEXT_PREAMBLE__}{}
+\setPlaceholder{__ROBEXT_INCLUDEGRAPHICS_OPTIONS__}{}
+\setPlaceholder{__ROBEXT_INCLUDEGRAPHICS_FILE__}{\robExtAddCachePathAndName{\robExtFinalHash.pdf}}
+\setPlaceholder{__ROBEXT_LATEX_TRIM_LENGTH__}{30cm}
+
+\begin{PlaceholderFromCode}{__ROBEXT_LATEX__}
+\documentclass[__ROBEXT_LATEX_OPTIONS__]{__ROBEXT_DOCUMENT_CLASS__}
+__ROBEXT_PREAMBLE__
+\begin{document}%
+__ROBEXT_MAIN_CONTENT_WRAPPED__
+\end{document}
+\end{PlaceholderFromCode}
+
+\begin{PlaceholderFromCode}{__ROBEXT_MAIN_CONTENT_WRAPPED__}
+__ROBEXT_CREATE_OUT_FILE__%
+\newsavebox\boxRobExt%
+\savebox{\boxRobExt}{%
+ __ROBEXT_MAIN_CONTENT__%
+}%
+\usebox{\boxRobExt}%
+__ROBEXT_WRITE_DEPTH_TO_OUT_FILE__%
+\end{PlaceholderFromCode}
+
+\begin{PlaceholderFromCode}{__ROBEXT_CREATE_OUT_FILE__}
+%% We save the height/depth of the content by using a savebox:
+\newwrite\writeRobExt%
+\immediate\openout\writeRobExt=\jobname-out.tex%
+\end{PlaceholderFromCode}
+
+\begin{PlaceholderFromCode}{__ROBEXT_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{PlaceholderFromCode}
+
+%% Compilation commands
+\setPlaceholder{__ROBEXT_COMPILATION_COMMAND_LATEX__}{__ROBEXT_LATEX_ENGINE__ __ROBEXT_COMPILATION_COMMAND_OPTIONS__ "__ROBEXT_SOURCE_FILE__"}
+\setPlaceholder{__ROBEXT_COMPILATION_COMMAND_OPTIONS__}{-shell-escape -halt-on-error}
+\setPlaceholder{__ROBEXT_LATEX_ENGINE__}{pdflatex}
+
+\robExtConfigure{
+ % some useful presets
+ latex/.style={
+ set template={__ROBEXT_LATEX__},
+ set compilation command={__ROBEXT_COMPILATION_COMMAND_LATEX__},
+ %% Configure the latex compilation engine
+ use latexmk/.style={
+ set placeholder={__ROBEXT_LATEX_ENGINE__}{latexmk},
+ },
+ 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_DOCUMENT_CLASS__}{##1},
+ },
+ set preamble/.style={
+ set placeholder={__ROBEXT_PREAMBLE__}{##1},
+ },
+ add to preamble/.style={
+ add to placeholder={__ROBEXT_PREAMBLE__}{##1},
+ },
+ do not wrap code/.style={
+ set placeholder={__ROBEXT_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}
+ },
+ },
+ tikz/.style={
+ latex,
+ add to preamble={\usepackage{tikz}},
+ add before placeholder no space={__ROBEXT_MAIN_CONTENT__}{\begin{tikzpicture}},
+ add to placeholder no space={__ROBEXT_MAIN_CONTENT__}{\end{tikzpicture}},
+ },
+}
+
+% %%%%%%% Integration with python
+
+
+\begin{PlaceholderFromCode}{__ROBEXT_PYTHON__}
+__ROBEXT_PYTHON_IMPORT__
+__ROBEXT_PYTHON_MAIN_CONTENT_WRAPPED__
+\end{PlaceholderFromCode}
+
+\setPlaceholder{__ROBEXT_PYTHON_IMPORT__}{}
+
+\begin{PlaceholderFromCode}{__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 an empty path
+ with open(get_filename_from_extension(".pdf"), 'w') as f:
+ pass
+
+### Starting main content
+__ROBEXT_MAIN_CONTENT__
+### Ending main content
+__ROBEXT_PYTHON_FINISHED_WITH_NO_ERROR__
+f_out_write.close()
+\end{PlaceholderFromCode}
+
+% It is annoying to manually call finished_with_no_error(), but it is handy to be able to disable it.
+\begin{PlaceholderFromCode}{__ROBEXT_PYTHON_FINISHED_WITH_NO_ERROR__}
+finished_with_no_error()
+\end{PlaceholderFromCode}
+
+%% 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.
+\setPlaceholder{__ROBEXT_PYTHON_EXEC__}{python}
+
+\robExtConfigure{
+ python/.style={
+ 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}
+ },
+ }
+}
+
+%% A style to print both the code and the result:
+\begin{PlaceholderFromCode}{__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{PlaceholderFromCode}
+
+
+\begin{PlaceholderFromCode}{__ROBEXT_PYTHON_PRINT_CODE_RESULT_TEMPLATE_AFTER__}
+### CODESTOPSHERE
+print_file.close()
+\end{PlaceholderFromCode}
+\setPlaceholder{__ROBEXT_PYTHON_TCOLORBOX_PROPS__}{colback=red!5!white,colframe=red!75!black}
+\setPlaceholder{__ROBEXT_PYTHON_CODE_MESSAGE__}{}
+\setPlaceholder{__ROBEXT_PYTHON_RESULT_MESSAGE__}{Output:}
+\setPlaceholder{__ROBEXT_PYTHON_LSTINPUT_STYLE__}{frame=single, breakindent=.5\textwidth, frame=single, breaklines=true, style=mypython}
+\robExtConfigure{
+ python print code and result/.style={
+ python,
+ 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__:
+ \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}
+ }
+ },
+ },
+}
+
+
+%%%% Verbatim text
+
+\robExtConfigure{
+ verbatim text/.style={
+ 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
+ set compilation command={echo "" > __ROBEXT_OUTPUT_PDF__},
+ },
+ verbatim text no include/.style={
+ verbatim text,
+ custom include command={\evalPlaceholder{%
+ \xdef\robExtPathToInput{\robExtAddCachePathAndName{\robExtFinalHash.tex}}%
+ }%
+ }%
+ },
+}
+
+%%%%% Bash
+
+\begin{PlaceholderFromCode}{__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
+touch "${outputPdf}"
+\end{PlaceholderFromCode}
+
+\setPlaceholder{__ROBEXT_BASH_SHELL__}{bash}
+
+\robExtConfigure{
+ bash/.style={
+ set compilation command={__ROBEXT_BASH_SHELL__ "__ROBEXT_SOURCE_FILE__"},
+ set template={__ROBEXT_BASH_TEMPLATE__},
+ print verbatim if no externalization,
+ }
+}
+
+
+%%%%% Replace tikzpicture:
+
+\NewDocumentCommand{\robExtExternalizeAllTikzpictures}{}{%
+ %% We need to save the tikzpicture environment to avoid infinite recursion if we disable externalization
+ \let\robExtTikzPictureOrig\tikzpicture%
+ \let\endrobExtTikzPictureOrig\endtikzpicture%
+ \DeclareDocumentEnvironment{tikzpicture}{O{}O{}b}{%
+ \begin{CacheMe}{tikz,##2}[##1]%
+ ##3%
+ \end{CacheMe}%
+ }{}%
+}
+
+% To avoid infinite recursion when using \robExtExternalizeAllTikzpictures, we need to redefine tikzpicture
+\NewDocumentCommand{\robExtDisableTikzpictureOverwrite}{}{%
+ \ifdefined\robExtTikzPictureOrig%
+ \let\tikzpicture\robExtTikzPictureOrig%
+ \let\endtikzpicture\endrobExtTikzPictureOrig%
+ \fi%
+}
+
+%% The cached version
+\DeclareDocumentEnvironment{tikzpictureC}{O{}O{}b}{%
+ \begin{CacheMe}{tikz,#2}[#1]%
+ #3%
+ \end{CacheMe}%
+}{}%
+