Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Let's say I have a file foo.sty that contains the following:

\newcommand{\a}{x}
\newcommand{\b}[2]{x}
\newcommand{\c}[2][y]{x}
\newenvironment{d}[3]{}{}

I would like to be able to run this through some software and produce a data file saying what commands and environments have been defined, and how many optional and mandatory arguments they have -- something like this:

command,a,0,0
command,b,0,2
command,c,1,1
environment,d,0,3

I could code up something in perl that would work most of the time, but I'm wondering if there's any way to do it so that it's highly reliable. The impression I get is that completely correct parsing of LaTeX is hard, and basically the only thing that does it is LaTeX. Is there some way of getting LaTeX to do this, maybe with an appropriately constructed .tex file that pulls in foo.sty, introspects, and then does write18's...?

I'm looking for a solution that's open-source and that I can run on linux in an automated way (not a GUI).

share|improve this question
1  
You can redefine \newcommand and \newenvironment to add a hook. Generally speaking, we have various ways to define macros with optional arguments, and it is a bit difficult to detect all of them. –  Leo Liu Jan 8 '12 at 2:58
1  
What do you mean by "commands and environments"? Most macros define secret internal macros behind the scenes; do you want to hit those too? If not, then your job is easy: just scan for \newcommand, \renewcommand, etc. in the top-level files. Or, perhaps better, do something trivial like \outer\let\countednewcommand=\newcommand and so on: then use these "counted" commands for the macros you want to track, and since they are \outer, you can be sure they are all visible to a naive scanner. –  Ryan Reich Jan 8 '12 at 3:31
    
The package storecmd can be adapted to do this, but time is currently a big problem for its author. Perhaps someone can take a look at the possibilities. –  Ahmed Musa Jan 8 '12 at 6:06
    
@Ryan: Good point. For my own application, I don't need the info about a gazillion internal macros, but it also doesn't hurt me to have that info. –  Ben Crowell Jan 8 '12 at 18:49

2 Answers 2

up vote 7 down vote accepted

Put the following code in the preamble or in a .sty file

