%D \module
%D   [     file=t-filter,
%D      version=2011.06.12,
%D        title=\CONTEXT\ User Module,
%D     subtitle=Filter,
%D       author=Aditya Mahajan,
%D         date=\currentdate,
%D    copyright=Aditya Mahajan,
%D        email=adityam <at> umich <dot> edu,
%D      license=Simplified BSD License]

\writestatus{loading}{ConTeXt User Module / Filter}

\ifx\undefined\normalexpanded \let\normalexpanded\expanded \fi

\startmodule    [filter]
\usemodule      [module-catcodes]

\unprotectmodulecatcodes


%D \section {Initialization}
%D
%D \subsubject {Interface}
%D
%D The first step is to set the interface variables. This allows me to use 
%D \type{\c!filter} etc. in the module definition, and thereby reduces the risk
%D of a typo. Currently, only English names are provided. If someone wants, I
%D can also add other multi-lingual names.

\startinterface all
    \setinterfaceconstant {filter}           {filter}
    \setinterfaceconstant {filtercommand}    {filtercommand}
    \setinterfaceconstant {output}           {output} 
    \setinterfaceconstant {read}             {read} 
    \setinterfaceconstant {readcommand}      {readcommand} 
\stopinterface

\def\m!externalfilter{t-filter}

%D \subsubject {Messages}

\setinterfacemessage{externalfilter}{title}     {\m!externalfilter}
\setinterfacemessage{externalfilter}{notfound}  {file -- cannot be found}
\setinterfacemessage{externalfilter}{missing}   {output file missing}
\setinterfacemessage{externalfilter}{forbidden} {Fatal Error: Cannot use absolute path -- as directory}
\setinterfacemessage{externalfilter}{slash}     {Appending / to directory -- }

%D \subsubject {Name space}

\def\externalfilter::id          {externalfilter}
\def\externalfilter::namespace   {@@@@\externalfilter::id}
\def\externalfilter::name        {}

\def\externalfilter::temp_prefix {temp}
\def\externalfilter::count{\externalfilter::namespace-\externalfilter::name-counter}

\installparameterhandler     \externalfilter::namespace \externalfilter::id
\installparameterhashhandler \externalfilter::namespace \externalfilter::id
\installsetuphandler         \externalfilter::namespace \externalfilter::id

\doifmode\s!mkiv
    {\installattributehandler \externalfilter::namespace \externalfilter::id}

% For backward compatibility
\let\setupexternalfilters \setupexternalfilter

%D \section {Tracing Macros}

\newif\iftraceexternalfilters

\let\traceexternalfilters\traceexternalfilterstrue

\starttexdefinition externalfilter::show_filenames
    \iftraceexternalfilters
        \writestatus\m!externalfilter{current filter : \externalfilter::name}
        \writestatus\m!externalfilter{base file      : \externalfilter::base_file}
        \writestatus\m!externalfilter{input file     : \externalfilter::input_file}
        \writestatus\m!externalfilter{output file    : \externalfilter::output_file}
    \fi
\stoptexdefinition

