0

Say all homework files: hw1.tex, hw2.tex, ... use some common homework.cls. Problem is that in every hw*.tex file these variables have to be set manually.

\documentclass{homework}
\author{John Smith}
\course{Math101}
\date{2022-10-16}
\hwnum{1}

\begin{document} {...} \end{document}

My goal is to eliminate repeated lines: \author{}, \course{}, ... to include them in a metadata file shared by all hw*.tex. As others commented out, this is solved by:

\documentclass{homework}
\input{./metadata.tex}
\hwnum{1}

\begin{document} {...} \end{document}

What remains is eliminating (1) \input{./metadata.tex} and (2) \hwnum{1}.

  1. Can be solved by having \input{./metadata.tex} in homework.cls. Is there a way to stop \input from complaining when it doesn't find the file?

  2. Is it possible to parse hwnum from the filename. i.e: if filename is hw39.tex, homework.cls automatically sets \hwnum{39}.

10
  • How much shell scripting are you willing to do? It seems it would be sufficient to just use cat to prepend the metadata file to the hw*.tex files before you run LaTeX on them (then you need to use syntax like \def\course{Math101} instead of just \course{Math101}). The number could also be extracted by a shell script, but there you can also use xstring macros like \StrBehind on \jobname, which is a built-in macro that contains the filename without the extension.
    – Marijn
    Commented Oct 16, 2022 at 18:16
  • I don't know if I understood your question correctly: You have a .yaml-file and you want LaTeX to read and parse that .yaml-file for defining macros which have names according to the variables introduced in the .yaml-file and which expand to the values of these variables? If so and if using LuaLaTeX the package YAMLvars might be of interest. Commented Oct 16, 2022 at 18:35
  • your question isn't very clear, why use yml syntax rather than the tex syntax you show? Commented Oct 16, 2022 at 18:48
  • Ad parsing filename: During compilation the name of the main tex-file usually can be obtained by expanding the primitive control sequence \jobname which delivers a sequence of explicit character-tokens of catrgory 12(other) - only space is of category 10(spacce). So parsing the expansion of \jobname might be a way of approaching the matter. But nowadays latex-compilers have a command-line-option for specifying a jobname differing from the name of the main .tex-file. E.g., Overleaf uses that option for specifying the \jobname-primitive to deliver the phrase output. Commented Oct 16, 2022 at 18:50
  • If it is not about the main .tex-file but about a file loaded via \input or \include, then obtaining the name of that file is not trivial. Commented Oct 16, 2022 at 18:51

1 Answer 1

2

Ad 1:

\InputIfFileExists{⟨file-name⟩}{}{} should do the trick.

Ad 2:

If it is only about getting the first digit sequence of the main .tex-file's name (with leading zeros removed) and if that file's name can can be obtained via expanding the \jobname-primitive (i.e., the commandline-option -job-name was not used when invoking LaTeX), you can try with expl3's \regex_extract_once:nnN:

file: hw39.tex

