%D \module
%D   [       file=supp-fil,
%D        version=1995.10.10,
%D          title=\CONTEXT\ Support Macros,
%D       subtitle=Files,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA / Hans Hagen \& Ton Otten}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

%D \TEX\ operates on files, so one wouldn't wonder that there
%D is a separate module for file support. In \CONTEXT\ files
%D are used for several purposes:
%D
%D \startitemize[packed]
%D \item  general textual input
%D \item  logging status information
%D \item  saving registers, lists and references
%D \item  buffering defered textual input
%D \stopitemize
%D
%D When dealing with files we can load them as a whole, using
%D the \type{\input} primitive or load them on a line||by||line
%D basis, using \type{\read}. Writing is always done line by
%D line, using \type{\write}.

\writestatus{loading}{Context Support Macros / Files}

\unprotect

\ifx\undefined\f!pathseparator
  \def\f!pathseparator{/}
  \def\f!currentpath  {.}
  \def\f!parentpath   {..}
\fi

%D \macros
%D   {normalwrite, normalimmediate}
%D
%D We save a few primitives first.

\let\normalwrite\write
\let\normalimmediate\immediate

%D \macros
%D   {pushendofline,popendofline}
%D
%D When we are loading files in the middle of the typesetting
%D process, for instance when we load references, we have to be
%D sure that the reading process does not generate so called
%D 'spurious spaces'. This can be prevented by assigning the
%D line ending character the \CATCODE\ comment. This is
%D accomplished by
%D
%D \starttyping
%D \pushendofline
%D ... reading ...
%D \popendofline
%D \stoptyping
%D
%D Just to be sure, we save the current meaning of \type{^^M}
%D in \type{\poppedendofline}.

\chardef\poppedendofline\catcode`\^^M

\def\pushendofline
  {\chardef\poppedendofline\catcode`\^^M\relax
   \catcode`\^^M\@@comment\relax}

\def\popendofline
  {\catcode`\^^M\poppedendofline}

%D \macros
%D   {scratchread, scratchwrite}
%D
%D We define a scratch file for reading. Keep in mind that
%D the number of files is limited to~16, so use this one when
%D possible. We also define a scratch output file.

\ifx\undefined\scratchread  \newread \scratchread  \fi
\ifx\undefined\scratchwrite \newwrite\scratchwrite \fi

%D \macros
%D   {unlinkfile}
%D
%D Sometimes we want to make sure a file is deleted, so here
%D is a macro that does the job. It's named after the \PERL\
%D one.