\usepackage{xparse}
\ExplSyntaxOn
% Define the command that will start the working; we'll redefine \newcommand
% so that \newcommand{\xac}[2][cccc]{abc} will execute
%
%    \command_check:w { \command_check_newcommand:w }{ command } {\xab} 
%
% #1 = \newcommand|\renewcommand|\newenvironment|\renewenvironment (in new form)
% #2 = command|environment
% #3 = the possible *
% #4 = the argument to \newcommand
% #5 = the number of arguments
% #6 = the possible optional argument
% 
% We check if the last argument is missing and take the appropriate action 
\NewDocumentCommand{ \command_check:w }{m m s m O{0} o}
  {
   \IfBooleanTF{#3}
     { \tl_gset:Nn \g_command_check_star_tl { * } \bool_gset_true:N \g_command_check_star_bool }
     { \tl_gset:Nn \g_command_check_star_tl { } \bool_gset_false:N \g_command_check_star_bool }
   \IfNoValueTF{#6}
     { \command_check_noopt:nnnn {#1} {#2} {#4} {#5} }
     { \command_check_opt:nnnnn {#1} {#2} {#4} {#5} {#6} }
  }
% \StartSaveCommands sets up the checks, first by creating aliases for the kernel commands
% and then redefining them as explained above
\NewDocumentCommand{\StartSaveCommands}{}
  {
   \cs_set_eq:NN \command_check_newcommand:w \newcommand
   \cs_set_eq:NN \command_check_renewcommand:w \renewcommand
   \cs_set_eq:NN \command_check_newenvironment:w \newenvironment
   \cs_set_eq:NN \command_check_renewenvironment:w \renewenvironment
   \cs_set:Npn \newcommand { \command_check:w {\command_check_newcommand:w}{command} }
   \cs_set:Npn \renewcommand { \command_check:w {\command_check_renewcommand:w}{command} }
   \cs_set:Npn \newenvironment { \command_check:w {\command_check_newenvironment:w}{environment} }
   \cs_set:Npn \renewenvironment { \command_check:w {\command_check_renewenvironment:w}{environment} }
  }
% \StopSaveCommands restores the kernel commands
\NewDocumentCommand{\StopSaveCommands}{}
  {
   \cs_set_eq:NN \newcommand \command_check_newcommand:w
   \cs_set_eq:NN \renewcommand \command_check_renewcommand:w
   \cs_set_eq:NN \newenvironment \command_check_newenvironment:w 
   \cs_set_eq:NN \renewenvironment \command_check_renewenvironment:w
  }
% \WriteSaveCommands will take care of writing out the list of commands
% with their number of arguments
\NewDocumentCommand{\WriteSaveCommands}{}
  {
   \iow_open:Nn \g_command_check_write { \jobname.cmd }
   \seq_map_inline:Nn \g_command_check_seq {\iow_now:Nx \g_command_check_write { ##1 } }
  }
% Allocate a write stream
\iow_new:N \g_command_check_write
% If there's no optional argument, say \newcommand{\xab}[1]{aaa},
% we want to write out "command,\xab ,1,0", so we store that
% string into an item appended to the sequence \g_command_check_seq
\cs_new:Npn \command_check_noopt:nnnn #1 #2 #3 #4
  {
   \seq_gput_right:Nx \g_command_check_seq
     {
      #2 \bool_if:NT \g_command_check_star_bool { (*) }, 
      \tl_to_str:n {#3} , #4, 0
     }
   \exp_after:wN #1 \g_command_check_star_tl {#3} [#4]
  }
% If there's an optional argument, say \newcommand{\xac}[2][cccc]{abc},
% we want to write out "command,\xac,1,1[cccc]; everything as before,
% but we decrease by 1 the number of stated arguments
\cs_new:Npn \command_check_opt:nnnnn #1 #2 #3 #4 #5
  { 
   \seq_gput_right:Nx \g_command_check_seq
     {
      #2 \bool_if:NT \g_command_check_star_bool { (*) }, \tl_to_str:n {#3} , 
      \int_to_arabic:n {#4-1} , 1[\tl_to_str:n{#5}]
     }
   \exp_after:wN #1 \g_command_check_star_tl {#3}[#4][#5]
  }
% Allocate the sequence, the token list variable and the boolean
\seq_new:N \g_command_check_seq
\tl_new:N \g_command_check_star_tl
\bool_new:N \g_command_check_star_bool
\ExplSyntaxOff

Now you can put your personal commands between \StartSaveCommands and \StopSaveCommands, say

\documentclass{article}

<the above code>

\StartSaveCommands

\newcommand{\xaa}{abc}
\newcommand*{\xab}[1]{abc}
\newcommand{\xac}[2][cccc]{abc}
\renewcommand{\phi}{\varphi}
\newenvironment{xad}{}{}
\newenvironment{xae}[1]{}{}
\newenvironment{xaf}[2][sss]{}{}

\StopSaveCommands
\WriteSaveCommands

\begin{document}

<text>

\end{document}

This will define the commands and environments and write a file with extension .cmd; in our example it would contain

command,\xaa ,0,0
command(*),\xab ,1,0
command,\xac ,1,1[cccc]
command,\phi ,0,0
environment,xad,0,0
environment,xae,1,0
environment,xaf,1,1[sss]

All commands defined with \newcommand or \renewcommand and environments created between \StartSaveCommands and \StopSaveCommands will create an entry in the .cmd file.

(Note: edited to take care also of \newcommand* and \newenvironment*)

share|improve this answer
1  
I'd personally not bother defining \command_check_write:n, instead using \seq_map_inline:Nn and putting the code inline inside the def of \WriteSaveCommands (with hash doubled of course). Also, the code could be simply \iow_now:Nn \command_check_write {#1}, not sure why you x-expand twice. And, nitpicking, I would add g_ in front of that _write variable. –  Bruno Le Floch Jan 8 '12 at 12:07
    
@BrunoLeFloch Fixed, thanks. –  egreg Jan 8 '12 at 13:25
    
Very cool, thanks! But I had some trouble figuring out how to make this into a minimal working example. I tried adding \documentclass{minimal} at the top and \begin{document}...\end{document} at the bottom, and then uncommenting the commented-out section and putting it after \begin{document}. That produced the following error: l.64 \seq_show:N \g_command_check_seq –  Ben Crowell Jan 8 '12 at 14:27
    
@BenCrowell I've reworked the code to make it easier to create an example. –  egreg Jan 8 '12 at 15:01
    
@egreg Can you please add a bit of commentary on the code? Code is ok, but somehow it needs a bit of finesse. –  Yiannis Lazarides Jan 8 '12 at 15:08

in the logfile you'll find:

command, ,\A,0,0
command,*,\AB,0,0
command, ,\B,2,0
command,*,\BA,2,0
command, ,\C,2,y
command,*,\CBA,2,y
environment, ,D,3,0
environment,*,DD,3,0
environment, ,foobar,3,0
environment,*,FooBar,3,y

it also handels a star version of \newcommand and \newenvironment

which can also be saved in an own file. The code can also be extended to \renewcommand and so on ...

\documentclass{minimal}

\makeatletter
\newif\if@star 
\newif\if@command
\let\NewCommand\newcommand
\let\NewEnvironment\newenvironment
\def\newcommand{\global\@commandtrue%
  \@ifnextchar*{\global\@startrue\test@CommEnv}{\global\@starfalse\test@CommEnv*}}
\def\newenvironment{\global\@commandfalse%
  \@ifnextchar*{\global\@startrue\test@CommEnv}{\global\@starfalse\test@CommEnv*}}
\def\test@CommEnv*#1{\@ifnextchar[{\test@CommEnv@i{#1}}{%
    \if@command
      \typeout{command,\if@star*\else\space\fi,\string#1,0,0}%
      \def\next{\NewCommand{#1}}%
    \else
      \typeout{environment,\if@star*\else\space\fi,\string#1,0,0}%
      \def\next{\NewEnvironment{#1}}
    \fi\next}}
\def\test@CommEnv@i#1[#2]{%
  \@ifnextchar[{\test@CommEnv@ii{#1}[#2]}{%
    \if@command
      \typeout{command,\if@star*\else\space\fi,\string#1,#2,0}%
      \def\next{\NewCommand{#1}[#2]}%
    \else
      \typeout{environment,\if@star*\else\space\fi,#1,#2,0}%
      \def\next{\NewEnvironment{#1}[#2]}
    \fi\next}}
\def\test@CommEnv@ii#1[#2][#3]{%
    \if@command
      \typeout{command,\if@star*\else\space\fi,\string#1,#2,#3}%
      \def\next{\NewCommand{#1}[#2][#3]}%
    \else
      \typeout{environment,\if@star*\else\space\fi,#1,#2,#3}%
      \def\next{\NewEnvironment{#1}[#2][#3]}
    \fi\next}

\newcommand{\A}{x}
\newcommand*{\AB}{x}
\newcommand{\B}[2]{x}
\newcommand*{\BA}[2]{x}
\newcommand{\C}[2][y]{x}
\newcommand*{\CBA}[2][y]{x}
\newenvironment{D}[3]{}{}
\newenvironment*{DD}[3]{}{}
\newenvironment{foobar}[3]{}{}
\newenvironment*{FooBar}[3][y]{}{}

\begin{document}
\A \B{1}{2} \C[1]{2} \begin{D}{1}{2}{3} D \end{D}
\end{document}
share|improve this answer
    
Awesome, thanks! I think there's a bug, though. If I change \newenvironment{D}[3]{}{} to \newenvironment{foobar}[3]{}{}, I get Missing \begin{document}. If I change it to \newenvironment{ddddd}[3]{}{} , I get Command \d already defined. It seems like the hook for newenvironment is only eating the first character of the environment name...? (Newcommand seems OK.) –  Ben Crowell Jan 8 '12 at 14:22
    
@BenCrowell: \d is an already existing command, the reason why I used \D. And \newenvironment{foobar} is now working. I edited my code, had forgotten to set #1 into braces –  Herbert Jan 8 '12 at 16:57
    
That works. Thanks! –  Ben Crowell Jan 8 '12 at 18:54
    
Thanks for pointing out the *-variants. –  egreg Jan 8 '12 at 19:14

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.