% ----------------------------------------------------------------------------
% the XSIMVERB package
% 
%   write environments verbatim to files
% 
% ----------------------------------------------------------------------------
% Clemens Niederberger
% Web:    https://github.com/cgnieder/xsim
% E-Mail: clemens@cnltx.de
% ----------------------------------------------------------------------------
% Copyright 2017--2022 Clemens Niederberger
% 
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3c
% of this license or (at your option) any later version.
% The latest version of this license is in
%   http://www.latex-project.org/lppl.txt
% and version 1.3c or later is part of all distributions of LaTeX
% version 2008/05/04 or later.
% 
% This work has the LPPL maintenance status `maintained'.
% 
% The Current Maintainer of this work is Clemens Niederberger.
% ----------------------------------------------------------------------------
% If you have any ideas, questions, suggestions or bugs to report, please
% feel free to contact me.
% ----------------------------------------------------------------------------
\RequirePackage {l3keys2e}
\ExplSyntaxOn

\tl_const:Nn \c_xsimverb_date_tl                 {2022/02/12}
\tl_const:Nn \c_xsimverb_version_major_number_tl {0}
\tl_const:Nn \c_xsimverb_version_minor_number_tl {4}
\tl_const:Nn \c_xsimverb_version_subrelease_tl   {}
\tl_const:Nx \c_xsimverb_version_number_tl
  {
    \c_xsimverb_version_major_number_tl .
    \c_xsimverb_version_minor_number_tl
  }
\tl_const:Nx \c_xsimverb_version_tl
  {
    \c_xsimverb_version_number_tl
    \c_xsimverb_version_subrelease_tl
  }
\tl_const:Nn \c_xsimverb_info_tl {write~ environments~ verbatim~ to~ files}

\ProvidesExplPackage
  {xsimverb}
  {\c_xsimverb_date_tl}
  {\c_xsimverb_version_tl}
  {\c_xsimverb_info_tl}

% options, information

\cs_new_protected:Npn \xsimverb_bool_provide:N #1
  { \bool_if_exist:NF #1 { \bool_new:N #1 } }
  
\xsimverb_bool_provide:N \g__xsim_final_bool
\xsimverb_bool_provide:N \g__xsim_verbose_bool
\xsimverb_bool_provide:N \g_xsim_clear_aux_bool
\xsimverb_bool_provide:N \g__xsim_write_to_file_bool
\xsimverb_bool_provide:N \g_xsim_use_aux_bool
\xsimverb_bool_provide:N \g__xsim_rerun_bool
\xsimverb_bool_provide:N \g__xsim_debug_bool
\xsimverb_bool_provide:N \g__xsim_blank_bool

\keys_define:nn {xsimverb}
  {
    final     .bool_gset:N   = \g__xsim_final_bool ,
    verbose   .bool_gset:N   = \g__xsim_verbose_bool ,
    debug     .bool_gset:N   = \g__xsim_debug_bool ,
    clear-aux .bool_gset:N   = \g_xsim_clear_aux_bool ,
    use-files .bool_gset:N   = \g__xsim_write_to_file_bool ,
    use-files .initial:n     = false ,
    no-files  .choice: ,
    no-files / true  .meta:n = { use-files = false } ,
    no-files / false .meta:n = { use-files = true } ,
    no-files  .default:n     = true ,
    use-aux   .bool_gset:N   = \g_xsim_use_aux_bool ,
    use-aux   .initial:n     = false ,
    blank     .bool_gset:N   = \g__xsim_blank_bool ,
    blank     .initial:n     = false
  }

\ProcessKeysPackageOptions {xsimverb}

\cs_if_exist:NF \xsim_if_final:T
  {
    \prg_new_conditional:Npnn \xsim_if_final: {p,T,F,TF}
      {
        \bool_if:NTF \g__xsim_final_bool
          { \prg_return_true: }
          { \prg_return_false: }
      }

    \prg_new_conditional:Npnn \xsim_if_verbose: {p,T,F,TF}
      {
        \bool_if:NTF \g__xsim_verbose_bool
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }

\cs_if_exist:NF \xsim_verbose:n
  {
    \cs_new_protected:Npn \xsim_verbose:n #1
      { \xsim_if_verbose:T { \msg_info:nnn {xsim} {verbose} {#1} } }
    \cs_generate_variant:Nn \xsim_verbose:n {x}
  }
  
% --------------------------------------------------------------------------
% #1: name
% #2: description
\cs_set_protected:Npn \XSIMmodule #1#2 {}
\cs_set_protected:Npn \XSIMmoduleend {}

% --------------------------------------------------------------------------
\XSIMmodule{verbwrite}{write contents of environments verbatim to files}

\int_new:N \l__xsimverb_tmpa_int
\tl_new:N  \l__xsimverb_tmpa_tl

\iow_new:N  \l__xsim_file_contents_iow
\tl_new:N   \l_xsim_file_begin_tl
\tl_new:N   \l_xsim_file_end_tl
\int_new:N  \l_xsim_line_gobble_int
\int_zero:N \l_xsim_line_gobble_int

\tl_const:Nx \c__xsim_backslash_char_tl { \cs_to_str:N \\ }

\group_begin:
  \char_set_catcode_other:n {37}
  \tl_const:Nn \c__xsim_percent_char_tl {%}
\group_end:

% ----------------------------------------------------------------------------
\bool_new:N \l__xsim_stream_open_bool
% the `final' option will prevent writing to files:
% #1: write stream
% #2: file name
\cs_new_protected:Npn \__xsim_open_stream:Nn #1#2
  {
    \file_if_exist:nTF {#2}
      {
        \xsim_if_final:F
          {
            \bool_set_true:N \l__xsim_stream_open_bool
            \iow_open:Nn #1 {#2}
          }
      }
      {
        \bool_set_true:N \l__xsim_stream_open_bool
        \iow_open:Nn #1 {#2}
      }
  }

% #1: write stream
\cs_new_protected:Npn \__xsim_close_stream:N #1
  { \bool_if:NT \l__xsim_stream_open_bool { \iow_close:N #1 } }

% #1: write stream
% #2: contents
\cs_new_protected:Npn \__xsim_write_to_stream:Nn #1#2
  {
    \xsim_if_final:F
      {
        % remove the /one/ space token with catcode 10 that is inserted if
        % no options are given to the surrounding environment:
        \tl_set:Nn \l__xsimverb_tmpa_tl {#2}
        \tl_remove_once:Nn \l__xsimverb_tmpa_tl {~}
        % \tl_show:N \l__xsimverb_tmpa_tl
        % \int_show:n { \tl_count:N \l__xsimverb_tmpa_tl }
        % \tl_analysis_show:N \l__xsimverb_tmpa_tl
        \int_zero:N \l__xsimverb_tmpa_int
        \int_while_do:nn { \l__xsimverb_tmpa_int < \l_xsim_line_gobble_int }
          {
            \int_incr:N \l__xsimverb_tmpa_int
            \tl_set:Nx \l__xsimverb_tmpa_tl { \tl_tail:N \l__xsimverb_tmpa_tl }
          }
        \iow_now:NV #1 \l__xsimverb_tmpa_tl
      }
  }
\cs_generate_variant:Nn \__xsim_write_to_stream:Nn {Nx}
\cs_generate_variant:Nn \iow_now:Nn {NV}

\cs_new:Npn \__xsim_tab: { \c_space_tl \c_space_tl }
\cs_new:Npn \__xsim_par: { ^^J ^^J }

% the following is inspired by the definition of the `filecontents'
% environment:
% #1: boolean - if true an active eol needs to be inserted before
%               starting to write
% #2: file name
\cs_new_protected:Npn \xsim_file_write_start:nn #1#2
  {
    % we need to insert an active ^^M if no options are given
    % see http://tex.stackexchange.com/q/9035/5049 reasons
    \use:nx
      { \__xsim_file_write_start:n {#2} }
      { \bool_if:nF {#1} { \exp_not:V \c__xsim_active_eol_tl } }
  }
\cs_generate_variant:Nn \xsim_file_write_start:nn {nV}
\cs_generate_variant:Nn \use:nn {nx}

\cs_new_protected:Npn \__xsim_set_verb_catcodes:
  {
    \seq_map_inline:Nn \l_char_special_seq
      { \char_set_catcode_other:N ##1 }
    \int_step_inline:nnnn {128} {1} {255}
      { \char_set_catcode_letter:n {##1} }
  }

\group_begin:
\char_set_catcode_active:n {13} % ^^M (carriage return, endlinechar)
\char_set_catcode_active:n {12} % ^^L (form feed)
\char_set_catcode_active:n {9}  % ^^I (horizontal tab)
%
\tl_const:Nn \c__xsim_active_eol_tl {^^M} %
%
% #1: file name
\cs_new_protected:Npn \__xsim_file_write_start:n #1 %
  { %
    \group_begin: %
    \xsim_if_final:TF %
      { \xsim_verbose:x { Not~ (re-)writing~ file~ `#1'. } } %
      { \xsim_verbose:x { (Re-)writing~ file~ `#1'. } } %
    \__xsim_open_stream:Nn \l__xsim_file_contents_iow {#1} %
    \tl_if_blank:VF \l_xsim_file_begin_tl %
      { %
        \xsim_if_final:F %
          { %
            \iow_now:Nx \l__xsim_file_contents_iow  %
              { \exp_not:V \l_xsim_file_begin_tl } %
          } %
      } %
    \__xsim_set_verb_catcodes: %
    \tl_set:Nx \l__xsimverb_tmpa_tl %
      { \c__xsim_backslash_char_tl end \cs_to_str:N \{ \@currenvir \cs_to_str:N \} } %
    \use:x %
      { %
        \cs_set:cpn {__xsim_tmpa:www} %
          ####1 \l__xsimverb_tmpa_tl %
          ####2 \l__xsimverb_tmpa_tl %
          ####3 \exp_not:N \q_stop: %
      } %
        { %
          \tl_if_blank:nTF {##3} %
            { \__xsim_write_to_stream:Nn \l__xsim_file_contents_iow {##1} } %
            { %
              \cs_set:Npx \__xsim_M:w { \exp_not:N \end {\@currenvir} } %
              \char_set_active_eq:nN {13} \__xsim_M:w %
              % the last line is `##1 \end{\@currenvir} ##2':
              \tl_if_blank:nF {##1} %
                { \__xsim_write_to_stream:Nn \l__xsim_file_contents_iow {##1} }%
              \tl_if_blank:nF {##2} %
                {} %
            } %
          ^^M %
        } %
    \char_set_catcode_active:n {13} %
    \char_set_catcode_active:n {12} %
    \char_set_catcode_active:n {9} %
    \cs_set:Npx \__xsim_M:w ##1 ^^M %
      { %
        \exp_not:N \__xsim_tmpa:www ##1 %
        \l__xsimverb_tmpa_tl %
        \l__xsimverb_tmpa_tl %
        \exp_not:N \q_stop: %
      } %
    \char_set_active_eq:nN {13} \__xsim_M:w %
    \char_set_active_eq:nN {12} \__xsim_par: %
    \char_set_active_eq:nN {9}  \__xsim_tab: %
  } %
\group_end:

\cs_new_protected:Npn \xsim_file_write_stop:
  {
    \tl_if_blank:VF \l_xsim_file_end_tl
      {
        \xsim_if_final:F
          {
            \iow_now:Nx \l__xsim_file_contents_iow
              { \exp_not:V \l_xsim_file_end_tl }
          }
      }
    \__xsim_close_stream:N \l__xsim_file_contents_iow
    \group_end:
  }

% ----------------------------------------------------------------------------
\cs_generate_variant:Nn \tl_set:Nn {Ne}

\NewDocumentCommand \XSIMfilewritestart {sm}
  {
    \IfBooleanTF {#1}
      { \xsim_file_write_start:nn { \c_false_bool } {#2} }
      { \xsim_file_write_start:nn { \c_true_bool } {#2} }
  }

\NewDocumentCommand \XSIMfilewritestop {}
  { \xsim_file_write_stop: }

\NewDocumentCommand \XSIMsetfilebegin {+m}
  { \tl_set:Nn \l_xsim_file_begin_tl {#1} }

\NewDocumentCommand \XSIMsetfilebeginX {+m}
  { \tl_set:Ne \l_xsim_file_begin_tl {#1} }

\NewDocumentCommand \XSIMsetfileend {+m}
  { \tl_set:Nn \l_xsim_file_end_tl {#1} }

\NewDocumentCommand \XSIMsetfileendX {+m}
  { \tl_set:Ne \l_xsim_file_end_tl {#1} }

\NewDocumentCommand \XSIMgobblechars {m}
  { \int_set:Nn \l_xsim_line_gobble_int {#1} }

\XSIMmoduleend