Hi Branden,

G. Branden Robinson wrote on Sun, Aug 09, 2026 at 03:35:57AM -0500:

> I look forward to seeing the new requests I've added since groff
> 1.22.4 show up.  ;-)
> 
> stringup
> stringdown

i just implemented these two, see the commit appended below.

With groff-1.24, i see one detail of behaviour that feels at least
somewhat ugly to me, arguably even buggy.

The .stringup request is documented to accept exactly one argument.
However, when i append additional bogus arguments, those sneak
through verbatim to the output stream, at the place of the request,
even though the request is not documented to produce output.

For example, with groff-1.24, the input

  .ds mynm content
  .stringup mynm trailing garbage
  \*[mynm]

produces this output for me:

  trailing garbage CONTENT

Groff prints no wraning or error message.

In mandoc, i chose to instead only print

  CONTENT

to standard output and the following to standard error output:

  mandoc: stringup_bad.in:7:16: ERROR: skipping excess arguments: \
          stringup ... trailing garbage

Yours,
  Ingo


Log Message:
-----------
groff-1.23.0 invented new roff(7) requests .stringup and .stringdown that
change the case of the named user-defined string in place.  I haven't seen
them used in any manual page yet and - like for all roff(7) requests - do
not recommend using them, but i chose to implement them anyway because that
was almost as trivial as it would have been to mark them as unsupported.

G. Branden Robinson reminded me of the new feature.

Modified Files:
--------------
    mandoc:
        roff.7
        roff.c
        roff.h
    mandoc/regress/roff/string:
        Makefile

Added Files:
-----------
    mandoc/regress/roff/string:
        stringup.in
        stringup.out_ascii
        stringup.out_utf8
        stringup_bad.in
        stringup_bad.out_ascii
        stringup_bad.out_lint

Revision Data
-------------
Index: roff.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/roff.c,v
diff -Lroff.c -Lroff.c -u -p -r1.405 -r1.406
--- roff.c
+++ roff.c
@@ -1,6 +1,6 @@
 /* $Id$ */
 /*
- * Copyright (c) 2010-2015, 2017-2025 Ingo Schwarze <[email protected]>
+ * Copyright (c) 2010-2015, 2017-2026 Ingo Schwarze <[email protected]>
  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <[email protected]>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -244,6 +244,7 @@ static      void             roff_setstrn(struct roffkv
                                size_t, const char *, size_t, int);
 static int              roff_shift(ROFF_ARGS);
 static int              roff_so(ROFF_ARGS);
+static int              roff_stringup(ROFF_ARGS);
 static int              roff_tr(ROFF_ARGS);
 static int              roff_Dd(ROFF_ARGS);
 static int              roff_TE(ROFF_ARGS);
@@ -309,6 +310,7 @@ const char *__roff_name[MAN_MAX + 1] = {
        "rs",           "rt",           "schar",        "sentchar",
        "shc",          "shift",        "sizes",        "so",
        "spacewidth",   "special",      "spreadwarn",   "ss",
+       "stringdown",   "stringup",
        "sty",          "substring",    "sv",           "sy",
        "T&",           "tc",           "TE",
        "TH",           "tkf",          "tl",
@@ -569,6 +571,8 @@ static      struct roffmac   roffs[TOKEN_NONE]
        { roff_line_ignore, NULL, NULL, 0 },  /* special */
        { roff_line_ignore, NULL, NULL, 0 },  /* spreadwarn */
        { roff_line_ignore, NULL, NULL, 0 },  /* ss */
+       { roff_stringup, NULL, NULL, 0 },  /* stringdown */
+       { roff_stringup, NULL, NULL, 0 },  /* stringup */
        { roff_line_ignore, NULL, NULL, 0 },  /* sty */
        { roff_unsupp, NULL, NULL, 0 },  /* substring */
        { roff_line_ignore, NULL, NULL, 0 },  /* sv */
@@ -3943,6 +3947,33 @@ roff_so(ROFF_ARGS)
 
        *offs = pos;
        return ROFF_SO;
