% File: saveenv.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{saveenv}{2022/07/08}{0.0.0}{Save environment content verbatim} \RequirePackage{precattl} \msg_new:nnn {saveenv} {trailing-content-or-pretokenized} {Trailing~content~found~on~line,~or~content~pretokenized!} \msg_new:nnn {saveenv} {trailing-content-end-line} {Trailing~content~found~on~environment~last~line} \msg_new:nnn {saveenv} {currfile-package-not-loaded} {currfile~package~is~needed~for~this~functionality} \precattl_exec:n{ % args: char code, content, environment name \msg_new:nnn {saveenv} {leading-content-last-line} {Leading~content~found~on~\cO\\end{#3}~line,~first~char~code~=~#1,~content~=~'#2'} } \makeatletter \seq_new:N \_senv_lines \ior_new:N \_senv_file \precattl_exec:n { \NewDocumentEnvironment {saveenvghost} {m} { \edef \_senv_firstline {\the\inputlineno} } { \cs_if_free:NT \currfilename { \msg_error:nn {saveenv} {currfile-package-not-loaded} } \ior_open:Nn \_senv_file {\currfilename} \prg_replicate:nn {\_senv_firstline} { \ior_str_get:NN \_senv_file \_senv_line } \tl_build_gbegin:N #1 \prg_replicate:nn {\the\inputlineno-\_senv_firstline-2} { \ior_str_get:NN \_senv_file \_senv_line \exp_args:NNo \tl_build_gput_right:Nn #1 {\_senv_line \cO\^^J} } \ior_str_get:NN \_senv_file \_senv_line \exp_args:NNo \tl_build_gput_right:Nn #1 {\_senv_line} \ior_close:N \_senv_file \tl_build_gend:N #1 } % #1: target macro \NewDocumentEnvironment {saveenvkeeplast} {m} { \begingroup \edef \_senv_old_endlinechar {\the\endlinechar} \cctab_select:N \c_other_cctab % note that this changes the value of \endlinechar as well \int_compare:nNnTF {\_senv_old_endlinechar} < {0} { \_senv_start_get_body:N #1 } { \exp_last_unbraced:Nf \peek_meaning_remove:NTF { \char_generate:nn{\_senv_old_endlinechar}{12} } %12 is other { \_senv_start_get_body:N #1 } { \msg_error:nn {saveenv} {trailing-content-or-pretokenized} } } } { } % #1: target macro \cs_new_protected:Npn \_senv_start_get_body:N #1 { \endlinechar=10~ \str_set:NV \_senv_env \@currenvir \tl_replace_all:Nnn \_senv_env {~} {\cO\ } \exp_args:NNVV \_senv_helper #1 \_senv_env \@currenvir } % #1: target macro % #2: value of \@currenvir but with all tokens catcode 12 (other) % #3: value of \@currenvir \def \_senv_helper #1 #2 #3 { \cs_set_protected:cpn {[saveenv]~verbatim~body~scanner~for~#2} ##1 \cO{ \\end\{ } #2 \cO\} { % ##1: the body \peek_meaning_remove:NTF \cO\^^J { \endgroup \str_gset:Nn #1 {##1} \end{#3} } { \msg_error:nn {saveenv} {trailing-content-end-line} } } \use:c {[saveenv]~verbatim~body~scanner~for~#2} } % set variable #1 to have content of #2, but with last line dropped. % lines are separated by \^^J. \cs_new_protected:Npn \saveenv_set_drop_last:Nn #1 #2 { \tl_set:Nn \_senv_body {#2} \tl_replace_all:Nnn \_senv_body {~} {\cO\ } % keep spaces in seq_set_split (support older expl3 versions) \seq_set_split:NnV \_senv_lines {\cO\^^J} \_senv_body \seq_pop_right:NN \_senv_lines \_senv_lastline \tl_map_inline:Nn \_senv_lastline { % debug check, ensure last line is empty \int_case:nnF {`##1} { {32} {} %space {9} {} %tab } { \msg_error:nnoVV {saveenv} {leading-content-last-line} {\number`##1} \_senv_lastline \@currenvir } } \str_gset:Nx #1 {\seq_use:Nn \_senv_lines {\cO\^^J}} } \cs_generate_variant:Nn \saveenv_set_drop_last:Nn {NV} \NewDocumentEnvironment {saveenv} {m} { \saveenvkeeplast \_senv_content } { \endsaveenvkeeplast \saveenv_set_drop_last:NV #1 \_senv_content } } \cs_generate_variant:Nn \msg_error:nnnnn {nnoVV}