%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