%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. Non||commercial use is 
%C granted. 

%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 \startopsomming[opelkaar]
%D \som  general textual input
%D \som  logging status information
%D \som  saving registers, lists and references
%D \som  buffering defered textual input
%D \stopopsomming
%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

%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
%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 \starttypen
%D \pushendofline
%D ... reading ...
%D \popendofline
%D \stoptypen
%D
%D Just to be sure, we save the current meaning of \type{^^M} 
%D in \type{\poppedendofline}. 

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

\def\popendofline
  {\catcode`\^^M=\poppedendofline}

%D \macros
%D   {scratchread, scratchwrite}
%D   {}
%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   {doprocessfile,fileline,fileprocessedtrue,dofinishfile}
%D
%D The next macro offers a framework for processing files on a 
%D line by line basis. 
%D 
%D \starttypen
%D \processfile \identifier {name} \action
%D \stoptypen
%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

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

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

\def\pathplusfile#1#2{#1/#2}

%D \macros
%D   {readfile,ReadFile,maxreadlevel,
%D    normalinput}
%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 \starttypen
%D \ReadFile {filename}
%D \readfile {filename} {before loading} {not found}
%D \stoptypen
%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 \starttypen
%D \def\maxreadlevel {3}
%D \stoptypen
%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. 

\let\normalinput=\input

\def\maxreadlevel {3}

\def\doreadfile#1#2#3%
  {\immediate\openin\scratchread=#1\relax
   \ifeof\scratchread
     \immediate\closein\scratchread
     \decrement\readlevel
     \ifnum\readlevel>0\relax
       \doreadfile{\pathplusfile{\f!parentpath}{#1}}{#2}{#3}%
     \else
       #3%
     \fi
   \else
     \immediate\closein\scratchread
     #2%
     \normalinput #1\relax
   \fi}

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

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

%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.

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

\def\readlocfile#1% current path, backtracking 
  {\let\readlevel=\maxreadlevel
   \doreadfile{\pathplusfile{\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.

\def\readsysfile#1% current path, obeys tex search 
  {\let\readlevel=\maxreadlevel
   \doreadfile{#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. 

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

\def\readsetfile#1#2% specified path, no backtracking 
  {\newcounter\readlevel
   \doreadfile{\pathplusfile{#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.

\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 \starttypen
%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 \stoptypen

%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%
  {\increment\readlevel
   \immediate\openin#1=#2\relax
   \ifeof#1\relax
     \ifnum\readlevel>\maxreadlevel\relax
     \else
       \immediate\closein#1\relax
       \doopenin{#1}{\pathplusfile{\f!parentpath}{#2}}%
     \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 \starttypen
%D \doiffileelse {filename} {before loading} {not found}
%D \stoptypen
%D
%D We use \type{\next} here, because we want to close the 
%D file first. We also provide the local alternative: 
%D 
%D \starttypen
%D \doiflocfileelse {filename} {before loading} {not found}
%D \stoptypen

\def\doiffileelse#1#2#3%
  {\immediate\openin\scratchread=#1\relax
   \ifeof\scratchread
     \def\next{#3}%
   \else
     \def\next{#2}%
   \fi
   \immediate\closein\scratchread
   \next}

\def\doiflocfileelse#1%
  {\doiffileelse{\pathplusfile{\f!currentpath}{#1}}}

%D \macros
%D   {doonlyonce, doinputonce}
%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 \starttypen
%D \doonlyonce{actions}
%D \doloadonce{filename}
%D \stoptypen
%D
%D This command obeys the standard method for locating files. 

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

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

%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 \starttypen 
%D \doifparentfileelse{filename}{yes}{no}
%D \stoptypen

\def\doifparentfileelse#1#2#3%
  {\edef\!!stringa{#1}%
   \@EA\convertargument\!!stringa\to\!!stringa
   \@EA\def\@EA\!!stringb\@EA{\jobname}%
   \ifx\!!stringa\!!stringb#2\else#3\fi}

% \newcounter\readingfilelevel
%
% \def\startreadingfile%
%   {\ifnum\readingfilelevel=0
%      \edef\doreadingfilecharacters%
%        {\catcode`"=\the\catcode`"\relax
%         \catcode`<=\the\catcode`<\relax
%         \catcode`>=\the\catcode`>\relax}%
%      \catcode`"=\@@other
%      \catcode`<=\@@other
%      \catcode`>=\@@other
%      \let\stopreadingfile=\dostopreadingfile
%    \fi
%    \increment\readingfilelevel}
% 
% \def\dostopreadingfile%
%   {\ifnum\readingfilelevel=1
%      \doreadingfilecharacters
%    \fi
%    \decrement\readingfilelevel}

\def\normalless {<} % geen \let ! 
\def\normalmore {>} % geen \let ! 
\def\normalequal{=} % 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}

\def\pushfilecharacter#1%
  {\ifnum\catcode`#1=\@@other \else
     \message{[pushing catcode #1 from \the\catcode`#1]}%
     \catcode`#1=\@@other
   \fi}

\def\startreadingfile%
  {\doglobal\increment\readingfilelevel
   \setxvalue{popfilecharacters::\readingfilelevel}%
     {\noexpand\popfilecharacter{"}{\the\catcode`"}%
      \noexpand\popfilecharacter{<}{\the\catcode`<}%
      \noexpand\popfilecharacter{>}{\the\catcode`>}}%
   \pushfilecharacter{"}%
   \pushfilecharacter{<}%
   \pushfilecharacter{>}}

\def\stopreadingfile%
  {\getvalue{popfilecharacters::\readingfilelevel}%
   \doglobal\decrement\readingfilelevel}

\protect

\endinput