\def\unlinkfile#1%
  {\immediate\openout\scratchwrite=#1\immediate\closeout\scratchwrite}

%D \macros
%D   {writeln}
%D
%D This saves a few tokens:

\def\writeln#1{\write#1{}}

%D New!

\def\doiffileexistselse#1%
  {\immediate\openin\scratchread=#1\relax
   \ifeof\scratchread
     \immediate\closein\scratchread
     \expandafter\secondoftwoarguments
   \else
     \immediate\closein\scratchread
     \expandafter\firstoftwoarguments
   \fi}

%D \macros
%D   {doprocessfile,fileline,fileprocessedtrue,dofinishfile}
%D
%D The next macro offers a framework for processing files on a
%D line by line basis.
%D
%D \starttyping
%D \doprocessfile \identifier {name} \action
%D \stoptyping
%D
%D The first argument can for instance be \type{\scratchread}.
%D The action must do something with \type{\fileline}, which
%D holds the current line. One can halfway step out using
%D \type{\dofinishfile} and ise \type{\iffileprocessed} to
%D see if indeed some content was found.

\newif\iffileprocessed

\let\fileline\empty

\def\doprocessfile#1#2#3%
  {\immediate\openin#1=#2\relax
   \ifeof#1%
     \fileprocessedfalse
     \immediate\closein#1%
   \else
     \fileprocessedtrue
     \gdef\dofinishfile
       {\immediate\closein#1%
        \global\let\doprocessline\relax}%
     \gdef\doprocessline
       {\ifeof#1%
          \expandafter\dofinishfile
        \else
          \global\read#1 to \fileline
          #3\relax
          \expandafter\doprocessline
        \fi}%
     \expandafter\doprocessline
   \fi}

%D \macros
%D   {pathplusfile,assignfullfilename,sanitizefilename}
%D
%D Use \type{\pathplusfile} to compose a full file name, like
%D in:
%D
%D \starttyping
%D \pathplusfile{path}{file}
%D \stoptyping
%D
%D By default, this expands into {\tt \pathplusfile{path}{file}}.

\def\pathplusfile#1#2{#1\f!pathseparator#2}

%D This one constructs a filename from a (possible empty)
%D path and filename.

\def\assignfullfilename#1#2\to#3%
  {\doifelsenothing{#1}
     {\edef#3{#2}}
     {\edef#3{#1\f!pathseparator#2}}}

%D For the moment, we limit sanitizing to taking care of
%D active \type {/}.

\bgroup % todo: _ cleanup

\catcode`\/=\@@active
\catcode`\:=\@@active
\catcode`\~=\@@active
\catcode`\_=\@@active

\gdef\sanitizefilename#1\to#2%
  {\bgroup
   \edef/{\string/}%
   \edef:{\string:}%
   \edef~{\string~}%
   \edef_{\string_}%
   \expanded{\xdef\noexpand\sanitizedfilename{#1}}%
   \egroup
   % maybe \convertcommand\sanitizedfilename\to#2%
   \let#2\sanitizedfilename}

\egroup

%D NEW:

\chardef\kindoffile=0 % 0=normal 1=full path spec (or http)

\def\checkfilename#1%
  {\doifinstringelse{@@/}{@@#1}%    unix: /full/path
     {\chardef\kindoffile\plusone}
     {\doifinstringelse{:/}{#1}%  windows: e:/full/path or http://
        {\chardef\kindoffile\plusone}
        {\chardef\kindoffile\zerocount}}}

%D \macros
%D   {input, normalinput}
%D
%D Sometimes we run into troubles when \type {\input} wants to get
%D expanded, e.g. in a \type {\write} (which happens in the metafun
%D manual when we permit long MP lines). So, instead of fixing that,
%D we go for a redefinition of \type {\input}. Of course it's better
%D to use \type {\readfile} or \type {\processfile}.

\ifx\normalinput\undefined \let\normalinput\input \fi

\unexpanded\def\input{\normalinput}

%D \macros
%D   {readfile,ReadFile,maxreadlevel}
%D
%D One cannot be sure if a file exists. When no file can be
%D found, the \type{\input} primitive gives an error message
%D and switches to interactive mode. The macro \type{\readfile}
%D takes care of non||existing files. This macro has two faces.
%D
%D \starttyping
%D \ReadFile {filename}
%D \readfile {filename} {before loading} {not found}
%D \stoptyping
%D
%D Many \TEX\ implementations have laid out some strategy for
%D locating files. This can lead to unexpected results,
%D especially when one loads files that are not found in the
%D current directory. Let's give an example of this. In
%D \CONTEXT\ illustrations can be defined in an external file.
%D The resizing macro first looks if an illustration is defined
%D in the local definitions file. When no such file is found,
%D it searches for a global file and when this file is not
%D found either, the illustration itself is scanned for
%D dimensions. One can imagine what happens if an adapted,
%D localy stored illustration, is scaled according to
%D dimensions stored somewhere else.
%D
%D When some \TEX\ implementation starts looking for a file, it
%D normally first looks in the current directory. When no file
%D is found, \TEX\ starts searching on the path where format
%D and|/|or style files are stored. Depending on the implementation
%D this can considerably slow down processing speed.
%D
%D In \CONTEXT, we support a project||wise ordening of files.
%D In such an approach it seems feasible to store common files
%D in a lower directory. When for instance searching for a
%D general layout file, we therefore have to backtrack.
%D
%D These three considerations have lead to a more advanced
%D approach for loading files.
%D
%D We first present an earlier implementation of
%D \type{\readfile}. This command backtracks parent
%D directories, upto a predefined level. Users can change this
%D level, but we default to~3.
%D
%D \starttyping
%D \def\maxreadlevel {3}
%D \stoptyping
%D
%D This is a pseudo \COUNTER.
%D
%D We use \type{\normalinput} instead of \type{\input}
%D because we want to be able to redefine the original
%D \type{\input} when needed, for instance when loading third
%D party libraries.

\newevery \everybeforereadfile \EveryBeforeReadFile
\newevery \everyafterreadfile  \EveryAfterReadFile

\let \everyreadfile \everybeforereadfile

\newif\iftracefiles

\def\maxreadlevel{3}

\newconditional\trackfilenames

% We need to postpone loading, else we got frozen type-* files and so when
% a format is generated on a source path.

\appendtoks \settrue\trackfilenames \to \everyjob

\let\trackedfilename\empty

\def\doreadfile#1#2#3#4%
  {\sanitizefilename#2\to\readfilename
   \ifx\readfilename\empty
     % silently ignore
   \else
     \let\trackedfilename\readfilename
     \ifconditional\trackfilenames
       \doifundefinedelse{fn..\trackedfilename}\donetrue\donefalse
     \else
       \donetrue
     \fi
     \ifdone
       \checkfilename\readfilename
       \ifcase\kindoffile
         \iftracefiles\writestatus\m!systems{searching for \readfilename\space on #1}\fi
         % not a full path or url, check for existence
         \doifelsenothing{#1}
           {\def\next{\redoreadfile\readfilename{#3}{#4}}}%
           {\def\next{\redoreadfile{\pathplusfile{#1}{\readfilename}}{#3}{#4}}}%
       \else
         % a full path or url, no further checking done
         \doiffileexistselse\readfilename
           {\iftracefiles\writestatus\m!systems{located \readfilename}\fi
            \def\next{#3\dodoreadfile}}%
           {\iftracefiles\writestatus\m!systems{not found \readfilename}\fi
            \def\next{#4}}%
       \fi
     \else
       \edef\readfilename{\getvalue{fn..\readfilename}}%
       \iftracefiles\writestatus\m!systems{already located \readfilename}\fi
       \def\next{#3\dodoreadfile}%
     \fi
     \expandafter\next
   \fi}

\def\redoreadfile#1#2#3%
  {\doiffileexistselse{#1}%
     {\edef\readfilename{#1}%
      \iftracefiles\writestatus\m!systems{#1 located}\fi
      \def\next{#2\dodoreadfile}}%
     {\iftracefiles\writestatus\m!systems{cannot locate #1}\fi
      \decrement\readlevel\relax
      \ifnum\readlevel>\zerocount
        \edef\readfilename{\pathplusfile{\f!parentpath}{\readfilename}}%
        \def\next{\redoreadfile\readfilename{#2}{#3}}%
      \else
        \def\next{#3}%
      \fi}%
   \next}

\def\dodoreadfile % we provide hooks, for instance for \enableXML
  {\ifconditional\trackfilenames
     \setxvalue{fn..\trackedfilename}{\readfilename}%
   \fi
   \the\everybeforereadfile
   \normalinput\readfilename\relax
   \the\everyafterreadfile}

\unexpanded\def\readfile% #1%
  {\let\readlevel\maxreadlevel
   \doreadfile\empty} % {#1}

\unexpanded\def\ReadFile#1%
  {\readfile{#1}\donothing\donothing}

%D \macros
%D   {readjobfile,readlocfile,readsysfile,
%D    readfixfile,readsetfile}
%D
%D This implementation honnors the third situation, but we
%D still can get unwanted files loaded and/or can get involved
%D in extensive searching.
%D
%D Due to different needs, we decided to offer four alternative
%D loading commands. With \type{\readjobfile} we load a local
%D file and do no backtracking, while \type{\readlocfile}
%D backtracks~\readlevel\ directories, including the current
%D one.

\unexpanded\def\readjobfile % #1% current path, no backtracking
  {\newcounter\readlevel
   \doreadfile\f!currentpath} % {#1}}

\unexpanded\def\readlocfile % #1% current path, backtracking
  {\let\readlevel\maxreadlevel
   \doreadfile\f!currentpath} % {#1}}

%D System files can be anywhere and therefore
%D \type{\readsysfile} is not bound to the current directory
%D and obeys the \TEX\ implementation.

\unexpanded\def\readsysfile % #1% current path, obeys tex search
  {\newcounter\readlevel
   \doreadfile\empty} % {#1}}

%D Of the last two, \type{\readfixfile} searches on the
%D directory specified and backtracks too, while
%D \type{\readsetfile} does only search on the specified path.

\unexpanded\def\readfixfile % #1#2% specified path, backtracking
  {\let\readlevel\maxreadlevel
   \doreadfile} % {#1}{#2}}

\unexpanded\def\readsetfile % #1#2% specified path, no backtracking
  {\newcounter\readlevel
   \doreadfile} % {#1}{#2}}

%D After having defined this commands, we reconsidered the
%D previously defined \type{\readfile}. This time we more or
%D less impose the search order.

\unexpanded\def\readfile#1#2#3%
  {\readlocfile{#1}{#2}
     {\readjobfile{#1}{#2}
        {\readsysfile{#1}{#2}{#3}}}}

%D So now we've got ourselves five file loading commands:
%D
%D \starttyping
%D \readfile                {filename} {before loading} {not found}
%D
%D \readjobfile             {filename} {before loading} {not found}
%D \readlocfile             {filename} {before loading} {not found}
%D \readfixfile             {filename} {before loading} {not found}
%D \readsysfile {directory} {filename} {before loading} {not found}
%D \stoptyping

%D \macros
%D   {readjobfile,readlocfile,readsysfile,readfixfile}
%D
%D The next four alternatives can be used for opening files
%D for reading on a line||by||line basis. These commands get
%D an extra argument, the filetag. Explicit closing is done
%D in the normal way by \type{\closein}.

\def\doopenin#1#2%
  {\sanitizefilename#2\to\readfilename
   \checkfilename\readfilename
   \ifcase\kindoffile
     \increment\readlevel
     \immediate\openin#1=\readfilename\relax
     \ifeof#1% \relax
       \ifnum\readlevel>\maxreadlevel % \relax
       \else
         \immediate\closein#1% \relax
         \doopenin{#1}{\pathplusfile\f!parentpath{#2}}%
       \fi
     \fi
   \fi}

\def\openjobin#1#2%
  {\newcounter\readlevel
   \doopenin{#1}{\pathplusfile\f!currentpath{#2}}}

\def\opensysin % #1#2%
  {\let\readlevel\maxreadlevel
   \doopenin} % {#1}{#2}}

\def\openlocin#1#2%
  {\let\readlevel\maxreadlevel
   \doopenin{#1}{\pathplusfile\f!currentpath{#2}}}

\def\openfixin#1#2#3%
  {\let\readlevel\maxreadlevel
   \doopenin{#1}{\pathplusfile{#2}{#3}}}

%D \macros
%D   {doiffileelse,doiflocfileelse}
%D
%D The next alternative only looks if a file is present. No
%D loading is done. This one obeys the standard \TEX\
%D implementation method.
%D
%D \starttyping
%D \doiffileelse {filename} {before loading} {not found}
%D \stoptyping
%D
%D We use \type{\next} here, because we want to close the
%D file first. We also provide the alternatives:
%D
%D \starttyping
%D \doiflocfileelse {filename} {before loading} {not found}
%D \stoptyping

\def\doiffileelse#1%
  {\doifelsenothing{#1}
     {\secondoftwoarguments}
     {\immediate\openin\scratchread=#1\relax
      \ifeof\scratchread
        \immediate\closein\scratchread
        \expandafter\secondoftwoarguments
      \else
        \immediate\closein\scratchread
        \expandafter\firstoftwoarguments
      \fi}}

\def\doiflocfileelse#1%
  {\makelocreadfilename{#1}%
   \doiffileelse\readfilename}

\def\makelocreadfilename#1%
  {\sanitizefilename#1\to\readfilename
   \checkfilename\readfilename
   \ifcase\kindoffile
     \edef\locreadfilename{\pathplusfile\f!currentpath{#1}}%
   \fi}

%D \macros
%D   {doonlyonce, doinputonce, doendinputonce}
%D
%D Especially macropackages need only be loaded once.
%D Repetitive loading not only costs time, relocating registers
%D often leads to abortion of the processing because \TEX's
%D capacity is limited. One can prevent multiple execution and
%D loading by using one of both:
%D
%D \starttyping
%D \doonlyonce{actions}
%D \doinputonce{filename}
%D \doendinputonce{filename}
%D \stoptyping
%D
%D This command obeys the standard method for locating files.

\long\def\doonlyonce#1#2%
  {\doifundefined{@@@#1@@@}{\letgvalue{@@@#1@@@}\empty#2}}

\def\doinputonce#1%
  {\doonlyonce{#1}{\doiffileelse{#1}{\normalinput#1\relax}\donothing}}

\def\doendinputonce#1%
  {\doifdefined{@@@#1@@@}\endinput}

\def\forgetdoingonce#1%
  {\global\letbeundefined{@@@#1@@@}}

%D \macros
%D   {doifparentfileelse}
%D
%D The test \type{\doifelse{\jobname}{filename}} does not give
%D the desired result, simply because \type{\jobname} expands
%D to characters with \CATCODE~12, while the characters in
%D \type{filename} have \CATCODE~11. So we can better use:
%D
%D \starttyping
%D \doifparentfileelse{filename}{yes}{no}
%D \stoptyping
%D
%D Since \TEXEXEC\ (and thereby \CONTEXT) supports renaming of
%D the outputfile, we also need to check on that alternative
%D name.

\ifx\outputfilename\undefined \def\outputfilename{\jobname} \fi

% \def\doifparentfileelse#1#2#3%
%   {\doifsamestringelse{#1}{\jobname       }{#2}
%   {\doifsamestringelse{#1}{\jobname.\c!tex}{#2}
%   {\doifsamestringelse{#1}{\outputfilename}{#2}{#3}}}}

\def\doifparentfileelse#1%
  {\doifsamestringelse{#1}{\jobname       }\firstoftwoarguments
  {\doifsamestringelse{#1}{\jobname.\c!tex}\firstoftwoarguments
  {\doifsamestringelse{#1}{\outputfilename}\firstoftwoarguments\secondoftwoarguments}}}

\def\normalless    {<} % geen \let !
\def\normalmore    {>} % geen \let !
\def\normalequal   {=} % geen \let !
\def\normaldblquote{"} % geen \let !

\newcounter\readingfilelevel

\def\popfilecharacter#1#2%
  {\ifnum\catcode`#1=\@@other \ifnum#2=\@@other \else
    %\message{[popping catcode #1 to #2]}%
     \catcode`#1=#2\relax
   \fi \fi}

\ifx\\\undefined \let\\\relax \fi

%D This changing catcodes is a direct result from the fact
%D that we support some long standing conventions with
%D regards to active characters (german ", polish /,
%D french : and ;).

\def\startreadingfile% beter een every en \setnormalcatcodes
  {\doglobal\increment\readingfilelevel
   \setxvalue{\string\readingfilelevel::\readingfilelevel}%
     {\catcode`/ =\the\catcode`/%
     %\catcode`_ =\the\catcode`_% math ! ! !
      \catcode`" =\the\catcode`"%
      \catcode`: =\the\catcode`:%
      \catcode`; =\the\catcode`;%
      \catcode`< =\the\catcode`<%
      \catcode`> =\the\catcode`>%
      \catcode`\noexpand\\=\the\catcode`\\%
      \catcode`\noexpand\{=\the\catcode`\{%
      \catcode`\noexpand\}=\the\catcode`\}%
      \catcode`\noexpand\%=\the\catcode`\%}%
   \catcode`/ =\@@other
  %\catcode`_ =\@@other
   \catcode`" =\@@other
   \catcode`: =\@@other
   \catcode`; =\@@other
   \catcode`< =\@@other
   \catcode`> =\@@other
   \catcode`\\=\@@escape
   \catcode`\{=\@@begingroup
   \catcode`\}=\@@endgroup
   \catcode`\%=\@@comment}

\def\stopreadingfile
  {\getvalue{\string\readingfilelevel::\readingfilelevel}%
   \doglobal\decrement\readingfilelevel}

%D \macros
%D   {splitfilename}
%D
%D I should have made this one sooner. This macro was first needed when
%D ran into graphic with a period in the pathpart.
%D
%D \startbuffer
%D \def\showfilesplit
%D   {\bgroup \tttf
%D      \hbox{(full: \splitofffull)}\space
%D      \hbox{(path: \splitoffpath)}\space
%D      \hbox{(base: \splitoffbase)}\space
%D      \hbox{(name: \splitoffname)}\space
%D      \hbox{(type: \splitofftype)}\space
%D    \egroup}
%D
%D \splitfilename{c:/aa/bb/cc/dd.ee.ff} \showfilesplit \endgraf
%D \splitfilename{c:/aa/bb/cc/dd.ee}    \showfilesplit \endgraf
%D \splitfilename{c:/aa/bb/cc/dd}       \showfilesplit \endgraf
%D
%D \splitfilename{dd.ee.ff} \showfilesplit \endgraf
%D \splitfilename{dd.ee}    \showfilesplit \endgraf
%D \splitfilename{dd}       \showfilesplit \endgraf
%D \stopbuffer
%D
%D \start \typebuffer \getbuffer \stop

\def\splitfilename#1%
  {\edef\splitofffull{#1}% normally outside this call: \sanitizefilename#1\to\sanitizedfilename
   \greedysplitstring\splitofffull\at/\to\splitoffpath\and\splitoffbase
   \ifx\splitoffbase\empty\let\splitoffbase\splitofffull\fi
   \greedysplitstring\splitoffbase\at.\to\splitoffname\and\splitofftype
   \ifx\splitoffpath\empty\def\splitoffpath{.}\fi
   \ifx\splitoffname\empty\let\splitoffname\splitoffbase\fi}

%% % gebruikt voor normale (!) files, will change to proper
%% % installer maybe combined with verb module push/popper
%%
%% \def\startreadingfile% beter een every
%%   {\doglobal\increment\readingfilelevel
%%    \setxvalue{popfilecharacters::\readingfilelevel}%
%%      {%\expnormalcatcodes
%%       \expspecialcatcodes}%
%%   %\setnormalcatcodes
%%    \setspecialcatcodes}
%%
%% \def\stopreadingfile%
%%   {\getvalue{popfilecharacters::\readingfilelevel}%
%%    \doglobal\decrement\readingfilelevel}
%%
%% \ifx\\\undefined \let\\\relax \fi
%% \ifx\!\undefined \let\!\relax \fi
%% \ifx\?\undefined \let\?\relax \fi
%%
%% \def\expnormalcatcodes%
%%   {\catcode`\noexpand\!=\the\catcode`\! \catcode`\noexpand\?=\the\catcode`\?%
%%    \catcode`\noexpand\&=\the\catcode`\&
%%    \catcode`\noexpand\#=\the\catcode`\# \catcode`\noexpand\$=\the\catcode`\$%
%%    \catcode`\noexpand\%=\the\catcode`\% \catcode`\noexpand\\=\the\catcode`\\%
%%    \catcode`\noexpand\^=\the\catcode`\^ \catcode`\noexpand\_=\the\catcode`\_%
%%    \catcode`\noexpand\{=\the\catcode`\{ \catcode`\noexpand\}=\the\catcode`\}}
%%
%% \def\setnormalcatcodes%
%%   {%\ifcase\protectionlevel
%%      \catcode`\!=\@@other  \catcode`\?=\@@other
%%    %\else
%%    %  \catcode`\!=\@@letter \catcode`\?=\@@letter
%%    %\fi
%%    \catcode`\&=\@@alignment
%%    \catcode`\#=\@@parameter   \catcode`\$=\@@mathshift
%%    \catcode`\%=\@@comment     \catcode`\\=\@@escape
%%    \catcode`\^=\@@superscript \catcode`\_=\@@subscript
%%    \catcode`\{=\@@begingroup  \catcode`\}=\@@endgroup}
%%
%% \def\expspecialcatcodes%
%%   {\catcode`\noexpand/=\the\catcode`/%
%%    \catcode`\noexpand"=\the\catcode`"%
%%    \catcode`\noexpand<=\the\catcode`<%
%%    \catcode`\noexpand>=\the\catcode`>}
%%
%% \def\setspecialcatcodes%
%%   {\catcode`/=\@@other
%%    \catcode`"=\@@other
%%    \catcode`<=\@@other
%%    \catcode`>=\@@other}

\protect \endinput