\ExplSyntaxOn
\cs_new:Npn \DefineFromFirstDigitsMatch #1#2 {
  \group_begin:
  \regex_extract_once:nnN {\d+}{#2}\l_tmpa_seq
  \seq_get_left:NN \l_tmpa_seq \l_tmpa_tl
  \exp_args:Nnf \use:n {\group_end: \cs_new:Npn #1} 
                {\exp_args:No \quark_if_no_value:nTF {\l_tmpa_tl}
                                                     {\exp_stop_f:}
                                                     {\int_value:w \l_tmpa_tl}}
}
\ExplSyntaxOff

\DefineFromFirstDigitsMatch{\macroa}{\foo 00123 bar\fi 45 whoozle \endcsname { 006 {777}} dd890jj}
\DefineFromFirstDigitsMatch{\macrob}{\foo {{00123}} bar\fi 45 whoozle \endcsname { 006 {777}} dd890jj}
\DefineFromFirstDigitsMatch{\macroc}{hh}
\expandafter\DefineFromFirstDigitsMatch\expandafter{\expandafter\macrod\expandafter}\expandafter{\jobname}

\documentclass{article}

\begin{document}

\ttfamily
\noindent\string\macroa=\meaning\macroa

\noindent\string\macrob=\meaning\macrob

\noindent\string\macroc=\meaning\macroc

\noindent\string\macrod=\meaning\macrod

\end{document}

enter image description here

Be aware that this does not work out with Overleaf as with Overleaf TeXLive-binaries are called with the -jobname-commandline-option causing the \jobname-primitive to deliver the phrase output instead of the name of the main .tex-file.


When the comment where you pointed out that you need only the first matching digit-sequence arrived at the site, I just finished writing a generic routine

\ExtractDigitSequences{⟨argument⟩}

which extracts each sequence of digits (with leading zeros removed) from the argument and delivers it nested in curly braces so that you obtain a list of undelimited arguments.

E.g., \ExtractDigitSequences{\foo 12 bar\fi 345 whoozle \endcsname { 006 {777}} dd} yields {12}{345}{6}{777}.

Due to \romannumeral-expansion the result is delivered by triggering exactly two expansion-steps.

In case the filename can be obtained via expanding the \jobname-primitive and contains only one digit sequence, you can do, e.g.,

\newcommand\PassFirstToSecond[2]{#2{#1}}

\expandafter\PassFirstToSecond\expandafter{\jobname}{%
  \expandafter\expandafter\expandafter\newcommand
  \expandafter\expandafter\expandafter*%
  \expandafter\expandafter\expandafter\macro
  \ExtractDigitSequences
}%

If there are more digit-sequences and you want just one of them, you can combine usage of \ExtractDigitSequences with usage of \UD@ExtractKthArg.

Here is the code:

\errorcontextlines=10000
\makeatletter
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@removespace, \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingExplicitSpace,
%% \UD@ExtractKthArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%-----------------------------------------------------------------------------
%% -- Take advantage of the fact that TeX discards space tokens when
%%    "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that
%%                        argument which is to be checked is blank>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked is not blank>}%
\newcommand\UD@CheckWhetherBlank[1]{%
  \romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}{}}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \CheckWhetherBrace{<Argument which is to be checked>}%
