From da0bd16b0ba9d42d044af47137003788f0b7f773 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Sat, 24 Dec 2022 03:01:20 +0000 Subject: CTAN sync 202212240301 --- .../contrib/pythonimmediate/pythonimmediate.sty | 183 +++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 macros/latex/contrib/pythonimmediate/pythonimmediate.sty (limited to 'macros/latex/contrib/pythonimmediate/pythonimmediate.sty') 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. + -- cgit v1.2.3