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.

Is it possible to define commands in a separate namespace so that they only work within a particular environment?

For example, can I create a package named foo that defines a foo environment and a \newfoocommand that works something like this:

\documentclass{article}

\newcommand{\asdf}{do something}
\newcommand{\zzz}{zzz}

\usepackage{foo}
% doesn't conflict with the above \asdf command because it's in a different
% namespace.
% this actually does \newcommand{\@foo@cmd@asdf}{do something else}
\newfoocommand{\asdf}{do something else}
\newfoocommand{\jkl}{blahblah}

\begin{document}

% expands to "do something"
\asdf
% expands to "zzz"
\zzz
% causes: ERROR: Undefined control sequence.
\jkl

\begin{foo}
  % is interpreted as \@foo@cmd@asdf and expands to "do something else"
  \asdf
  % expands to "zzz" because \@foo@cmd@zzz isn't defined
  \zzz
  % is interpreted as \@foo@cmd@jkl and expands to "blahblah"
  \jkl
\end{foo}

% expands to "do something"
\asdf
% expands to "zzz"
\zzz
% causes: ERROR: Undefined control sequence.
\jkl

\end{document}
share|improve this question
    
Users interested in this question might also be interested in tex.stackexchange.com/questions/39738/… –  Richard Hansen Mar 15 '12 at 16:21
add comment

3 Answers

up vote 12 down vote accepted

