Next: Improved fatal_error, Previous: Improved cleardivert, Up: Answers [Contents][Index]
capitalize
The capitalize
macro (see Patsubst) as presented earlier does
not allow clients to follow the quoting rule of thumb. Consider the
three macros active
, Active
, and ACTIVE
, and the
difference between calling capitalize
with the expansion of a
macro, expanding the result of a case change, and changing the case of a
double-quoted string:
$ m4 -I examples include(`capitalize.m4')dnl define(`active', `act1, ive')dnl define(`Active', `Act2, Ive')dnl define(`ACTIVE', `ACT3, IVE')dnl upcase(active) ⇒ACT1,IVE upcase(`active') ⇒ACT3, IVE upcase(``active'') ⇒ACTIVE downcase(ACTIVE) ⇒act3,ive downcase(`ACTIVE') ⇒act1, ive downcase(``ACTIVE'') ⇒active capitalize(active) ⇒Act1 capitalize(`active') ⇒Active capitalize(``active'') ⇒_capitalize(`active') define(`A', `OOPS') ⇒ capitalize(active) ⇒OOPSct1 capitalize(`active') ⇒OOPSctive
First, when capitalize
is called with more than one argument, it
was throwing away later arguments, whereas upcase
and
downcase
used ‘$*’ to collect them all. The fix is simple:
use ‘$*’ consistently.
Next, with single-quoting, capitalize
outputs a single character,
a set of quotes, then the rest of the characters, making it impossible
to invoke Active
after the fact, and allowing the alternate macro
A
to interfere. Here, the solution is to use additional quoting
in the helper macros, then pass the final over-quoted output string
through _arg1
to remove the extra quoting and finally invoke the
concatenated portions as a single string.
Finally, when passed a double-quoted string, the nested macro
_capitalize
is never invoked because it ended up nested inside
quotes. This one is the toughest to fix. In short, we have no idea how
many levels of quotes are in effect on the substring being altered by
patsubst
. If the replacement string cannot be expressed entirely
in terms of literal text and backslash substitutions, then we need a
mechanism to guarantee that the helper macros are invoked outside of
quotes. In other words, this sounds like a job for changequote
(see Changequote). By changing the active quoting characters, we
can guarantee that replacement text injected by patsubst
always
occurs in the middle of a string that has exactly one level of
over-quoting using alternate quotes; so the replacement text closes the
quoted string, invokes the helper macros, then reopens the quoted
string. In turn, that means the replacement text has unbalanced quotes,
necessitating another round of changequote
.
In the fixed version below, (also shipped as
m4-1.4.19/examples/capitalize2.m4), capitalize
uses the alternate quotes of ‘<<[’ and ‘]>>’ (the longer
strings are chosen so as to be less likely to appear in the text being
converted). The helpers _to_alt
and _from_alt
merely
reduce the number of characters required to perform a
changequote
, since the definition changes twice. The outermost
pair means that patsubst
and _capitalize_alt
are invoked
with alternate quoting; the innermost pair is used so that the third
argument to patsubst
can contain an unbalanced
‘]>>’/‘<<[’ pair. Note that upcase
and downcase
must be redefined as _upcase_alt
and _downcase_alt
, since
they contain nested quotes but are invoked with the alternate quoting
scheme in effect.
$ m4 -I examples include(`capitalize2.m4')dnl define(`active', `act1, ive')dnl define(`Active', `Act2, Ive')dnl define(`ACTIVE', `ACT3, IVE')dnl define(`A', `OOPS')dnl capitalize(active; `active'; ``active''; ```actIVE''') ⇒Act1,Ive; Act2, Ive; Active; `Active' undivert(`capitalize2.m4')dnl ⇒divert(`-1') ⇒# upcase(text) ⇒# downcase(text) ⇒# capitalize(text) ⇒# change case of text, improved version ⇒define(`upcase', `translit(`$*', `a-z', `A-Z')') ⇒define(`downcase', `translit(`$*', `A-Z', `a-z')') ⇒define(`_arg1', `$1') ⇒define(`_to_alt', `changequote(`<<[', `]>>')') ⇒define(`_from_alt', `changequote(<<[`]>>, <<[']>>)') ⇒define(`_upcase_alt', `translit(<<[$*]>>, <<[a-z]>>, <<[A-Z]>>)') ⇒define(`_downcase_alt', `translit(<<[$*]>>, <<[A-Z]>>, <<[a-z]>>)') ⇒define(`_capitalize_alt', ⇒ `regexp(<<[$1]>>, <<[^\(\w\)\(\w*\)]>>, ⇒ <<[_upcase_alt(<<[<<[\1]>>]>>)_downcase_alt(<<[<<[\2]>>]>>)]>>)') ⇒define(`capitalize', ⇒ `_arg1(_to_alt()patsubst(<<[<<[$*]>>]>>, <<[\w+]>>, ⇒ _from_alt()`]>>_$0_alt(<<[\&]>>)<<['_to_alt())_from_alt())') ⇒divert`'dnl
Next: Improved fatal_error, Previous: Improved cleardivert, Up: Answers [Contents][Index]