%%                   {<Tokens to be delivered in case that argument
%%                     which is to be checked has a leading
%%                     explicit catcode-1-character-token>}%
%%                   {<Tokens to be delivered in case that argument
%%                     which is to be checked does not have a
%%                     leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
  \expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does have a
%%                                       leading explicit space-token>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does not have a
%%                                       a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
  \romannumeral\UD@CheckWhetherNull{#1}%
  {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
  {%
    % Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
    % and thus do not disturb when the test is carried out within \halign/\valign:
    \expandafter\UD@firstoftwo\expandafter{%
      \expandafter\expandafter\expandafter\UD@stopromannumeral
      \romannumeral\expandafter\UD@secondoftwo
      \string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
    }{}%
  }%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
  \long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
    {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
    {\expandafter\expandafter\expandafter\UD@stopromannumeral
     \expandafter\expandafter\expandafter}%
     \expandafter\UD@secondoftwo\expandafter{\string}%
  }%
}%
%%-----------------------------------------------------------------------------
%% Extract K-th inner undelimited argument:
%%-----------------------------------------------------------------------------
%% \UD@ExtractKthArg{<integer K>}%
%%                  {<tokens in case list of undelimited args doesn't have a k-th argumnent>}%
%%                  {<list of undelimited args>} %
%% 
%% In case there is no K-th argument in <list of indelimited args> : 
%%   Does deliver <tokens in case list of undelimited args doesn't have a k-th argumnent.
%% In case there is a K-th argument in <list of indelimited args> : 
%%   Does deliver that K-th argument with one level of braces removed.
%%
%% Examples:
%%
%%   \UD@ExtractKthArg{0}{not available}{ABCDE} yields: not available
%%
%%   \UD@ExtractKthArg{3}{not available}{ABCDE} yields:  C
%%
%%   \UD@ExtractKthArg{3}{not available}{AB{CD}E} yields:  CD
%%
%%   \UD@ExtractKthArg{4}{not available}{{001}{002}{003}{004}{005}} yields: 004
%%
%%   \UD@ExtractKthArg{6}{not available}{{001}{002}{003}} yields: not available 
%%.............................................................................
\newcommand\UD@ExtractKthArg[2]{%
  \romannumeral
  % #1: <integer number K>
  % #2: <action if there is no K-th argument>
  \expandafter\UD@ExtractKthArgCheck\expandafter{\romannumeral\number\number#1 000}{#2}%
}%
\newcommand\UD@ExtractKthArgCheck[3]{%
  \UD@CheckWhetherNull{#1}{\UD@stopromannumeral#2}{% empty
    \expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
  }%
}%
\begingroup
\def\UD@ExtractFirstArgLoop#1{%
  \endgroup
  \@ifdefinable\UD@RemoveTillFrozenrelax{%
    \long\def\UD@RemoveTillFrozenrelax##1##2#1{{##1}}%
  }%
  \newcommand\UD@ExtractKthArgLoop[3]{%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo##3{}.}{\UD@stopromannumeral##2}{%
      \UD@CheckWhetherNull{##1}{%
        \UD@ExtractFirstArgLoop{##3#1}%
      }{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}##3}%
        {\expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}##1}{##2}}%
      }%
    }%
  }%
}%
\expandafter\expandafter\expandafter\UD@ExtractFirstArgLoop
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
%% Usage of frozen-\relax as delimiter is for speeding things up by reducing the
%% amount of iterations needed. I chose frozen-\relax because David Carlisle 
%% pointed out in   <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractKthArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\expandafter\UD@stopromannumeral\UD@secondoftwo{}#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%%-----------------------------------------------------------------------------
%% Fork whether argument is digit 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, s.th. else
%%.............................................................................
\@ifdefinable\UD@gobbletoexclam{\long\def\UD@gobbletoexclam#1!{}}%
\@ifdefinable\UD@DigitFork{%
  \long\def\UD@DigitFork#1!1!2!3!4!5!6!7!8!9!0!#2#3!!!!{#2}%
}%
\newcommand\UD@ForkDigit[1]{%
  % #1 argument to examine
  % #2 = {%
  %        {tokens in case digit 1}%
  %        {tokens in case digit 2}%
  %        {tokens in case digit 3}%
  %        {tokens in case digit 4}%
  %        {tokens in case digit 5}%
  %        {tokens in case digit 6}%
  %        {tokens in case digit 7}%
  %        {tokens in case digit 8}%
  %        {tokens in case digit 9}%
  %        {tokens in case digit 0}%
  %        {tokens in case no digit}%
  %      }% 
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletoexclam#1!}%
  {%
    \expandafter\UD@firstoftwo\expandafter{%
      \UD@DigitFork
      !#1!2!3!4!5!6!7!8!9!0!{\UD@ExtractKthArg{1}{}}% digit 1
      !1!#1!3!4!5!6!7!8!9!0!{\UD@ExtractKthArg{2}{}}% digit 2
      !1!2!#1!4!5!6!7!8!9!0!{\UD@ExtractKthArg{3}{}}% digit 3
      !1!2!3!#1!5!6!7!8!9!0!{\UD@ExtractKthArg{4}{}}% digit 4
      !1!2!3!4!#1!6!7!8!9!0!{\UD@ExtractKthArg{5}{}}% digit 5
      !1!2!3!4!5!#1!7!8!9!0!{\UD@ExtractKthArg{6}{}}% digit 6
      !1!2!3!4!5!6!#1!8!9!0!{\UD@ExtractKthArg{7}{}}% digit 7
      !1!2!3!4!5!6!7!#1!9!0!{\UD@ExtractKthArg{8}{}}% digit 8
      !1!2!3!4!5!6!7!8!#1!0!{\UD@ExtractKthArg{9}{}}% digit 9
      !1!2!3!4!5!6!7!8!9!#1!{\UD@ExtractKthArg{10}{}}% digit 0
      !1!2!3!4!5!6!7!8!9!0!{\UD@ExtractKthArg{11}{}}!!!!% no digit
    }{}% 
  }{\UD@ExtractKthArg{11}{}}% no digit
}%
%%-----------------------------------------------------------------------------
%% Extract sequences of explicit category 12 digit character tokens from
%% argument:
%%-----------------------------------------------------------------------------
%%
%% \ExtractDigitSequences{<argument>}
%%
%% If {<argument>} is, e.g.,   {\foo 12 bar\fi 345 whoozle \endcsname { 006 {777}} dd}
%% , then you get a list of undelimited/brace-nested arguments:
%% {12}{345}{006}{777}
%%
%% Due to \romannumeral-expansion the result is delivered after two
%% expansion-steps.
%%
%%.............................................................................
\newcommand\UD@DigitExtractLoop[3]{%
  % #1 remaining token list to process
  % #2 Numbers gathered so far
  % #3 digits gathered so far
  \UD@CheckWhetherBlank{#1}{%
    \UD@CheckWhetherNull{#3}{\UD@stopromannumeral#2}%
                            {\expandafter\UD@PassFirstToSecond\expandafter{\number#3}{\UD@stopromannumeral#2}}%
  }{%
    \UD@CheckWhetherBrace{#1}{%
      \expandafter\UD@PassFirstToSecond\expandafter{%
        \romannumeral\expandafter\UD@Exchange\expandafter{%
          \romannumeral\expandafter\expandafter\expandafter\UD@DigitExtractLoop
                       \expandafter\expandafter\expandafter{\UD@ExtractKthArg{1}{}{#1}}{}{}%
        }{%
          \UD@CheckWhetherNull{#3}{\UD@stopromannumeral#2}%
                                  {\expandafter\UD@PassFirstToSecond\expandafter{\number#3}{\UD@stopromannumeral#2}}%
        }%
      }%
      {\expandafter\UD@DigitExtractLoop\expandafter{\UD@firstoftwo{}#1}}{}%
    }{%
      \UD@CheckWhetherLeadingExplicitSpace{#1}{%
        \UD@CheckWhetherNull{#3}{%
          \expandafter\UD@DigitExtractLoop\expandafter{\UD@removespace#1}{#2}{}%
        }{%
          \expandafter\expandafter\expandafter\UD@PassFirstToSecond\expandafter\expandafter\expandafter{%
            \expandafter\UD@PassFirstToSecond\expandafter{\number#3}{#2}%
          }{%
            \expandafter\UD@DigitExtractLoop\expandafter{\UD@removespace#1}%
          }{}%
        }%
      }{%
        \expandafter\expandafter\expandafter\UD@ForkDigit
        \expandafter\expandafter\expandafter{\UD@ExtractKthArg{1}{}{#1}}{%
          {\UD@Exchange{{#2}{#31}}}{\UD@Exchange{{#2}{#32}}}{\UD@Exchange{{#2}{#33}}}%
          {\UD@Exchange{{#2}{#34}}}{\UD@Exchange{{#2}{#35}}}{\UD@Exchange{{#2}{#36}}}%
          {\UD@Exchange{{#2}{#37}}}{\UD@Exchange{{#2}{#38}}}{\UD@Exchange{{#2}{#39}}}%
          {\UD@Exchange{{#2}{#30}}}%
          {\UD@CheckWhetherNull{#3}{\UD@Exchange{{#2}{}}}{%
            \expandafter\expandafter\expandafter\UD@Exchange
            \expandafter\expandafter\expandafter{%
            \expandafter\expandafter\expandafter{%
              \expandafter\UD@PassFirstToSecond\expandafter{\number#3}{#2}%
            }{}}%
          }}%
        }%
        {\expandafter\UD@DigitExtractLoop\expandafter{\UD@firstoftwo{}#1}}%
      }%
    }%
  }%
}%
\newcommand\ExtractDigitSequences[1]{\romannumeral\UD@DigitExtractLoop{#1}{}{}}%
\makeatother


\documentclass{article}

\begin{document}

\begin{verbatim}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
  \ExtractDigitSequences{\foo 12 bar\fi 345 whoozle \endcsname { 006 {777}} dd890jj}%
}%
{\ttfamily \meaning\test}
\end{verbatim}

\noindent yields:
\vskip\partopsep\vskip\topsep

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
  \ExtractDigitSequences{\foo 12 bar\fi 345 whoozle \endcsname { 006 {777}} dd890jj}%
}%
{\ttfamily\noindent\meaning\test}

\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .