diff mbox series

[02/11] Support GIT_TEST_GETTEXT_POISON=rot13

Message ID ab7832fc7ac783bc8f8c10014fc9747128fc37bc.1610441263.git.gitgitgadget@gmail.com (mailing list archive)
State New
Headers show
Series Introduce support for GETTEXT_POISON=rot13 | expand

Commit Message

Johannes Schindelin Jan. 12, 2021, 8:47 a.m. UTC
From: Johannes Schindelin <johannes.schindelin@gmx.de>

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 gettext.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 gettext.h |  5 +++--
 2 files changed, 66 insertions(+), 4 deletions(-)

Comments

Junio C Hamano Jan. 12, 2021, 7:37 p.m. UTC | #1
"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> +const char *gettext_maybe_rot13(const char *msgid)
> +{
> +...
> +	while (*msgid) {
> +		const char *p = strchrnul(msgid, '%'), *spec;
> +
> +		while (*p && p[1] == '%')
> +			p = strchrnul(p + 2, '%');

We are at '%', and while the next is '%' (i.e. a literal '%' is
asked), we skip these two bytes and then we go back to the
equivalent of the initialization of 'p'.  Effects?  We leave the
loop when we are at the end of the string, or at '%' that is not
followed by a '%'.

It would have been easier to understand what is going on in the loop
if there weren't duplicated strchrnul() call, perhaps

	const char *p = msgid;
	while (*(p = strchrnul(p, '%')) == '%' && p[1] == '%')
        	p += 2;

That reads "find '%' and if the next char is also '%', skip these
two and redo the loop", which would be equivalent.  And it would be
much easier to follow the logic.

> +		if (p != msgid) {

And if 'p' is different (actually we know p is the same as, or
always ahead of, msgid, so "if (msgid < p)" would be easier to
follow for readers) ...

> +			strbuf_addstr(buf, "<rot13>");
> +			while (p != msgid)

Ditto.

> +				strbuf_addch(buf, do_rot13(*(msgid++)));
> +			strbuf_addstr(buf, "</rot13>");

... we add a section of obfuscated output enclosed with an xml
looking marker.

> +		}
> +
> +		if (!*p)
> +			break;

And if we are at the end, we are done.

> +		spec = strpbrk(p + 1, "diouxXeEfFgGaAcsCSpnm%");
> +		if (!spec)
> +			BUG("Unrecognized format string: %s", p);

It is a bit surprising that things like "%.*d" and "%.7f" do not
appear in our translatable strings (or perhaps this is one of the
places why the series is marked RFH and is not complete?).

> +		strbuf_add(buf, p, spec + 1 - p);
> +		msgid = spec + 1;
> +	}
> +
> +	return buf->buf;
> +}

Anyway, thanks for a fun reading.
diff mbox series

Patch

diff --git a/gettext.c b/gettext.c
index 35d2c1218db..7e8966d8326 100644
--- a/gettext.c
+++ b/gettext.c
@@ -68,11 +68,72 @@  const char *get_preferred_languages(void)
 int use_gettext_poison(void)
 {
 	static int poison_requested = -1;
-	if (poison_requested == -1)
-		poison_requested = git_env_bool("GIT_TEST_GETTEXT_POISON", 0);
+	if (poison_requested == -1) {
+		const char *v = getenv("GIT_TEST_GETTEXT_POISON");
+		if (v && !strcmp(v, "rot13"))
+			poison_requested = 2;
+		else
+			poison_requested =
+				git_env_bool("GIT_TEST_GETTEXT_POISON", 0);
+	}
 	return poison_requested;
 }
 
+static inline char do_rot13(char c)
+{
+	if (c >= 'a' && c <= 'm')
+		return c + 'n' - 'a';
+	if (c >= 'n' && c <= 'z')
+		return c + 'a' - 'n';
+	if (c >= 'A' && c <= 'M')
+		return c + 'N' - 'A';
+	if (c >= 'N' && c <= 'Z')
+		return c + 'A' - 'N';
+	return c;
+}
+
+const char *gettext_maybe_rot13(const char *msgid)
+{
+	static struct strbuf round_robin[4] = {
+		STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+	};
+	static int current;
+	struct strbuf *buf;
+
+	if (use_gettext_poison() != 2)
+		return "# GETTEXT POISON #";
+
+	buf = &round_robin[current++];
+	if (current >= ARRAY_SIZE(round_robin))
+		current = 0;
+
+	strbuf_reset(buf);
+	while (*msgid) {
+		const char *p = strchrnul(msgid, '%'), *spec;
+
+		while (*p && p[1] == '%')
+			p = strchrnul(p + 2, '%');
+
+		if (p != msgid) {
+			strbuf_addstr(buf, "<rot13>");
+			while (p != msgid)
+				strbuf_addch(buf, do_rot13(*(msgid++)));
+			strbuf_addstr(buf, "</rot13>");
+		}
+
+		if (!*p)
+			break;
+
+		spec = strpbrk(p + 1, "diouxXeEfFgGaAcsCSpnm%");
+		if (!spec)
+			BUG("Unrecognized format string: %s", p);
+		strbuf_add(buf, p, spec + 1 - p);
+		msgid = spec + 1;
+	}
+
+	return buf->buf;
+}
+
 #ifndef NO_GETTEXT
 static int test_vsnprintf(const char *fmt, ...)
 {
diff --git a/gettext.h b/gettext.h
index bee52eb1134..298adda7e15 100644
--- a/gettext.h
+++ b/gettext.h
@@ -29,6 +29,7 @@ 
 #define FORMAT_PRESERVING(n) __attribute__((format_arg(n)))
 
 int use_gettext_poison(void);
+const char *gettext_maybe_rot13(const char *msgid);
 
 #ifndef NO_GETTEXT
 void git_setup_gettext(void);
@@ -48,14 +49,14 @@  static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
 {
 	if (!*msgid)
 		return "";
-	return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid);
+	return use_gettext_poison() ? gettext_maybe_rot13(msgid) : gettext(msgid);
 }
 
 static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)
 const char *Q_(const char *msgid, const char *plu, unsigned long n)
 {
 	if (use_gettext_poison())
-		return "# GETTEXT POISON #";
+		return gettext_maybe_rot13(n < 2 ? msgid : plu);
 	return ngettext(msgid, plu, n);
 }