\starttexdefinition externalfilter::show_status #1
    \iftraceexternalfilters
        \writestatus\m!externalfilter{#1}
    \fi
\stoptexdefinition

\starttexdefinition externalfilter::show_filtercommand
    \writestatus\m!externalfilter{command : \externalfilterparameter\c!filtercommand}
\stoptexdefinition

%D \section {The main user macros}

%D \subsubject {Define a new filter}

\def\defineexternalfilter
  {\dodoubleargument\externalfilter::define}

\starttexdefinition externalfilter::define [#1][#2]
    \externalfilter::show_status{defining filter : #1}

    \edef\externalfilter::name{#1}
    \getparameters[\externalfilter::namespace#1][\s!parent=\externalfilter::namespace,#2]

    \doif{\externalfilterparameter\c!continue}\v!yes
         {\expandafter\newcounter\csname\externalfilter::count\endcsname}

    \setvalue{\e!start#1}{\bgroup\obeylines\dodoubleargument\externalfilter::start[#1]}
    \setvalue {\e!stop#1}{\externalfilter::process_filter}

    \setvalue{process#1file}{\dodoubleargument\externalfilter::process_file[#1]}
    \setvalue{inline#1}{\externalfilter::inline[#1]}
\stoptexdefinition

\starttexdefinition externalfilter::start [#1][#2] 
    % #1 = filter
    % #2 = options
    \egroup %\bgroup in \start#1 

    \edef\externalfilter::name{#1}

    \begingroup % to keep assignments local
    \getparameters[\externalfilter::namespace#1][\c!name=,#2]

    \externalfilter::set_filenames

    % Capture the contents of the buffer
    \dostartbuffer[\externalfilter::temp_file][\e!start#1][\e!stop#1]
\stoptexdefinition

\starttexdefinition externalfilter::process_file [#1][#2]#3
    \begingroup

    \edef\externalfilter::name{#1}
    \getparameters[\externalfilter::namespace#1][\c!name=,#2]

    \externalfilter::set_directory
    
    \edef\externalfilter::input_file{#3}
    \splitfilename{#3}
    %NOTE: \edef doesn not work because \splitoffname is not expandable
    \def\externalfilter::base_file   {\splitoffname}

    % The output is always in the directory specified by 
    % \c!directory; even if the input is from some other directory
    \def\externalfilter::output_file{\externalfilter::get_directory\externalfilterparameter\c!output}

    \externalfilter::show_filenames
    \externalfilter::execute_filter
    \externalfilter::read_processed_file

    \endgroup
\stoptexdefinition
   
\starttexdefinition externalfilter::inline [#1]
   \edef\externalfilter::name{#1}

   \begingroup % to keep assignments local
   \getparameters[\externalfilter::namespace#1][\c!name=]

   \externalfilter::set_filenames

   \pushcatcodetable
   \futurelet\next\externalfilter::inline_aux
\stoptexdefinition

%D \subsubject {Catcode tables}
%D 
%D Just to be sure, I define all catcode tables that are needed within the
%D module. Some of these are repetition of what is defined in ConTeXt, but the
%D internal names keep on changing which is a  maintenance nightmare.

\newcatcodetable \externalfilter::read_catcodes
\newcatcodetable \externalfilter::minimal_catcodes
\newcatcodetable \externalfilter::verb_catcodes

\startcatcodetable \externalfilter::read_catcodes % same as typcatcodesa
    \catcode\tabasciicode        = \othercatcode
    \catcode\endoflineasciicode  = \othercatcode
    \catcode\formfeedasciicode   = \othercatcode
    \catcode\spaceasciicode      = \othercatcode
    \catcode\endoffileasciicode  = \othercatcode
    \catcode\leftbraceasciicode  = \begingroupcatcode
    \catcode\rightbraceasciicode = \endgroupcatcode
\stopcatcodetable

\startcatcodetable \externalfilter::minimal_catcodes
    \catcode\backslashasciicode  = \escapecatcode
    \catcode\leftbraceasciicode  = \begingroupcatcode  
    \catcode\rightbraceasciicode = \endgroupcatcode
    \catcode\endoflineasciicode  = \activecatcode
    \catcode\formfeedasciicode   = \activecatcode
    \catcode\spaceasciicode      = \activecatcode
\stopcatcodetable 

\startcatcodetable \externalfilter::verb_catcodes % same as vrbcatcodes
    \catcode\tabasciicode       = \othercatcode
    \catcode\endoflineasciicode = \othercatcode
    \catcode\formfeedasciicode  = \othercatcode
    \catcode\spaceasciicode     = \othercatcode
    \catcode\endoffileasciicode = \othercatcode
\stopcatcodetable

%D \subsubject {Write argument to file verbatim}
%D
%D Surprisingly, there is nothing in the core to define a function that write its
%D argument to a file verbatim. I basically copied the \type{\type} macro.

\starttexdefinition externalfilter::inline_aux
   \ifx\next\bgroup
       \expandafter\externalfilter::inline_group
   \else
       \expandafter\externalfilter::inline_other
   \fi
\stoptexdefinition

\starttexdefinition externalfilter::inline_group
    \setcatcodetable \externalfilter::read_catcodes
    \externalfilter::process_inline
\stoptexdefinition

\starttexdefinition externalfilter::inline_other #1
    \setcatcodetable \externalfilter::verb_catcodes

    \def\next##1#1{\externalfilter::process_inline{##1}}
    \next
\stoptexdefinition

\newwrite\externalfilter::write

\starttexdefinition externalfilter::process_inline #1
    \immediate\openout \externalfilter::write\externalfilter::input_file
    \immediate\write   \externalfilter::write{\detokenize{#1}}
    \immediate\closeout\externalfilter::write

    \popcatcodetable

    \externalfilter::execute_filter
    \endlinechar\minusone %to prevent line break after reading file
    \externalfilter::read_processed_file

    % Finalization
    \doif{\externalfilterparameter\c!continue}\v!yes
         {\doglobal\expandafter\increment\csname\externalfilter::count\endcsname}
    \endgroup
\stoptexdefinition


%D \section {Helper Functions}
%D
%D \subsubject {First and last character of a string}

\def\externalfilter::get_first_character#1%
  {\externalfilter::get_first_character_aux#1\relax}

\def\externalfilter::get_first_character_aux#1#2\relax{#1}

\def\externalfilter::get_last_character#1%
  {\@EA\externalfilter::get_last_character_aux#1\relax}

\def\externalfilter::get_last_character_aux#1#2%
  {\ifx#2\relax#1\else\@EA\externalfilter::get_last_character_aux\@EA#2\fi}

%D \subsubject {Set the name of output directory}

\starttexdefinition externalfilter::set_directory
    \edef\externalfilter::get_directory{\externalfilterparameter\c!directory}
    \doifsomething{\externalfilter::get_directory}\externalfilter::set_directory_aux
\stoptexdefinition
   
\starttexdefinition externalfilter::set_directory_aux
    \doif{\externalfilter::get_first_character\externalfilter::get_directory}{/}
        {\writeline
         \showmessage\externalfilter::id{forbidden}\externalfilter::get_directory
         \batchmode
         \errmessage{}
         \normalend}

    \doifnot{\externalfilter::get_last_character\externalfilter::get_directory}{/}
        {\showmessage\externalfilter::id{slash}\externalfilter::get_directory
         \edef\externalfilter::get_directory{\externalfilter::get_directory/}}
\stoptexdefinition
  


%D \subsubject {Set file names}
%D
%D \type{\externalfilter::base_file} is the name of the temporary file without
%D extension. Its actual value depends on the state of \type{continue} key as
%D well as the value of \type{name} key.

\starttexdefinition externalfilter::set_filenames
   \externalfilter::set_directory

   % Set the name of temp file for the filter
   \doifelse{\externalfilterparameter\c!continue}\v!yes
        {\edef\externalfilter::temp_file{\externalfilter::temp_prefix-\externalfilter::name-\csname\externalfilter::count\endcsname}}
        {\edef\externalfilter::temp_file{\externalfilter::temp_prefix-\externalfilter::name}}
   \doifsomething{\externalfilterparameter\c!name}
        {\edef\externalfilter::temp_file{\externalfilter::temp_prefix-\externalfilter::name-\externalfilterparameter\c!name}}

   % The following  macros are useful for filter= and filtercommand= options
   % The basename of the external file 
   \edef\externalfilter::base_file  {\jobname-\externalfilter::temp_file}

   % In MkII, the buffer output is written to \TEXbufferfile{buffername} where
   % the macro \TEXbufferfile is defined as
   %
   %        \def\TEXbufferfile   #1{\bufferprefix#1.\f!temporaryextension}
   %
   % We redefine bufferprefix to include the directory name.
   \doifmode{\s!mkii}
        {\edef\bufferprefix{\externalfilter::get_directory\jobname-}}

   % In MkIV, we do not need to do such jugglary, because we can specify the
   % name of the file where the buffer has to be saved. This file is
   % \externalfilter::input_file (because it is the input to the filter).
   \edef\externalfilter::input_file {\externalfilter::get_directory\externalfilter::base_file.\f!temporaryextension}

   % Append directory name to the name of the output file
   \edef\externalfilter::output_file{\externalfilter::get_directory\externalfilterparameter\c!output}
   \externalfilter::show_filenames
\stoptexdefinition


%D \subsubject {Process Filter}
%D
%D Execute filter, read the output and do book-keeping if needed.

\starttexdefinition externalfilter::process_filter
     % By defualt, buffers are in memory in MkIV. So, we save them to disk
     \doifmode\s!mkiv{\savebuffer[\externalfilter::temp_file][\externalfilter::input_file]}

     \externalfilter::execute_filter
     \externalfilter::read_processed_file
     \endgroup 

     % Finalization
     \doif{\externalfilterparameter\c!continue}\v!yes
          {\doglobal\expandafter\increment\csname\externalfilter::count\endcsname}

     \expanded{\checknextindentation[\externalfilterparameter\c!indentnext]}
     \dorechecknextindentation
\stoptexdefinition

%D \subsubject {Execute Filter}   

\starttexdefinition externalfilter::execute_filter

   \externalfilter::show_filtercommand

   \doifelse{\externalfilterparameter\c!continue}\v!yes
       {\doifmode{*first}
           {\executesystemcommand
            {mtxrun --ifchanged=\externalfilter::input_file\space 
                --direct \externalfilterparameter\c!filtercommand}}}
       {\executesystemcommand
          {\externalfilterparameter\c!filtercommand}}
\stoptexdefinition

%D \subsubject {Read output}

\starttexdefinition externalfilter::read_processed_file
    \doif{\externalfilterparameter\c!read}\v!yes
       {\doiffileelse{\externalfilter::output_file}
           {\externalfilter::read_processed_file_aux}
           {\showmessage\externalfilter::id{notfound}\externalfilter::output_file 
            \blank
              {\tttf [[\getmessage\externalfilter::id{missing}]]}
            \blank}}
\stoptexdefinition

\starttexdefinition externalfilter::read_processed_file_aux
   \externalfilterparameter\c!before

   \begingroup
   \doifmode\s!mkiv
       {\dosetexternalfilterattributes\c!style\c!color}

   \processcommacommand[\externalfilterparameter\c!setups]\directsetup
   \externalfilterparameter\c!readcommand\externalfilter::output_file
   \endgroup

   \externalfilterparameter\c!after
\stoptexdefinition

%D \section {Default Values}

\setupexternalfilters
  [
   \c!before=,
   \c!after=,
   \c!style=,
   \c!color=,
   \c!indentnext=\v!auto,
   \c!setups=,
   \c!continue=\v!no,
   \c!read=\v!yes,
   \c!readcommand=\ReadFile,
   \c!directory=,
   \c!output=\externalfilterbasefile.tex,
   \c!filter=,
   \c!filtercommand={\externalfilterparameter\c!filter\space \externalfilter::input_file},
 ]

\def\externalfilterbasefile  {\externalfilter::base_file}
\def\externalfilterinputfile {\externalfilter::input_file}
\def\externalfilteroutputfile{\externalfilter::output_file}
\def\currentexternalfilter   {\externalfilter::name}
   
\protectmodulecatcodes
\stopmodule
