%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 \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 \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 \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}. \chardef\poppedendofline=\the\catcode`\^^M \def\pushendofline {\chardef\poppedendofline=\the\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 {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 \doprocessfile \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 \let\fileline\empty \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,assignfullfilename,sanitizefilename} %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\f!pathseparator#2} %D This one constructs a filename from a (possible empty) %D path and filename. % The special \type {system} is ignored. \def\assignfullfilename#1#2\to#3% {\doifelsenothing{#1} {\edef#3{#2}} {\edef#3{#1\f!pathseparator#2}}} % \def\assignfullfilename#1#2\to#3% % {\doifelsenothing{#1} % {\edef#3{#2}} % {\doifelse{#1}{system} % special case, honors default searching % {\edef#3{#2}} % {\edef#3{#1\f!pathseparator#2}}}} %D For the moment, we limit sanitizing to taking care of %D active \type {/}. \bgroup \catcode`\/=\@@active \gdef\sanitizefilename#1\to#2% {\bgroup \let/=\f!pathseparator \expanded{\xdef\noexpand\sanitizedfilename{#1}}% \egroup \let#2\sanitizedfilename} \egroup %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. \newevery \everyreadfile \EveryReadFile \let\normalinput=\input \def\maxreadlevel{3} \def\doreadfile#1#2#3% {\sanitizefilename#1\to\readfilename \immediate\openin\scratchread=\readfilename\relax \ifeof\scratchread \immediate\closein\scratchread \decrement\readlevel\relax \ifnum\readlevel>0 \def\next{\doreadfile{\pathplusfile{\f!parentpath}{#1}}{#2}{#3}}% \else \def\next{#3}% \fi \else \def\next{\immediate\closein\scratchread#2\dodoreadfile}% \fi \next} \def\dodoreadfile% {\the\everyreadfile % hook, for instance for \enableXML \normalinput\readfilename\relax} \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 alternatives: %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, 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 \starttypen %D \doonlyonce{actions} %D \doinputonce{filename} %D \doendinputonce{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}{}}} \def\doendinputonce#1% {\doifdefined{@@@#1@@@}{\endinput}} %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 %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% % {\edef\!!stringa{#1}% % \edef\!!stringb{\jobname}% % \edef\!!stringc{\outputfilename}% % \convertcommand\!!stringa\to\!!stringa % \convertcommand\!!stringb\to\!!stringb % \convertcommand\!!stringc\to\!!stringc % \ifx\!!stringa\!!stringb#2\else % \ifx\!!stringa\!!stringc#2\else % #3\fi\fi} \def\doifparentfileelse#1#2#3% {\doifsamestringelse{#1}{\jobname }{#2} {\doifsamestringelse{#1}{\jobname.\c!tex}{#2} {\doifsamestringelse{#1}{\outputfilename}{#2}{#3}}}} % \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\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 \def\startreadingfile% beter een every {\doglobal\increment\readingfilelevel \setxvalue{popfilecharacters::\readingfilelevel}% {\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`\\=\@@escape \catcode`\{=\@@begingroup \catcode`\}=\@@endgroup \catcode`\%=\@@comment} \def\stopreadingfile% {\getvalue{popfilecharacters::\readingfilelevel}% \doglobal\decrement\readingfilelevel} %% % 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