Browsing through list archives from 2020, I found a mention of the unexpand 
command (in POSIX)

>From 
>http://lists.landley.net/pipermail/toybox-landley.net/2020-May/019792.html:

unexpand "converts spaces to tabs". Haven't gotten around to it yet. :)

This commands behavior is so simple (s/  /\t/g) that it can be knocked out in a 
couple hours,
The below patch is the command in 60 lines of code, and some tests for it.

Since the command only looks for 2 characters (' ' and '\t'), no UTF safety 
checking is required,
unexpand doesn't parse backspaces either.

The only problem is...

The GNU man page doesn't say if spaces are supposed to be processed beyond the 
beginning of lines.
Since it specifies -a (Spaces -> tabs after the start of lines), and puts the 
option to disable that
behavior under "--first-only" (while noting that it OVERRIDES -a). You would 
THINK it'd not process
spaces like that, and the "--first-only" option serves the same purpose as grep 
-G (None at all, but might
make option parsing a bit simpler for scripts).

POSIX also doesn't make this apparent while also specifying -a, but in a much 
more verbose way because POSIX

--first-only behavior is nice (the only reason I can find that would make this 
command more useful than sed 's/  /\t/g'), but long options are cumbersome.

FAIL: unexpand -a behavior default
echo -ne '  123  123\n' | "/sbin/unexpand" -t 2
[...]
-       123      123
+       123       123

Now it's converting spaces to tabs, while leaving trailing spaces?

The man page does not document that:
       -a, --all
              convert all blanks, instead of just initial blanks

       --first-only
              convert only leading sequences of blanks (overrides -a)

       -t, --tabs=N
              have tabs N characters apart instead of 8 (enables -a)

Also... why does -t enable -a, that makes doing testing on a terminal where 
tabs are displayed
as 8 spaces a lot harder (have to switch on -t to make sure anything is being 
CONVERTED,
but if you do that it will switch on another, completely unrelated option)...

-   Oliver Webb <aquahobby...@proton.me>
From 3aaf0736639a7d12b5b5371a8b0d7b65dba01744 Mon Sep 17 00:00:00 2001
From: Oliver Webb <aquahobby...@proton.me>
Date: Fri, 23 Feb 2024 20:18:23 -0600
Subject: [PATCH] A 'unexpand' implementation

---
 tests/unexpand.test     | 11 ++++++++
 toys/pending/unexpand.c | 59 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 70 insertions(+)
 create mode 100644 tests/unexpand.test
 create mode 100644 toys/pending/unexpand.c

diff --git a/tests/unexpand.test b/tests/unexpand.test
new file mode 100644
index 00000000..b93e9621
--- /dev/null
+++ b/tests/unexpand.test
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . ./testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+testcmd "" "" "\t123\n" "" "        123\n"
+testcmd "-t" "-t 4" "\t\t123\n" "" "        123\n"
+testcmd "tabs at start" "-t 2" "\t\t\t123\n" "" "  \t  123\n"
+testcmd "-a behaviour not default" "" "\t123  123\n" "" "        123  123\n"
+testcmd "-a" "-a -t 2" "\t123\t123\n" "" "  123  123\n"
diff --git a/toys/pending/unexpand.c b/toys/pending/unexpand.c
new file mode 100644
index 00000000..e32a6bf2
--- /dev/null
+++ b/toys/pending/unexpand.c
@@ -0,0 +1,59 @@
+/* unexpand.c - Convert Spaces to Tabs
+ *
+ * Copyright 2024 Oliver Webb <aquahobby...@proton.me>
+ *
+ * See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/unexpand.html
+ * TODO: CSV tabstop list for -t
+
+USE_UNEXPAND(NEWTOY(unexpand, "at#", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LINEBUF))
+
+config UNEXPAND
+  bool "unexpand"
+  default n
+  help
+    usage: unexpand [-a] [-t NUM] [FILE...]
+
+    Convert spaces to tabs. By default only at the start of a line
+    Defaults to 8 spaces per 1 tab
+
+    -a      Convert all spaces in a line, not just initial ones
+    -t NUM  Convert NUM spaces to 1 tab
+*/
+
+#define FOR_unexpand
+#include "toys.h"
+
+GLOBALS(
+  long t;
+)
+
+static void do_unexpand(int fd, char *name)
+{
+  char *line;
+  FILE *fin = xfdopen(fd, "r");
+  size_t i;
+  int next = 0, factor = (FLAG(t)) ? TT.t : 8;
+
+  while ((line = xgetline(fin))) {
+    for (; *line && !next; line++) {
+      if (*line == ' ') {
+        for (i = 0; line[i] == ' '; i++);
+
+        if (i >= factor) putchar('\t'), line += factor-1;
+        else putchar(*line);
+      }
+      else if (*line == '\t') putchar('\t');
+      else {
+        if (!FLAG(a)) xprintf("%s", line), next = 1;
+        else putchar(*line);
+      }
+    }
+    putchar('\n');
+    next = 0;
+  }
+}
+
+void unexpand_main(void)
+{
+  loopfiles(toys.optargs, do_unexpand);
+}
-- 
2.44.0

_______________________________________________
Toybox mailing list
Toybox@lists.landley.net
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to