summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/pythonimmediate/pythonimmediate.sty
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/pythonimmediate/pythonimmediate.sty')
-rw-r--r--macros/latex/contrib/pythonimmediate/pythonimmediate.sty183
1 files changed, 183 insertions, 0 deletions
diff --git a/macros/latex/contrib/pythonimmediate/pythonimmediate.sty b/macros/latex/contrib/pythonimmediate/pythonimmediate.sty
new file mode 100644
index 0000000000..6ed72780bf
--- /dev/null
+++ b/macros/latex/contrib/pythonimmediate/pythonimmediate.sty
@@ -0,0 +1,183 @@
+% File: pythonimmediate.sty
+% Copyright 2022 user202729
+%
+% This work may be distributed and/or modified under the conditions of the
+% LaTeX Project Public License (LPPL), either version 1.3c of this license or
+% (at your option) any later version. The latest version of this license is in
+% the file:
+%
+% http://www.latex-project.org/lppl.txt
+%
+% This work has the LPPL maintenance status `maintained'.
+%
+% The Current Maintainer of this work is user202729.
+
+\ProvidesExplPackage{pythonimmediate}{2022/12/23}{0.0.0}{Library to run Python code}
+
+\RequirePackage{saveenv}
+\RequirePackage{currfile}
+\RequirePackage{l3keys2e}
+\RequirePackage{precattl}
+
+
+\cs_generate_variant:Nn \str_set:Nn {NV}
+\cs_generate_variant:Nn \str_if_eq:nnT {VnT}
+\cs_generate_variant:Nn \msg_error:nnn {nnV}
+\cs_generate_variant:Nn \str_if_eq:nnF {xnF}
+\cs_generate_variant:Nn \str_range:nnn {Vnn}
+\cs_generate_variant:Nn \str_if_eq:nnF {VnF}
+\cs_generate_variant:Nn \str_if_in:nnF {VnF}
+\cs_generate_variant:Nn \tl_build_put_right:Nn {NV}
+
+%\bench before~rescan.
+%\bench after~rescan.
+
+%\GenerateVariantsFile:n{\rescansynclastfilename}
+
+\str_set:Nn \_pythonimmediate_outputdir{.}
+\tl_set:Nn \_pythonimmediate_textopy_script_path{}
+\str_set:Nn \_pythonimmediate_mode{multiprocessing-network}
+\str_set:Nn \_pythonimmediate_python_executable{python3}
+\keys_define:nn{pythonimmediate}{
+ outputdir.tl_set_x:N=\_pythonimmediate_outputdir,
+ %outputdir.default:n={.}, % huh does not work?
+
+ mode.tl_set:N=\_pythonimmediate_mode,
+ scriptpath.tl_set_x:N=\_pythonimmediate_textopy_script_path,
+
+ python-executable.tl_set:N=\_pythonimmediate_python_executable,
+}
+\ProcessKeysOptions{pythonimmediate}
+
+\str_set:NV \_pythonimmediate_outputdir \_pythonimmediate_outputdir
+\str_set:NV \_pythonimmediate_mode \_pythonimmediate_mode
+\str_set:NV \_pythonimmediate_textopy_script_path \_pythonimmediate_textopy_script_path
+
+\msg_new:nnn {pythonimmediate} {shell-fail} {Shell~command~execution~failed!}
+\msg_new:nnn {pythonimmediate} {cannot-determine-script-path} {Cannot~determine~script~path!}
+\msg_new:nnn {pythonimmediate} {cannot-read-line} {Cannot~read~line!}
+\msg_new:nnn {pythonimmediate} {internal-error} {Internal~error!}
+\msg_new:nnn {pythonimmediate} {invalid-mode} {Invalid~mode:~'#1'.}
+
+
+\bool_new:N \_pythonimmediate_mode_multiprocessing_network
+\bool_new:N \_pythonimmediate_mode_unnamed_pipe
+\str_if_eq:VnT \_pythonimmediate_mode {multiprocessing-network} {\bool_set_true:N \_pythonimmediate_mode_multiprocessing_network}
+\str_if_eq:VnT \_pythonimmediate_mode {unnamed-pipe} {\bool_set_true:N \_pythonimmediate_mode_unnamed_pipe}
+\bool_if:nF {\_pythonimmediate_mode_multiprocessing_network || \_pythonimmediate_mode_unnamed_pipe} {
+ \msg_error:nnV {pythonimmediate} {invalid-mode} \_pythonimmediate_mode
+}
+
+% we need to persistently open the file anyway, so using LaTeX3 stream reference counting doesn't help
+\newread \_pythonimmediate_read_file
+
+
+
+
+%\bench before~setup.
+
+\begingroup
+ \endlinechar=-1~
+ \tl_if_empty:NT \_pythonimmediate_textopy_script_path {
+ % ======== first use kpsewhich to get the _pythonimmediate_script_path here ========
+ % (abuse \_pythonimmediate_read_file variable for this purpose)
+ \openin \_pythonimmediate_read_file=|"kpsewhich~ pythonimmediate_script_textopy.py"~
+ \readline \_pythonimmediate_read_file to \_pythonimmediate_textopy_script_path
+ %\bench after~get~textopy~path.
+ \ifeof \_pythonimmediate_read_file
+ \msg_error:nn {pythonimmediate} {cannot-determine-script-path}
+ \fi
+ }
+
+ \str_if_eq:xnF {\str_range:Vnn \_pythonimmediate_textopy_script_path {-10} {-1}} {textopy.py} {
+ \msg_error:nn {pythonimmediate} {cannot-determine-script-path}
+ }
+
+ \newwrite \_pythonimmediate_write_file
+
+ %\bench before~openin.
+
+ % ======== open persistent pipes to the child processes
+ \bool_if:NTF \_pythonimmediate_mode_unnamed_pipe { % in this case make sure the pipe remains open...
+ \openin \_pythonimmediate_read_file=|"sleep~ 0.5s|\_pythonimmediate_python_executable \space \str_range:Vnn \_pythonimmediate_textopy_script_path {1} {-11} pytotex.py \space \_pythonimmediate_mode"~ % we must use the primitive here to use the pipe file path
+ % TODO sleep infinity causes some resource leak (the process will not exit after TeX exits). Need to fix
+ % but sleep too little might be problematic that it exits before the setup is done
+ % we can just assume machines are not that slow
+ } {
+ \openin \_pythonimmediate_read_file=|"\_pythonimmediate_python_executable \space \str_range:Vnn \_pythonimmediate_textopy_script_path {1} {-11} pytotex.py \space \_pythonimmediate_mode"~ % we must use the primitive here to use the pipe file path
+ }
+
+ %\bench after~openin.
+
+ %\bench before~openout.
+ \immediate\openout \_pythonimmediate_write_file=|"\_pythonimmediate_python_executable \space \_pythonimmediate_textopy_script_path \space \_pythonimmediate_mode"~
+ % both processes must be before the \readline above so that the 2 Python processes are started "in parallel"
+ %\bench after~openout.
+
+ \readline \_pythonimmediate_read_file to \_pythonimmediate_dummy_line % endlinechar still -1
+ %\bench after~read~line.
+ \bool_if:NT \_pythonimmediate_mode_multiprocessing_network {
+ \str_if_eq:VnF \_pythonimmediate_dummy_line {listener-setup-done} {
+ \msg_error:nn {pythonimmediate} {cannot-read-line}
+ }
+ }
+ \bool_if:NT \_pythonimmediate_mode_unnamed_pipe {
+ \str_if_in:VnF \_pythonimmediate_dummy_line {pytotex_pid=} {
+ \msg_error:nn {pythonimmediate} {cannot-read-line}
+ }
+ }
+ \bool_if:NT \_pythonimmediate_mode_unnamed_pipe {
+ \immediate\write\_pythonimmediate_write_file {\_pythonimmediate_dummy_line} % which contains pytotex's pid
+ }
+\endgroup
+
+%\bench after~setup.
+
+
+% read one block of \TeX\ code from Python, store into the specified variable
+% the block is delimited using |surround_delimiter()| in Python i.e. the first and last line are identical
+% new lines are represented with ^^J
+\cs_new_protected:Npn \_pythonimmediate_gread_block:N #1 {
+ \begingroup
+ \endlinechar=10~ % affect \readline
+ \readline \_pythonimmediate_read_file to \_pythonimmediate_delimiter
+
+ \tl_build_gbegin:N #1
+ \readline \_pythonimmediate_read_file to \_pythonimmediate_line
+
+ %\bench read~first~line.
+
+ \bool_do_until:nn {\tl_if_eq_p:NN \_pythonimmediate_delimiter \_pythonimmediate_line} {
+ \tl_build_gput_right:NV #1 \_pythonimmediate_line
+ \ifeof \_pythonimmediate_read_file
+ \msg_error:nn {pythonimmediate} {internal-error}
+ \fi
+ \readline \_pythonimmediate_read_file to \_pythonimmediate_line
+ }
+ \tl_build_gend:N #1
+ \endgroup
+}
+\cs_generate_variant:Nn \tl_build_gput_right:Nn {NV}
+
+\cs_new_protected:Npn \_pythonimmediate_read_block:N #1 {
+ \_pythonimmediate_gread_block:N \_pythonimmediate_block
+ \tl_set_eq:NN #1 \_pythonimmediate_block
+}
+
+
+% read one block of \TeX\ code from Python and |\scantokens|-run it
+% the content inside is the actual TeX code to be executed
+\cs_new_protected:Npn \_pythonimmediate_run_block: {
+ \_pythonimmediate_gread_block:N \_pythonimmediate_code
+ \begingroup
+ \newlinechar=10~
+ \expandafter
+ \endgroup
+ \scantokens \expandafter{\_pythonimmediate_code}
+} % trick described in https://tex.stackexchange.com/q/640274 to scantokens the code with \newlinechar=10
+
+% bootstrap code
+%\bench before~bootstrap.
+\_pythonimmediate_run_block:
+%\bench after~bootstrap.
+