m4: Improved capitalize

 
 17.7 Solution for 'capitalize'
 ==============================
 
 The 'capitalize' macro (⇒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' (⇒
 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.18/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