diff options
Diffstat (limited to 'Master/texmf-dist/tex/latex')
-rw-r--r-- | Master/texmf-dist/tex/latex/robust-externalize/robust-externalize.sty | 1302 |
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}% +}{}% + |