%D \module %D [ file=t-filter, %D version=2020.06.29, %D title=\CONTEXT\ User Module, %D subtitle=Filter, %D author=Aditya Mahajan, %D date=\currentdate, %D copyright=Aditya Mahajan, %D email=adityam ieee org, %D license=Simplified BSD License] \writestatus{loading}{Filter (ver: 2020.06.29)} \startmodule [filter] \usemodule [module-catcodes] \unprotect % \f!temporaryextension was removed from the core in July 2012. \ifx\f!temporaryextension\undefined \def\f!temporaryextension{tmp} \fi %D \subject {Interface} %D %D Using interface constants allows one to use \type{\c!filter} etc. in %D the module definition, and thereby reduces the risk of a typo. %D Currently, only English names are provided. If someone wants a %D multi-lingual interface, let me know and I will add other language %D names as well, \startinterface all \setinterfaceconstant {filter} {filter} \setinterfaceconstant {filtercommand} {filtercommand} \setinterfaceconstant {output} {output} \setinterfaceconstant {purge} {purge} \setinterfaceconstant {read} {read} \setinterfaceconstant {write} {write} \setinterfaceconstant {readcommand} {readcommand} \setinterfaceconstant {taglabel} {taglabel} \setinterfaceconstant {tagdetail} {tagdetail} \stopinterface %D \subject {Name space} %D %D We use logical names to easily catch typos. \def\externalfilter@id {externalfilter} \def\externalfilter@temp_prefix {temp} \def\externalfilter@count {\????externalfilter-\currentexternalfilter-counter} \definenamespace [externalfilter] [\c!type=module, \c!name=externalfilter, \c!command=\v!yes, setup=\v!list, \c!style=\v!yes, \s!parent=externalfilter] \appendtoks \externalfilter@show_status{defining filter : \currentexternalfilter}% \doifinset{\externalfilterparameter\c!cache}{\v!yes,\v!force} {\ifcsname\externalfilter@count\endcsname \else \expandafter\newcounter\csname\externalfilter@count\endcsname\fi}% \setuevalue{\e!start\currentexternalfilter}{\externalfilter@start[\currentexternalfilter]}% \setuvalue {\e!stop\currentexternalfilter}{}% \setuevalue{process\currentexternalfilter file}{\externalfilter@process_file[\currentexternalfilter]}% \setuevalue{process\currentexternalfilter buffer}{\externalfilter@process_buffer[\currentexternalfilter]}% \setuevalue{inline\currentexternalfilter}{\externalfilter@inline[\currentexternalfilter]}% \setelementbackendtag[\externalfilterparameter\c!taglabel] \setelementnature [\externalfilterparameter\c!taglabel][display] \setelementbackendtag[inline\externalfilterparameter\c!taglabel] \setelementnature [inline\externalfilterparameter\c!taglabel][inline] \to \everydefineexternalfilter % For backward compatibility \let\setupexternalfilters \setupexternalfilter %D \subject {Messages} \def\m!externalfilter{t-filter} \setinterfacemessage\m!externalfilter{title} {\m!externalfilter} \setinterfacemessage\m!externalfilter{notfound} {file -- cannot be found} \setinterfacemessage\m!externalfilter{missing} {output file missing} \setinterfacemessage\m!externalfilter{missing_cache} {cached output file -- missing. Rerunning filter} \setinterfacemessage\m!externalfilter{forbidden} {Fatal Error: Cannot use absolute path -- as directory} \setinterfacemessage\m!externalfilter{unwriteable} {Fatal Error: Cannot write to file --} \setinterfacemessage\m!externalfilter{slash} {Appending / to directory -- } \setinterfacemessage\m!externalfilter{reuse} {\c!state=\v!stop : Not running filter on file --} \setinterfacemessage\m!externalfilter{writedisabled} {\c!write=\v!no : Not writing output for filter --} %D \subject {Tracing Macros} \newif\iftraceexternalfilters \unexpanded\def\traceexternalfilters {\traceexternalfilterstrue \enabletrackers[graphic.runfile]} \starttexdefinition externalfilter@show_filenames \writestatus\m!externalfilter{current filter : \currentexternalfilter} \writestatus\m!externalfilter{base file : \externalfilter@base_file} \writestatus\m!externalfilter{input file : \externalfilter@input_file} \writestatus\m!externalfilter{output file : \externalfilter@output_file} \stoptexdefinition \starttexdefinition externalfilter@show_status #1 \iftraceexternalfilters \writestatus\m!externalfilter{#1} \fi \stoptexdefinition \starttexdefinition externalfilter@show_filtercommand \writestatus\m!externalfilter{command : \externalfilterparameter\c!filtercommand} \writestatus\m!externalfilter{state : \externalfilterparameter\c!state} \stoptexdefinition %D \section {The main user macros} \unexpanded\def\externalfilter@start {\bgroup\obeylines\dodoubleargument\externalfilter@start_indeed} \starttexdefinition externalfilter@start_indeed [#1][#2] % #1 = filter % #2 = options \egroup %\bgroup in \externalfilter@start \begingroup % to keep assignments local \edef\currentexternalfilter{#1} \setupexternalfilter[#1][\c!name=,#2] \externalfilter@set_filenames % Capture the contents of the buffer \edef\p_strip{\getvalue{\externalfilter@id-\c!strip-\externalfilterparameter\c!strip}} \buff_pickup{\externalfilter@buffer_name}{\e!start#1}{\e!stop#1}{}{\externalfilter@process_filter}{\p_strip} \stoptexdefinition \setvalue{\externalfilter@id-\c!strip-}{\zerocount} \setvalue{\externalfilter@id-\c!strip-\v!off}{\zerocount} \setvalue{\externalfilter@id-\c!strip-\v!on}{\plusone} \setvalue{\externalfilter@id-\c!strip-\v!no}{\zerocount} \setvalue{\externalfilter@id-\c!strip-\v!yes}{\plusone} \unexpanded\def\externalfilter@process_file {\dodoubleargument\externalfilter@process_file_indeed} \starttexdefinition externalfilter@process_file_indeed [#1][#2]#3 % #1 = filter % #2 = options % #3 = filename \begingroup \edef\currentexternalfilter{#1} \setupexternalfilter[#1][\c!name=,#2] \processcommacommand[\externalfilterparameter{\c!filter\c!setups}]\directsetup \externalfilter@set_directory \edef\externalfilter@input_file{\externalfilter@any_filename{#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} \iftraceexternalfilters \externalfilter@show_filenames \fi \externalfilter@execute_filter \externalfilter@read_processed_file \endgroup \stoptexdefinition \unexpanded\def\externalfilter@process_buffer {\dotripleargument\externalfilter@process_buffer_indeed} \starttexdefinition externalfilter@process_buffer_indeed [#1][#2][#3] % #1 = filter % #2 = options % #3 = buffer \begingroup \edef\currentexternalfilter{#1} \ifthirdargument \setupexternalfilter[#1][\c!name=,#2] \fi \processcommacommand[\externalfilterparameter{\c!filter\c!setups}]\directsetup \externalfilter@set_directory \ifthirdargument \edef\externalfilter@buffer_name{#3} \else \edef\externalfilter@buffer_name{#2} \fi \externalfilter@set_filenames_extras \iftraceexternalfilters \externalfilter@show_filenames \fi \externalfilter@process_filter \stoptexdefinition \unexpanded\def\externalfilter@inline {\dodoubleargument\externalfilter@inline_indeed} \starttexdefinition externalfilter@inline_indeed [#1][#2] \begingroup % to keep assignments local \edef\currentexternalfilter{#1} \setupexternalfilter[#1][\c!escape=\v!off,\c!numbering=,\c!name=,\c!location=\v!text,#2] \externalfilter@set_filenames \doifelse{\externalfilterparameter\c!write}\v!no \externalfilter@inline_write_disabled \externalfilter@inline_write_enabled \stoptexdefinition \starttexdefinition externalfilter@inline_write_disabled \iftraceexternalfilters \showmessage\m!externalfilter{writedisabled} \currentexternalfilter \fi \externalfilter@execute_filter \endlinechar\minusone %to prevent line break after reading file \externalfilter@read_processed_file % Finalization \doifinset{\externalfilterparameter\c!cache}{\v!yes,\v!force} {\doglobal\incrementvalue\externalfilter@count} \endgroup \stoptexdefinition \starttexdefinition externalfilter@inline_write_enabled \pushcatcodetable \futurelet\next\externalfilter@inline_grabcontent \stoptexdefinition %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_grabcontent \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 \iftraceexternalfilters \else \doif{\externalfilterparameter\c!purge}\v!yes {\ctxlua{os.remove(\!!bs\externalfilter@input_file\!!es)}} \fi % Finalization \doifinset{\externalfilterparameter\c!cache}{\v!yes,\v!force} {\doglobal\incrementvalue\externalfilter@count} \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_indeed#1\relax} \def\externalfilter@get_first_character_indeed#1#2\relax{#1} \def\externalfilter@get_last_character#1% {\expandafter\externalfilter@get_last_character_indeed#1\relax} \def\externalfilter@get_last_character_indeed#1#2% {\ifx#2\relax#1\else\expandafter\externalfilter@get_last_character_indeed\expandafter#2\fi} %D \subsubject {Setting font and color attributes} %D I want to use the same interface for MkII and MkIV \starttexdefinition externalfilter@attributes_start #1#2#3 % id style color \getvalue{use#1styleandcolor}{#2}{#3} \stoptexdefinition \def\externalfilter@attributes_stop{} %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_indeed \stoptexdefinition \starttexdefinition externalfilter@set_directory_indeed \doif{\externalfilter@get_first_character\externalfilter@get_directory}{/} {\writeline \showmessage\m!externalfilter{forbidden}\externalfilter@get_directory \batchmode \errmessage{} \normalend} \doifnot{\externalfilter@get_last_character\externalfilter@get_directory}{/} {\iftraceexternalfilters \showmessage\m!externalfilter{slash}\externalfilter@get_directory \fi \edef\externalfilter@get_directory{\externalfilter@get_directory/}} \stoptexdefinition %D \subsubject {Check if file is writeable} \starttexdefinition externalfilter@check_writable #1 \ctxcommand{doifnot(file.is_writable("#1"))} {\showmessage\m!externalfilter{unwriteable}{#1} \batchmode \errmessage{} \normalend} \stoptexdefinition %D \subsubject {Find file name (with search in \usepath)} %D %D The `\locfilename` macro does not search in the path specified by `\usepath`. %D So, we define a macro that is based on `\readfile`. \def\externalfilter@any_filename#1% {\clf_getreadfilename{any}{.}{#1}} %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{cache} key as %D well as the value of \type{name} key. \starttexdefinition externalfilter@set_filenames \processcommacommand[\externalfilterparameter{\c!filter\c!setups}]\directsetup \externalfilter@set_directory % Set the name of temp file for the filter \doifinsetelse{\externalfilterparameter\c!cache}{\v!yes,\v!force} {\edef\externalfilter@buffer_name{\externalfilter@temp_prefix-\currentexternalfilter-\csname\externalfilter@count\endcsname}} {\edef\externalfilter@buffer_name{\externalfilter@temp_prefix-\currentexternalfilter}} \doifsomething{\externalfilterparameter\c!name} {\edef\externalfilter@buffer_name{\externalfilter@temp_prefix-\currentexternalfilter-\externalfilterparameter\c!name}} \doif{\externalfilterparameter\c!write}\v!no {\edef\externalfilter@buffer_name{\externalfilter@temp_prefix-\currentexternalfilter-\externalfilterparameter{\c!cache\c!option}}} \externalfilter@set_filenames_extras \iftraceexternalfilters \externalfilter@show_filenames \fi \stoptexdefinition \starttexdefinition externalfilter@set_filenames_extras % The following macros are useful for filter= and filtercommand= options % The basename of the external file \edef\externalfilter@base_file {\jobname-\externalfilter@buffer_name} % Append directory name to the name of the input file \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} \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 % % \savebuffer[\externalfilter@buffer_name][\externalfilter@input_file] % % We can also save a list of buffers to a file. The empty % elements of the list are ignored. So, instead we use the following % % \savebuffer[\externalfilterparameter{\c!buffer\c!before}, % \externalfilter@buffer_name, % \externalfilterparameter{\c!buffer\c!after}] % [\externalfilter@input_file]} % % but using this method we cannot save the file in another directory. % So, we use the key-value interface for \savebuffer. \externalfilter@check_writable \externalfilter@input_file \savebuffer [ \c!list={\externalfilterparameter{\c!buffer\c!before}, \externalfilter@buffer_name, \externalfilterparameter{\c!buffer\c!after}}, \c!file={\externalfilter@input_file}, \c!prefix=\v!no, ] \externalfilter@execute_filter \externalfilter@read_processed_file \iftraceexternalfilters \else \doif{\externalfilterparameter\c!purge}\v!yes {\ctxlua{os.remove(\!!bs\externalfilter@input_file\!!es)}} \fi % Finalization \doifinset{\externalfilterparameter\c!cache}{\v!yes,\v!force} {\doglobal\incrementvalue\externalfilter@count} \doif{\externalfilterparameter\c!location}\v!paragraph {\useindentnextparameter\externalfilterparameter \aftergroup\dorechecknextindentation} \endgroup \stoptexdefinition %D \subsubject {Execute Filter} % In MkIV, we use job.files.run to check if a file has changed. This function % writes the md5 sum to the tuc file rather than to an external file. So, we % must not check for the \type{*first} mode (otherwise the md5 sum is overridden % in the next run. \starttexdefinition externalfilter@execute_filter \externalfilter@check_writable \externalfilter@output_file \iftraceexternalfilters \externalfilter@show_filtercommand \fi \doifelsenothing{\externalfilter@input_file} {\showmessage\m!externalfilter{missing}\externalfilter@input_file} {\doifelse{\externalfilterparameter\c!cache}\v!yes {\doifelse{\externalfilterparameter\c!state}\v!stop {\showmessage\m!externalfilter{reuse}\externalfilter@input_file} {\doiffileelse{\externalfilter@output_file} {\doifnot{\externalfilterparameter\c!write}\v!no % When write=no is selected, % base_name=....-cacheoption... % Since there is no input, caching is simply achieved % using the filename. {\ctxlua{job.files.run(\!!bs\externalfilter@input_file\!!es, \!!bs\externalfilterparameter\c!filtercommand\!!es)}}} {\showmessage\m!externalfilter{missing_cache}\externalfilter@output_file \executesystemcommand {\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_indeed} {\showmessage\m!externalfilter{notfound}\externalfilter@output_file \externalfilter@show_filenames \blank {\tttf [[\getmessage\m!externalfilter{missing}]]} \blank}} \stoptexdefinition \starttexdefinition externalfilter@read_processed_file_indeed \doifelse{\externalfilterparameter\c!location}\v!paragraph {\blank[\externalfilterparameter\c!spacebefore] \usealignparameter\externalfilterparameter \externalfilterparameter\c!before \dostarttagged{\externalfilterparameter\c!taglabel}{\externalfilterparameter\c!tagdetail}} {\externalfilterparameter\c!left \dostarttagged{inline\externalfilterparameter\c!taglabel}{\externalfilterparameter\c!tagdetail}} \begingroup \useexternalfilterstyleandcolor\c!style\c!color \processcommacommand[\externalfilterparameter\c!setups]\directsetup \externalfilterparameter\c!readcommand\externalfilter@output_file \endgroup \dostoptagged \doifelse{\externalfilterparameter\c!location}\v!paragraph {\externalfilterparameter\c!after \par\blank[\externalfilterparameter\c!spaceafter]} {\externalfilterparameter\c!right} \stoptexdefinition %D \section {Default Values} \setupexternalfilters [ \c!location=\v!paragraph, \c!before=, \c!after=, \c!left=, \c!right=, \c!spacebefore=, \c!spaceafter=, \c!style=, \c!color=, \c!indentnext=\v!auto, \c!align=, \c!setups=, \c!continue=\v!no, \c!cache=\externalfilterparameter\c!continue, % for backward compatibility \c!cache\c!option=, \c!read=\v!yes, \c!strip=\v!yes, \c!readcommand=\ReadFile, \c!directory=, \c!purge=\v!yes, \c!output=\externalfilterbasefile.tex, \c!filter=, \c!filtercommand={\externalfilterparameter\c!filter\space \externalfilter@input_file}, \c!buffer\c!before=, \c!buffer\c!after=, \c!taglabel=\externalfilter@id, \c!tagdetail=\currentexternalfilter, ] \def\externalfilterbasefile {\externalfilter@base_file} \def\externalfilterinputfile {\externalfilter@input_file} \def\externalfilteroutputfile{\externalfilter@output_file} % t-syntax-groups still uses this. \def\externalfilter@name {\currentexternalfilter} % Default value \def\externalfilter@input_file {} \def\externalfilter@output_file{} \protect \stopmodule