+}
+
+static int
+roff_stringup(ROFF_ARGS)
+{
+       const char      *name;
+       char            *cp;
+       size_t           namesz;
+       int              deftype;
+
+       cp = buf->buf + pos;
+       name = cp;
+       namesz = roff_getname(&cp, ln, pos);
+       if (*cp != '\0')
+               mandoc_msg(MANDOCERR_ARG_EXCESS, ln, (int)(cp - buf->buf),
+                   "%s ... %s", roff_name[tok], cp);
+       deftype = ROFFDEF_USER;
+       if ((cp = (char *)roff_getstrn(r, name, namesz, &deftype)) != NULL) {
+               while (*cp != '\0') {
+                       if (tok == ROFF_stringup)
+                               *cp = toupper(*cp);
+                       else
+                               *cp = tolower(*cp);
+                       cp++;
+               }
+       }
+       return ROFF_IGN;
 }
 
 /* --- user defined strings and macros ------------------------------------ */
Index: roff.h
===================================================================
RCS file: /home/cvs/mandoc/mandoc/roff.h,v
diff -Lroff.h -Lroff.h -u -p -r1.76 -r1.77
--- roff.h
+++ roff.h
@@ -273,6 +273,8 @@ enum        roff_tok {
        ROFF_special,
        ROFF_spreadwarn,
        ROFF_ss,
+       ROFF_stringdown,
+       ROFF_stringup,
        ROFF_sty,
        ROFF_substring,
        ROFF_sv,
Index: roff.7
===================================================================
RCS file: /home/cvs/mandoc/mandoc/roff.7,v
diff -Lroff.7 -Lroff.7 -u -p -r1.123 -r1.124
--- roff.7
+++ roff.7
@@ -1,6 +1,7 @@
 .\" $Id$
 .\"
-.\" Copyright (c) 2010-2019,2022-2023,2025 Ingo Schwarze <[email protected]>
+.\" Copyright (c) 2010-2019, 2022-2023, 2025, 2026
+.\"               Ingo Schwarze <[email protected]>
 .\" Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <[email protected]>
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
@@ -1637,6 +1638,18 @@ Currently ignored.
 .It Ic \&ss Ar wordspace Op Ar sentencespace
 Set space character size.
 Currently ignored.
+.It Ic \&stringdown Ar stringname
+Convert each byte of the user-defined string with the given
+.Ar stringname
+to lower case.
+If the strings contains escape sequences, for example special character
+escapes, each byte of the names and arguments of the sequences is
+converted rather than attempting wide character case conversion.
+This is a groff-1.23 extension.
+.It Ic \&stringup Ar stringname
+Like
+.Ic \&stringdown ,
+but converts to upper case instead of to lower case.
 .It Ic \&sty Ar position style
 Associate style with a font position.
 This is a groff extension and currently ignored.
--- /dev/null
+++ regress/roff/string/stringup_bad.in
@@ -0,0 +1,8 @@
+.\" $OpenBSD: stringup_bad.in,v 1.1 2026/08/16 16:22:47 schwarze Exp $
+.TH STRING-STRINGUP_BAD 1 "August 16, 2026"
+.SH NAME
+string-stringup_bad - invalid syntax in case conversion
+.SH DESCRIPTION
+.ds mynm content
+.stringup mynm trailing garbage
+\*[mynm]
--- /dev/null
+++ regress/roff/string/stringup_bad.out_lint
@@ -0,0 +1 @@
+mandoc: stringup_bad.in:7:16: ERROR: skipping excess arguments: stringup ... 
trailing garbage
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/roff/string/Makefile,v
diff -Lregress/roff/string/Makefile -Lregress/roff/string/Makefile -u -p -r1.5 
-r1.6
--- regress/roff/string/Makefile
+++ regress/roff/string/Makefile
@@ -1,20 +1,26 @@
-# $OpenBSD: Makefile,v 1.10 2022/06/03 11:50:25 schwarze Exp $
+# $OpenBSD: Makefile,v 1.11 2026/08/16 16:22:47 schwarze Exp $
 #
 # This directory is intended for tests of string *expansion*,
 # in particular testing the behaviour of the \* escape sequence.
 # Tests of string *definitions* are better placed in the roff/ds
 # directory.
 
-REGRESS_TARGETS         = dotT escape infinite name std undef zerolength
-LINT_TARGETS    = name std undef
-UTF8_TARGETS    = dotT
+REGRESS_TARGETS         = dotT escape infinite name std stringup stringup_bad
+REGRESS_TARGETS        += undef zerolength
+LINT_TARGETS    = name std stringup_bad undef
+UTF8_TARGETS    = dotT stringup
 HTML_TARGETS    = dotT
-SKIP_MARKDOWN   = escape infinite name std undef zerolength
+SKIP_MARKDOWN   = escape infinite name std stringup stringup_bad
+SKIP_MARKDOWN  += undef zerolength
 
 # The infinite test fails badly with groff-1.20.1:
 # It fails to print the following text.
 
 SKIP_GROFF     ?= infinite
+
+# groff-1.24 lets excess .stringup arguments sneak through to the output stream
+
+SKIP_GROFF     += stringup_bad
 
 # Groff can expand standard macros as strings, but mandoc cannot.
 
--- /dev/null
+++ regress/roff/string/stringup.in
@@ -0,0 +1,16 @@
+.\" $OpenBSD: stringup.in,v 1.1 2026/08/16 16:22:47 schwarze Exp $
+.TH STRING-STRINGUP 1 "August 16, 2026"
+.SH NAME
+string-stringup - convert case of string
+.SH DESCRIPTION
+.ie dmynm already defined (unexpected)
+.el undefined
+.stringup mynm
+.ie dmynm now defined
+.el still undefined (unexpected)
+.ds mynm R\['e]sum\['e]\"
+\*[mynm]
+.stringdown mynm
+\*[mynm]
+.stringup mynm
+\*[mynm]
--- /dev/null
+++ regress/roff/string/stringup.out_utf8
@@ -0,0 +1,9 @@
+STRING-STRINGUP(1)          General Commands Manual         STRING-STRINGUP(1)
+
+NNAAMMEE
+     string-stringup - convert case of string
+
+DDEESSCCRRIIPPTTIIOONN
+     undefined now defined Résumé résumé RÉSUMÉ
+
+OpenBSD                         August 16, 2026             STRING-STRINGUP(1)
--- /dev/null
+++ regress/roff/string/stringup.out_ascii
@@ -0,0 +1,9 @@
+STRING-STRINGUP(1)          General Commands Manual         STRING-STRINGUP(1)
+
+NNAAMMEE
+     string-stringup - convert case of string
+
+DDEESSCCRRIIPPTTIIOONN
+     undefined now defined R'esum'e r'esum'e R'ESUM'E
+
+OpenBSD                         August 16, 2026             STRING-STRINGUP(1)
--- /dev/null
+++ regress/roff/string/stringup_bad.out_ascii
@@ -0,0 +1,9 @@
+STRING-STRINGUP_BAD(1)      General Commands Manual     STRING-STRINGUP_BAD(1)
+
+NNAAMMEE
+     string-stringup_bad - invalid syntax in case conversion
+
+DDEESSCCRRIIPPTTIIOONN
+     CONTENT
+
+OpenBSD                         August 16, 2026         STRING-STRINGUP_BAD(1)

              • ... Larry Kollar
              • ... Deri via discussion of the GNU roff typesetting system and related software
              • ... John Gardner
              • ... Larry Kollar
              • ... G. Branden Robinson
              • ... G. Branden Robinson
              • ... G. Branden Robinson
              • ... Ingo Schwarze
              • ... G. Branden Robinson
              • ... Ingo Schwarze
              • ... Ingo Schwarze
              • ... G. Branden Robinson
              • ... Ingo Schwarze
              • ... Ingo Schwarze
              • ... G. Branden Robinson
              • ... Ingo Schwarze
  • ... Bruno Haible via discussion of the GNU roff typesetting system and related software
    • ... G. Branden Robinson
      • ... Bruno Haible via discussion of the GNU roff typesetting system and related software
        • ... G. Branden Robinson
      • ... G. Branden Robinson

Reply via email to