TeX - LaTeX Asked by Tommiie on May 13, 2021
Edit 2: At the bottom I have updated the question after incorporating the answer from here: newcommand for index inserts unwanted spaces when using {}
I created a function to display a command in bold with optionally giving the man page section in parenthesis.
usepackage{xparse}
newcommand*{man}[2][]{%
textbf{#2}IfNoValueF{#1}{(#1)}%
}
Now I also want to be able to create an index entry for these commands. So I created a wrapper command:
newcommand*{mani}[2][]{%
man[#1]{#2}%
index{#2@man[#1]{#2}}%
}
However, sometimes I want to create an index entry using the same formatting, but not print the command in the running text. Here is a MWE:
documentclass{memoir}
usepackage{xparse}
newcommand*{man}[2][]{%
textbf{#2}IfNoValueF{#1}{(#1)}%
}
newcommand*{mani}[2][]{%
man[#1]{#2}%
index{#2@man[#1]{#2}}%
}
makeindex
begin{document}
Only an index entry: index{ssh-keygen@man[1]{ssh-keygen}}
Hello world
Command and index entry: mani[1]{ssh-keygen}
printindex
end{document}
The file output.idx
contains two lines, the second containing an additional space. The question thus is, why is there an additional space and how can I fix it so there is no additional space?
indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}
indexentry{ssh-keygen@man [1]{ssh-keygen}}{1}
Edit 1: I’ve added a screenshot of the result of this extra space: two identical index entries are printed.
I have updated my code as per the provided solution but I now get an error message which I do not understand.
documentclass{memoir}
usepackage{xparse}
makeindex
NewDocumentCommandman{om}{%
textbf{#2}IfNoValueF{#1}{(#1)}%
}
makeatletter
newcommand*{mani}{%
@bsphack
begingroup
@sanitize
@mani
}
newcommand*{@mani}[2][]{%
man[#1]{#2}%
@wrindex{#2@stringman[#1]{#2}}%
}
makeatletter
begin{document}
Two separate commands: man[1]{ssh-keygen}index{ssh-keygen@man[1]{ssh-keygen}}
Hello world
One command: mani[1]{ssh-keygen}
printindex
end{document}
OverLeaf states:
The compiler is having trouble understanding a command you have used. Check that the command is spelled correctly. If the command is part of a package, make sure you have included the package in your preamble using usepackage{…}.
The log file reports:
! Missing number, treated as zero.
<to be read again>
{
l.25 One command: mani[1]{ssh-keygen}
A number should have been here; I inserted `0'.
(If you can't figure out why I needed to see a number,
look up `weird error' in the index to The TeXbook.)
The command index
is expected to get its argument by reading and tokenizing things from the .tex-input file under @sanitize
-category-code-régime. (@sanitize
-category-code-régime means: Space-character, ,
$
, &
, #
, ^
, _
, %
and ~
have category code 12(other).) This is for several reasons—e.g.,
But with your command mani
the command index
does get its argument passed from mani
. When mani
gathers/composes index
's argument, the tokens forming that argument are not tokenized under @sanitize
-category-code-régime but are tokenized under normal category-code-régime.
Beneath other things tokenizing under normal category-code-régime implies that phrases like man
get tokenized as control-word-tokens, not as character-sequences ,
m
, a
, n
. When control-word-tokens get written unexpanded to text-file, e.g., some .idx
-file belonging to the process of creating the index, a space-character will be appended. I.e., the character-sequence ,
m
, a
, n
, ⟨space character⟩
will be written.
Within the definition of mani
you can apply string
to the command man
in order to turn it into a sequence of character-tokens. (Hereby it is relied on only one input-character having category code 0 (escape) and the value of the integer-parameter escapechar
being equal to the number of the code-point of that character in the TeX-engine's internal character-encoding-scheme. Usually the backslash-character is the only character of category code 0(escape) and usually
escapechar
has the value 92 which is the number of the backslash-character's code-point in the TeX-engine's internal character-encoding-scheme.)
documentclass{memoir}
usepackage{xparse}
NewDocumentCommand{man}{om}{%
textbf{#2}IfNoValueF{#1}{(#1)}%
}
NewDocumentCommand{mani}{om}{%
IfNoValueTF{#1}{%
man{#2}%
index{#2@stringman{#2}}%
}{%
man[#1]{#2}%
index{#2@stringman[#1]{#2}}%
}%
}%
makeindex
begin{document}
Only an index entry: index{ssh-keygen@man[1]{ssh-keygen}}
Hello world
Command and index entry: mani[1]{ssh-keygen}
Only an index entry: index{ssh-keygen-no-optional-argument@man{ssh-keygen-no-optional-argument}}
Hello world
Command and index entry: mani{ssh-keygen-no-optional-argument}
printindex
end{document}
With the example above the resulting .idx-file looks like this:
indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}
indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}
indexentry{ssh-keygen-no-optional-argument@man{ssh-keygen-no-optional-argument}}{1}
indexentry{ssh-keygen-no-optional-argument@man{ssh-keygen-no-optional-argument}}{1}
With this approach, the only thing "stringified" within the argument of index
, which is supplied via the macro mani
, is the phrase man
.
Things that come from mani
's first or second argument are not stringified. In case the sets of tokens supplied via these arguments also contain control-word-tokens, you might get unwanted spaces here, too.
I can offer a routine StringifyNAct
which applies string
to each token within its argument:
StringifyNAct{⟨action⟩}{⟨token 1⟩⟨token 2⟩...⟨token n⟩}
yields:
⟨action⟩{⟨stringification of token 1⟩}%
⟨action⟩{⟨stringification of token 2⟩}%
...
⟨action⟩{⟨stringification of token n⟩}%
whereby "stringification of token" means the result of applying string
to the token in question.
Due to romannumeral
-expansion the result is delivered by triggering two expansion-steps (e.g., via two expandafter
-chains).
I suggest the command mani
to read and tokenize its arguments under normal category-code-régime, but with the space-character (and probably also the horizontal-tab-character, addressable as ^^I
with TeX's ^^
-notation) being of category-code 12(other), then to apply StringifyNAct
to the arguments, then passing the result thereof to the index
-command and—nested in scantokens
—to the man
-command:
documentclass{memoir}
makeatletter
%%========================Code for StringifyNAct==============================
%%
%% Copyright (C) 2019, 2020 by Ulrich Diez ([email protected])
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public Licence (LPPL), either
%% version 1.3 of this license or (at your option) any later
%% version. (The latest version of this license is in:
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included
%% documentation nor for any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%%=============================================================================
%% Paraphernalia:
%% UD@firstoftwo, UD@secondoftwo,
%% UD@PassFirstToSecond, UD@Exchange, UD@removespace
%% UD@CheckWhetherNull, UD@CheckWhetherBrace,
%% UD@CheckWhetherLeadingSpace, UD@ExtractFirstArg
%%=============================================================================
newcommandUD@firstoftwo[2]{#1}%
newcommandUD@secondoftwo[2]{#2}%
newcommandUD@PassFirstToSecond[2]{#2{#1}}%
newcommandUD@Exchange[2]{#2#1}%
newcommandUD@removespace{}UD@firstoftwo{defUD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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>
newcommandUD@CheckWhetherNull[1]{%
romannumeral0expandafterUD@secondoftwostring{expandafter
UD@secondoftwoexpandafter{expandafter{string#1}expandafter
UD@secondoftwostring}expandafterUD@firstoftwoexpandafter{expandafter
UD@secondoftwostring}expandafterexpandafterUD@firstoftwo{ }{}%
UD@secondoftwo}{expandafterexpandafterUD@firstoftwo{ }{}UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has leading
%% catcode-1-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has no leading
%% catcode-1-token>}%
newcommandUD@CheckWhetherBrace[1]{%
romannumeral0expandafterUD@secondoftwoexpandafter{expandafter{%
string#1.}expandafterUD@firstoftwoexpandafter{expandafter
UD@secondoftwostring}expandafterexpandafterUD@firstoftwo{ }{}%
UD@firstoftwo}{expandafterexpandafterUD@firstoftwo{ }{}UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
newcommandUD@CheckWhetherLeadingSpace[1]{%
romannumeral0UD@CheckWhetherNull{#1}%
{expandafterexpandafterUD@firstoftwo{ }{}UD@secondoftwo}%
{expandafterUD@secondoftwostring{UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
newcommandUD@CheckWhetherLeadingSpaceB{}%
longdefUD@CheckWhetherLeadingSpaceB#1 {%
expandafterUD@CheckWhetherNullexpandafter{UD@secondoftwo#1{}}%
{UD@Exchange{UD@firstoftwo}}{UD@Exchange{UD@secondoftwo}}%
{UD@Exchange{ }{expandafterexpandafterexpandafterexpandafter
expandafterexpandafterexpandafter}expandafterexpandafter
expandafter}expandafterUD@secondoftwoexpandafter{string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%.............................................................................
newcommandUD@RemoveTillUD@SelDOm{}%
longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
newcommandUD@ExtractFirstArg[1]{%
romannumeral0%
UD@ExtractFirstArgLoop{#1UD@SelDOm}%
}%
newcommandUD@ExtractFirstArgLoop[1]{%
expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}%
{ #1}%
{expandafterUD@ExtractFirstArgLoopexpandafter{UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% In case an argument's first token is an opening brace, stringify that and
%% add another opening brace before that and remove everything behind the
%% matching closing brace:
%% UD@StringifyOpeningBrace{{Foo}bar} yields {{Foo} whereby the second
%% opening brace is stringified:
%%.............................................................................
newcommandUD@StringifyOpeningBrace[1]{%
romannumeral0%
expandafterUD@ExtractFirstArgLoopexpandafter{%
romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
expandafterexpandafter
expandafter {%
expandafterUD@firstoftwo
expandafter{%
expandafter}%
romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
expandafterstring
expandafter}%
string#1%
UD@SelDOm}%
}%
%%-----------------------------------------------------------------------------
%% In case an argument's first token is an opening brace, remove everything till
%% finding the corresponding closing brace. Then stringify that closing brace:
%% UD@StringifyClosingBrace{{Foo}bar} yields: {}bar} whereby the first closing
%% brace is stringified:
%%.............................................................................
newcommandUD@StringifyClosingBrace[1]{%
romannumeral0expandafterexpandafterexpandafter
UD@StringifyClosingBraceloop
UD@ExtractFirstArg{#1}{#1}%
}%
newcommandUD@CheckWhetherStringifiedOpenBraceIsSpace[1]{%
%% This can happen when character 32 (space) has catcode 1...
expandafterUD@CheckWhetherLeadingSpaceexpandafter{%
romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
expandafterUD@secondoftwo
expandafter{%
expandafter}%
expandafter{%
romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
expandafterUD@firstoftwo
expandafter{%
expandafter}%
romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
expandafterstring
expandafter}%
string#1%
}%
}%
newcommandUD@TerminateStringifyClosingBraceloop[2]{%
UD@Exchange{ }{expandafterexpandafterexpandafter}%
expandafterexpandafter
expandafter{%
expandafterstring
romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
expandafter#1%
string#2%
}%
}%
newcommandUD@StringifyClosingBraceloopRemoveElement[4]{%
expandafterUD@PassFirstToSecondexpandafter{expandafter
{romannumeral0expandafterUD@secondoftwostring}{}%
UD@CheckWhetherStringifiedOpenBraceIsSpace{#4}{%
UD@Exchange{UD@removespace}%
}{%
UD@Exchange{UD@firstoftwoexpandafter{expandafter}}%
}{%
UD@Exchange{ }{expandafterexpandafterexpandafter}%
expandafter#1%
romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
expandafter
}%
string#4%
}{expandafterUD@StringifyClosingBraceloopexpandafter{#2#3}}%
}%
newcommandUD@StringifyClosingBraceloop[2]{%
UD@CheckWhetherNull{#1}{%
UD@CheckWhetherStringifiedOpenBraceIsSpace{#2}{%
UD@TerminateStringifyClosingBraceloop{UD@removespace}%
}{%
UD@TerminateStringifyClosingBraceloop{UD@firstoftwoexpandafter{expandafter}}%
}%
{#2}%
}{%
UD@CheckWhetherLeadingSpace{#1}{%
UD@StringifyClosingBraceloopRemoveElement
{UD@removespace}{UD@removespace}%
}{%
UD@StringifyClosingBraceloopRemoveElement
{UD@firstoftwoexpandafter{expandafter}}{UD@firstoftwo{}}%
}%
{#1}{#2}%
}%
}%
%%-----------------------------------------------------------------------------
%% Apply <action> to the stringification of each token of the argument:
%%
%% StringifyNAct{<action>}{<token 1><token 2>...<token n>}
%%
%% yields: <action>{<stringification of token 1>}%
%% <action>{<stringification of token 2>}%
%% ...
%% <action>{<stringification of token n>}%
%%
%% whereby "stringification of token" means the result of applying string
%% to the token in question.
%% Due to romannumeral-expansion the result is delivered after two
%% expandafter-chains.
%% If you leave <action> empty, you can apply a loop on the list formed by
%% {<stringification of token 1>}%
%% {<stringification of token 2>}%
%% ...
%% {<stringification of token n>}%
%%
%% Below a macro ConcatenateStringifiedtokens is implemented which loops
%% on that list for concatenating.
%%.............................................................................
newcommandStringifyNAct{%
romannumeral0StringifyNActLoop{}%
}%
%%.............................................................................
%% StringifyNActLoop{{<stringification of token 1>}...{<stringification of token k-1>}}%
%% {<action>}%
%% {<token k>...<token n>}
%%.............................................................................
newcommandStringifyNActLoop[3]{%
UD@CheckWhetherNull{#3}{%
UD@firstoftwo{ }{}#1%
}{%
UD@CheckWhetherBrace{#3}{%
expandafterexpandafterexpandafterUD@Exchange
expandafterexpandafterexpandafter{%
UD@StringifyClosingBrace{#3}%
}{%
expandafterStringifyNActLoopexpandafter{%
romannumeral0%
expandafterexpandafterexpandafterUD@Exchange
expandafterexpandafterexpandafter{UD@StringifyOpeningBrace{#3}}{StringifyNActLoop{#1}{#2}}%
}{#2}%
}%
}{%
UD@CheckWhetherLeadingSpace{#3}{%
expandafterUD@PassFirstToSecondexpandafter{UD@removespace#3}{%
StringifyNActLoop{#1#2{ }}{#2}%
}%
}{%
expandafterUD@PassFirstToSecondexpandafter{UD@firstoftwo{}#3}{%
expandafterStringifyNActLoopexpandafter{%
romannumeral0%
expandafterexpandafterexpandafterexpandafterexpandafterexpandafterexpandafterUD@PassFirstToSecond
expandafterexpandafterexpandafterexpandafterexpandafterexpandafterexpandafter{%
expandafterexpandafterexpandafterstring
expandafterUD@Exchange
romannumeral0UD@ExtractFirstArgLoop{#3UD@SelDOm}{}%
}{ #1#2}%
}%
{#2}%
}%
}%
}%
}%
}%
%% The promised loop for concatenating stringified tokens - apply as:
%%
%% romannumeral0%
%% expandafterexpandafterexpandafter
%% ConcatenateStringifiedtokens
%% StringifyNAct{}{<tokens to stringify>}relax
%%
newcommand*ConcatenateStringifiedtokens{%
ConcatenateStringifiedtokensloop{ }%
}%
newcommandConcatenateStringifiedtokensloop[2]{%
ifxrelax#2expandafter@firstoftwoelseexpandafter@secondoftwofi
{#1}{%
ConcatenateStringifiedtokensloop{#1#2}%
}%
}%
%%=================== End of code for StringifyNAct ==========================
makeatother
usepackage{xparse}
makeatletter
NewDocumentCommand{man}{om}{%
UD@CheckWhetherNull{#2}{}{%
toks@{#2}%
textbf{thetoks@}%
}%
IfNoValueF{#1}{(#1)}%
}%
NewDocumentCommand{mani}{}{%
begingroup
catcode` =12relax
catcode`^^I=12relax
maniinner
}%
NewDocumentCommand{maniinner}{om}{%
endgroup
IfNoValueTF{#1}{%
expandaftermaniinnerinnerexpandafter{%
romannumeral0%
expandafterexpandafterexpandafter
ConcatenateStringifiedtokens
StringifyNAct{}{#2}relax
}%
}{%
expandafterUD@PassFirstToSecondexpandafter{%
romannumeral0%
expandafterexpandafterexpandafter
ConcatenateStringifiedtokens
StringifyNAct{}{#2}relax
}{%
expandaftermaniinnerinnerexpandafter[expandafter{%
romannumeral0%
expandafterexpandafterexpandafter
ConcatenateStringifiedtokens
StringifyNAct{}{#1}relax
}]%
}%
}%
}%
makeatother
begingroup
newcommandmaniinnerinner[1]{%
endgroup
NewDocumentCommand{maniinnerinner}{om}{%
IfNoValueTF{##1}{%
scantokens{man{##2}#1}%
index{##2@stringman{##2}}%
}{%
scantokens{man[##1]{##2}#1}%
index{##2@stringman[##1]{##2}}%
}%
}%
}%
catcode`%=12relax
maniinnerinner{%}%
makeindex
begin{document}
Only an index entry: index{ssh-keygen@man[1]{ssh-keygen}}
Hello world
Command and index entry: mani[1]{ssh-keygen}
Only an index entry: index{ssh-keygen-no-optional-argument@man{ssh-keygen-no-optional-argument}}
Hello world
Command and index entry: mani{ssh-keygen-no-optional-argument}
newcommandke{ke}%
newcommandone{1}%
Only an index entry: index{ssh-ke ystring#gen@man[one]{ssh-ke ystring#gen}}
Hello world
Command and index entry: mani[one]{ssh-ke ystring#gen}
printindex
end{document}
With the example above the resulting .idx-file looks like this:
indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}
indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}
indexentry{ssh-keygen-no-optional-argument@man{ssh-keygen-no-optional-argument}}{1}
indexentry{ssh-keygen-no-optional-argument@man{ssh-keygen-no-optional-argument}}{1}
indexentry{ssh-ke ystring#gen@man[one]{ssh-ke ystring#gen}}{1}
indexentry{ssh-ke ystring#gen@man[one]{ssh-ke ystring#gen}}{1}
Answered by Ulrich Diez on May 13, 2021
The name of the write
-handle used for the .idx-file with memoir differs from the name of the write
-handle used with the LaTeX 2ε-macro @wrindex
.
Thus you need to map the name of the "kernel-write
-handle" for the index to the "memoir-write
-handle" for the index:
documentclass{memoir}
usepackage{xparse}
makeindex
NewDocumentCommandman{om}{%
textbf{#2}IfNoValueF{#1}{(#1)}%
}
makeatletter
newcommand*{mani}{%
@bsphack
begingroup
@sanitize
@mani
}
NewDocumentCommand{@mani}{om}{%
@ifundefined{@indexfile}{%
expandafterletexpandafter@indexfilecsnamejobname @idxfileendcsname
}{}%
IfNoValueTF{#1}{%
man{#2}%
@wrindex{#2@stringman{#2}}%
}{%
man[#1]{#2}%
@wrindex{#2@stringman[#1]{#2}}%
}%
}
makeatletter
begin{document}
Two separate commands: man[1]{ssh-keygen}index{ssh-keygen@man[1]{ssh-keygen}}
Hello world
One command: mani[1]{ssh-keygen}
printindex
end{document}
The .idx-file looks like this:
indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}
indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}
Be aware that—unlike the approach of stringification after tokenization presented in my other answer—@sanitize
does not handle the balancing of curly braces in case the arguments of mani
are to contain the control-symbol-token {
and/or the control-symbol-token }
.
Answered by Ulrich Diez on May 13, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP