A recurring theme in my man page clean-up work has been my violent
antipathy for shouting capitals in their texts.  While I don't _like_
being shouted at for reasons other than true emergency, the real problem
with the capitalization convention in man pages is that it happens at
the input source, destroying information (case distinctions) that is
unrecoverable by the typesetting system later.

Here is the last time we discussed the issue:
https://lists.gnu.org/archive/html/groff/2018-12/msg00141.html

The consensus seemed to be that pushing case-transformation
functionality down into language would be worth trying.

So, here's an implementation.  Comments welcome.

I expect some bikeshedding on the names of the requests.  I'm not wedded
to the ones I have; my main criterion is:

* The new request names should collate adjacently in the existing
  request namespace.  E.g., "stringup" and "stringdn" are a much better
  pair than "upstring" and "dnstring".  If someone is looking for one of
  them, it's not going to be long before they wonder what/where the
  other one is.

Regards,
Branden
commit 0b7ce40809549aed643165fbb31dcdaa198454c0
Author: G. Branden Robinson <g.branden.robin...@gmail.com>
Date:   Thu Jul 4 01:07:38 2019 +1000

    Implement .stringdown and .stringup requests.
    
    * src/roff/troff/input.cpp: Add .stringdown and .stringup requests.
    * doc/groff.texi: Document them, including example.
    * man/groff_diff.7.man: Document them.
    * man/groff.7.man: Document them briefly.

diff --git a/ChangeLog b/ChangeLog
index d44bb276..ca6d65d4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2019-07-04  G. Branden Robinson <g.branden.robin...@gmail.com>
+
+	Implement .stringdown and .stringup requests.
+
+	* src/roff/troff/input.cpp: Add .stringdown and .stringup
+	requests.
+	* doc/groff.texi: Document them, including example.
+	* man/groff_diff.7.man: Document them.
+	* man/groff.7.man: Document them briefly.
+
 2019-06-28  G. Branden Robinson <g.branden.robin...@gmail.com>
 
 	devlatin1: Map \(oq to ' on output.
diff --git a/NEWS b/NEWS
index 636804e0..340c0c1a 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,13 @@ VERSION 1.22.5
 
 Troff
 -----
+o New requests 'stringdown' and 'stringup' are implemented.  These transform
+  each letter in their string register argument to the desired case.  For
+  best results, use only the basic Latin alphabet ([A-Za-z]) in your string
+  values; groff special characters (see the groff_char man page) can be used
+  and the output will usually transform in the expected way due to the
+  regular naming convention of the special character escapes.
+
 o On the Latin-1 output device ("groff -T latin1") the output glyph \[oq]
   (opening quote) is now rendered as code point 0x27 (apostrophe) instead of
   0x60 (grave accent).  The ECMA-94 Latin character sets do not define any
diff --git a/doc/groff.texi b/doc/groff.texi
index bcc47cf5..f19b872f 100644
--- a/doc/groff.texi
+++ b/doc/groff.texi
@@ -10590,6 +10590,31 @@ number register @var{reg}.  If @var{reg} doesn't exist, it is created.
 Rename the request, macro, diversion, or string @var{xx} to @var{yy}.
 @endDefreq
 
+@DefreqList {stringdown, str}
+@DefreqListEndx {stringup, str}
+@cindex case-transforming a string (@code{stringdown}, @code{stringup})
+@cindex uppercasing a string (@code{stringup})
+@cindex lowercasing a string (@code{stringdown})
+@cindex up-casing a string (@code{stringup})
+@cindex down-casing a string (@code{stringdown})
+Transform each letter in @var{str} to to lowercase (@code{stringdown})
+or uppercase (@code{stringup}).  For best results, use only the basic
+Latin alphabet (@code{[A-Za-z]}) in your string values; @code{groff}
+special characters (see the @cite{groff_char(7)} man page) can be used
+and the output will usually transform in the expected way due to the
+regular naming convention of the special character escapes.
+
+@Example
+.ds resume R\['e]sum\['e]\"
+\*[resume]
+.stringdown resume
+\*[resume]
+.stringup resume
+\*[resume]
+    @result{} Résumé résumé RÉSUMÉ
+@endExample
+@endDefreq
+
 @Defreq {rm, xx}
 @cindex removing request (@code{rm})
 @cindex request, removing (@code{rm})
diff --git a/man/groff.7.man b/man/groff.7.man
index 24f093fe..f0e02b6c 100644
--- a/man/groff.7.man
+++ b/man/groff.7.man
@@ -2384,6 +2384,18 @@ and sentence space size set to
 of the space width in the current font.
 .
 .TPx
+.REQ .stringdown "stringvar"
+Transform each letter in\~\c
+.I stringvar
+to its lowercase version.
+.
+.TPx
+.REQ .stringup "stringvar"
+Transform each letter in\~\c
+.I stringvar
+to its uppercase version.
+.
+.TPx
 .REQ .sty "n style"
 Associate
 .I style
diff --git a/man/groff_diff.7.man b/man/groff_diff.7.man
index ef31a028..bc692df7 100644
--- a/man/groff_diff.7.man
+++ b/man/groff_diff.7.man
@@ -2168,6 +2168,27 @@ This request is active only if text is justified to both margins (using
 .BR .ad\ b ).
 .
 .TP
+.BI .stringdown \~stringvar
+.TQ
+.BI .stringup \~stringvar
+Transform each letter in\~\c
+.I stringvar
+to lowercase
+.RB ( down )
+or uppercase
+.RB ( up ).
+.
+For best results,
+use only the basic Latin alphabet (\[lq][A-Za-z]\[rq]) in your string
+values;
+.I groff
+special characters
+(see
+.IR groff_char (@MAN7EXT@))
+can be used and the output will usually transform in the expected way
+due to the regular naming convention of the special character escapes.
+.
+.TP
 .BI .sty\  n\ f
 Associate style\~\c
 .I f
diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp
index a1bd8eaf..e01308b9 100644
--- a/src/roff/troff/input.cpp
+++ b/src/roff/troff/input.cpp
@@ -4717,6 +4717,59 @@ void chop_macro()
   skip_line();
 }
 
+enum case_xform_mode { STRING_UPCASE, STRING_DOWNCASE };
+
+// Case-transform each character of the string argument.
+void do_string_case_transform(case_xform_mode mode)
+{
+  if ((mode != STRING_DOWNCASE) && (mode != STRING_UPCASE)) {
+    error("impossible string case transformation mode %1!", mode);
+    return;
+  }
+  symbol s = get_name(1);
+  if (s.is_null()) {
+    skip_line();
+    return;
+  }
+  request_or_macro *p = lookup_request(s);
+  macro *m = p->to_macro();
+  if (!m) {
+    error("cannot apply string case transformation to a request ('%1')",
+          s.contents());
+    skip_line();
+    return;
+  }
+  string_iterator iter1(*m);
+  macro *mac = new macro;
+  for (int l = 0; l < m->macro::length(); l++) {
+    int nc, c = iter1.get(0);
+    if (c == PUSH_GROFF_MODE
+        || c == PUSH_COMP_MODE
+        || c == POP_GROFFCOMP_MODE)
+      nc = c;
+    else if (c == EOF)
+      break;
+    else
+      if (mode == STRING_DOWNCASE)
+	nc = tolower(c);
+      else
+	nc = toupper(c);
+    mac->append(nc);
+  }
+  request_dictionary.define(s, mac);
+  tok.next();
+}
+
+// Uppercase-transform each character of the string argument.
+void stringdown_request() {
+  do_string_case_transform(STRING_DOWNCASE);
+}
+
+// Lowercase-transform each character of the string argument.
+void stringup_request() {
+  do_string_case_transform(STRING_UPCASE);
+}
+
 void substring_request()
 {
   int start;				// 0, 1, ..., n-1  or  -1, -2, ...
@@ -8213,6 +8266,8 @@ void init_input_requests()
   init_request("shift", shift);
   init_request("so", source);
   init_request("spreadwarn", spreadwarn_request);
+  init_request("stringdown", stringdown_request);
+  init_request("stringup", stringup_request);
   init_request("substring", substring_request);
   init_request("sy", system_request);
   init_request("tag", tag);
commit 46a237cd04b454a134832788992147028c035680
Author: G. Branden Robinson <g.branden.robin...@gmail.com>
Date:   Thu Jul 4 00:12:39 2019 +1000

    Regression-test string case transform feature.
    
    * src/roff/groff/tests/string_case_xform_errors.sh: New test.
    * src/roff/groff/tests/string_case_xform_requests.sh: New test.
    * src/roff/groff/groff.am: Run the tests.

diff --git a/src/roff/groff/groff.am b/src/roff/groff/groff.am
index b2b30557..954c52f8 100644
--- a/src/roff/groff/groff.am
+++ b/src/roff/groff/groff.am
@@ -42,7 +42,9 @@ groffopts_DATA = $(GROFF_OPTS_OUTPUT)
 
 groff_TESTS = \
   src/roff/groff/tests/regression-56555.sh \
-  src/roff/groff/tests/on-latin1-device-oq-is-0x27.sh
+  src/roff/groff/tests/on-latin1-device-oq-is-0x27.sh \
+  src/roff/groff/tests/string_case_xform_requests.sh \
+  src/roff/groff/tests/string_case_xform_errors.sh
 TESTS += $(groff_TESTS)
 
 MOSTLYCLEANFILES += groff_opts.tmp $(GROFF_OPTS_OUTPUT)
diff --git a/src/roff/groff/tests/string_case_xform_errors.sh b/src/roff/groff/tests/string_case_xform_errors.sh
new file mode 100755
index 00000000..f65d9dce
--- /dev/null
+++ b/src/roff/groff/tests/string_case_xform_errors.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+#
+# Copyright (C) 2019 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+expected="troff: <standard input>:1: cannot apply string case transformation to a request ('br')"
+
+actual=$("$groff" -Tutf8 2>&1 <<EOF
+.stringdown br
+EOF
+)
+
+diff -u <(echo "$expected") <(echo "$actual")
+exit $?
diff --git a/src/roff/groff/tests/string_case_xform_requests.sh b/src/roff/groff/tests/string_case_xform_requests.sh
new file mode 100755
index 00000000..bf13faf1
--- /dev/null
+++ b/src/roff/groff/tests/string_case_xform_requests.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+#
+# Copyright (C) 2019 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+expected="Résumé résumé RÉSUMÉ"
+
+actual=$("$groff" -Tutf8 <<EOF
+.pl 1v
+.ds resume R\\['e]sum\\['e]\\\"
+\\*[resume]
+.stringdown resume
+\\*[resume]
+.stringup resume
+\\*[resume]
+EOF
+)
+
+diff -u <(echo "$expected") <(echo "$actual")
+exit $?

Attachment: signature.asc
Description: PGP signature

Reply via email to