With this implementation any command can have different meanings in environments which have \checkenvcommands in their "begin" part:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\newenvcommand}{ m m } % #1 = env name, #2 = command name
  {
   \cs_if_exist:cF { g_envc_#1_list_tl } { \tl_new:c { g_envc_#1_list_tl } }
   \tl_gput_right:cn { g_envc_#1_list_tl } { #2 }
   \exp_after:wN \newcommand \cs:w envc_#1_\cs_to_str:N #2 \cs_end:
  }
\NewDocumentCommand{\checkenvcommands}{ }
  {
   \cs_if_exist:cT { g_envc_\use:c {@currenvir} _list_tl }
     {
      \tl_map_inline:cn { g_envc_\use:c {@currenvir} _list_tl }
        { \cs_set_eq:Nc ##1 { envc_\use:c {@currenvir} _\cs_to_str:N ##1 } }
     }
  }
\ExplSyntaxOff

\newcommand{\asdf}[1]{OUTER DEF - arg is #1}
\newenvcommand{foo}{\asdf}[1]{FOO INNER DEF - arg is #1}
\newenvcommand{baz}{\asdf}[1]{BAZ INNER DEF - arg is #1}

\newenvironment{foo}{\checkenvcommands}{}
\newenvironment{baz}{\checkenvcommands}{}

\begin{document}
\show\asdf

\begin{foo}
\show\asdf
\end{foo}

\show\asdf

\begin{baz}
\show\asdf
\end{baz}

\end{document}

The output is

> \asdf=\long macro:
#1->OUTER DEF - arg is #1.
l.28 \show\asdf

? 
> \asdf=\long macro:
#1->FOO INNER DEF - arg is #1.
l.31 \show\asdf

? 
> \asdf=\long macro:
#1->OUTER DEF - arg is #1.
l.34 \show\asdf

? 
> \asdf=\long macro:
#1->BAZ INNER DEF - arg is #1.
l.37 \show\asdf

The implementation is similar to David's, but it doesn't require to define a \<env>@let macro for each environment where we want "local" meanings. Only a single \checkenvcommands is required in those environments.

The "local" commands are stored in a different token list variable for each environment (in the example \g_envc_foo_list_tl and \g_envc_baz_list_tl), and \checkenvcommands does a mapping so that the "private" version of a command, say \envc_foo_asdf, is made equivalent to \asdf. If the list doesn't exist, nothing is done.

share|improve this answer
    
Very cool, but all that LaTeX3 stuff is strange to me, and strange things scare me. :) What's the motivation for using LaTeX3 stuff instead of a more "basic" (and maybe more portable?) TeX approach like David's? Couldn't David's answer be easily adapted to provide an interface similar to yours? –  Richard Hansen Mar 15 '12 at 16:13
    
Can I add \checkenvcommands to existing environments, perhaps via \AtBeginEnvironment in etoolbox? –  Richard Hansen Mar 15 '12 at 16:18
    
@RichardHansen The motivation is that LaTeX3 provides interfaces for the most common tasks, such as \tl_map_inline:nn that easily allows to pass each token in a list as argument to a command. Thus we don't need to define a \foo@let command for foo, \baz@let command for baz and so on. The code is robust and portable, as it uses only LaTeX3 features that are present since a long time. The answer to the later question about AtBeginEnvironment is yes. –  egreg Mar 15 '12 at 16:23
add comment

You can save the definitions in internal macros and then expose them locally within the foo environment.

\documentclass{article}

\newcommand{\asdf}{do something}
\newcommand{\zzz}{zzz}

%\usepackage{foo}
\makeatletter
\long\def\foo{%
\par FOO\par\hrule
\the\foo@defs}

\newtoks\foo@defs

\def\newfoocommand#1{%
\addto@hook\foo@defs{\foo@let#1}%
\expandafter\newcommand\csname foo\string#1\endcsname}

\def\foo@let#1{%
\expandafter\let\expandafter#1\csname foo\string#1\endcsname}

%\makeatother

% doesn't conflict with the above \asdf command because it's in a different
% namespace.
% this actually does \newcommand{\@foo@cmd@asdf}{do something else}
\newfoocommand{\asdf}{do something else}
\newfoocommand{\jkl}{blahblah}


\begin{document}

% expands to "do something"
\asdf
% expands to "zzz"
\zzz
% causes: ERROR: Undefined control sequence.
\jkl

\begin{foo}
  % is interpreted as \@foo@cmd@asdf and expands to "do something else"
\show\asdf
  \asdf
  % expands to "zzz" because \@foo@cmd@zzz isn't defined
  \zzz
  % is interpreted as \@foo@cmd@jkl and expands to "blahblah"
  \jkl
\end{foo}

\end{document}
share|improve this answer
    
Thanks for the answer. Does this restore the original definitions (or lack thereof) after \end{foo}? See the revised example. –  Richard Hansen Mar 15 '12 at 4:31
    
Yes LaTeX environments make a group so all non-local definitions are discarded. –  David Carlisle Mar 15 '12 at 7:00
add comment

Here is an extension of David Carlisle's solution. I have included it in catoptions package.

\documentclass[a4paper]{article}
\usepackage{catoptions}
\usepackage{xcolor}
\makeatletter
\robust@def*\AtBeginColony#1{\grightaddtocsn{atbegin@#1@colonyhook}}
\robust@def*\AtEndColony#1{\grightaddtocsn{atend@#1@colonyhook}}
\new@def*\colonyarg#1{\@nameuse{colony@arg@\romannumeral#1}}
\robust@def*\newcolony{\cpt@starorlong\new@colony}
\def\new@colony#1{\cpt@testopt{\newcolony@a#1}0}
\def\newcolony@a#1[#2]{%
  \cpt@ifbrack{\newcolony@b{#1}{#2}}{\newcolony@c{#1}{[#2]}}%
}
\def\newcolony@b#1#2[#3]{\newcolony@c{#1}{[#2][{#3}]}}
\robust@def*\renewcolony{\cpt@starorlong\renew@colony}
\def\renew@colony#1{%
  \ifcsndefTF{#1}{}{\cpt@err{Environment '#1' is undefined}\@ehc}%
  \expandafter\let\csname#1\endcsname\relax
  \expandafter\let\csname end#1\endcsname\relax
  \new@colony{#1}%
}
\long\def\newcolony@c#1#2#3#4{%
  \ifcsndefTF{#1}{}{\letcsntocsn{#1}{end#1}}%
  \def\reserved@a[##1]##2\@nil{%
    \chardef\@tempa\z@pt
    \loop\ifnum\@tempa<##1\relax
      \edef\@tempa{\the\numexpr\@tempa+1}%
      \cptexpandarg{\rightaddtocs\@tempb}{%
        \csn@edef{colony@arg@\romannumeral\@tempa}%
        {\noexpand\unexpanded{################\number\@tempa}}%
      }%
    \repeat
  }%
  \reserved@a#2\@nil
  \cptexpandbracenext{\aftercsname\new@command{#1}#2}{%
    \@tempb
    \@nameuse{atbegin@#1@colonyhook}%
    \@nameuse{#1@env@defs}#3%
  }%
  \l@ngrel@x\csn@def{end#1}{%
    #4%
    \@nameuse{atend@#1@colonyhook}%
  }%
}
% #1 = environment name; #2 = command
\robust@def*\newcolonycmd{\cpt@testst\new@colonycmd}
\robust@def\new@colonycmd#1#2{%
  \aftercsname\rightaddtocs{#1@env@defs}{\colonyletcs{#1}{#2}}%
  \cptexpanded{\noexpand\newcommand\ifcpt@st*\fi
    \noexpandcsn{#1@\cptgobblescape#2}}%
}
\def\colonyletcs#1#2{\letcstocsn{#2}{#1@\cptgobblescape#2}}

% Examples:
\newcommand{\asdf}{do something}
\newcommand{\zzz}{zzz}
\newcolony{foo}[2][\hsize]{%
  \endgraf\hrule width#1 depth0pt height.4pt
  \endgraf
  \textsc{This is \texttt{\textcolor{red}{foo}} environment}\\[.5ex]
  Arg 1: {\tt\detokenize{#1}}\\Arg 2: {\tt\detokenize{#2}}\\
}{%
  % Use argument 2 of this colony at exit. This is usually not possible
  % by LaTeX's \newenvironment:  
  \edef\y{\colonyarg{2}}%
}
\AtEndColony{foo}{\def\z#1{#1}\ignorespacesafterend}
\newcolonycmd*{foo}{\asdf}[1][bbb]{%
  Do something else. Arg 1 of {\tt\string\asdf}: {\tt\detokenize{#1}}%
}
\newcolonycmd{foo}{\jkl}{blah blah}

\makeatother
\begin{document}
\parindent0pt
% \asdf expands to "do something": we aren't in environment 'foo'
\asdf
\par
% \zzz expands to "zzz"
\zzz
% Undefined control sequence: we aren't in environment 'foo' and 
% \jkl isn't defined outside 'foo'.
%\jkl

\par\medskip
\def\showcmd#1{{\tt\string#1}: #1}

\begin{foo}[.5\hsize]{this value}
\showcmd\asdf
\endgraf
\showcmd\zzz
\endgraf
\showcmd\jkl
\end{foo}

\end{document}
share|improve this answer
add comment

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.