about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2018-02-07T14·19+0100
committerEelco Dolstra <edolstra@gmail.com>2018-02-07T14·23+0100
commit84989d3af23c717744b8ddeacd6828bc87e7eda1 (patch)
treeedbd50b08c19559a56c2b0350ed6f44c44c0e731
parentcfdfad5c3451731879a5a693059c094f107c2bc8 (diff)
Improve filtering of ANSI escape sequences in build logs
All ANSI sequences except color setting are now filtered out. In
particular, terminal resets (such as from NixOS VM tests) are filtered
out.

Also, fix the completely broken tab character handling.
-rw-r--r--src/libstore/build.cc2
-rw-r--r--src/libutil/logging.cc2
-rw-r--r--src/libutil/util.cc67
-rw-r--r--src/libutil/util.hh10
-rw-r--r--src/nix/progress-bar.cc43
-rw-r--r--tests/misc.sh2
6 files changed, 54 insertions, 72 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 5be7ce60da..9f669f7e46 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -3428,7 +3428,7 @@ void DerivationGoal::flushLine()
     else {
         if (settings.verboseBuild &&
             (settings.printRepeatedBuilds || curRound == 1))
-            printError(filterANSIEscapes(currentLogLine, true));
+            printError(currentLogLine);
         else {
             logTail.push_back(currentLogLine);
             if (logTail.size() > settings.logLines) logTail.pop_front();
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index 6924e00804..27a631a37d 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -44,7 +44,7 @@ public:
             prefix = std::string("<") + c + ">";
         }
 
-        writeToStderr(prefix + (tty ? fs.s : filterANSIEscapes(fs.s)) + "\n");
+        writeToStderr(prefix + filterANSIEscapes(fs.s) + "\n");
     }
 
     void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 2729973977..f7a12d21b2 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -1178,36 +1178,51 @@ void ignoreException()
 }
 
 
-string filterANSIEscapes(const string & s, bool nixOnly)
-{
-    string t, r;
-    enum { stTop, stEscape, stCSI } state = stTop;
-    for (auto c : s) {
-        if (state == stTop) {
-            if (c == '\e') {
-                state = stEscape;
-                r = c;
-            } else
-                t += c;
-        } else if (state == stEscape) {
-            r += c;
-            if (c == '[')
-                state = stCSI;
-            else {
-                t += r;
-                state = stTop;
+std::string filterANSIEscapes(const std::string & s, unsigned int width)
+{
+    std::string t, e;
+    size_t w = 0;
+    auto i = s.begin();
+
+    while (w < (size_t) width && i != s.end()) {
+
+        if (*i == '\e') {
+            std::string e;
+            e += *i++;
+            char last = 0;
+
+            if (i != s.end() && *i == '[') {
+                e += *i++;
+                // eat parameter bytes
+                while (i != s.end() && *i >= 0x30 && *i <= 0x3f) e += *i++;
+                // eat intermediate bytes
+                while (i != s.end() && *i >= 0x20 && *i <= 0x2f) e += *i++;
+                // eat final byte
+                if (i != s.end() && *i >= 0x40 && *i <= 0x7e) e += last = *i++;
+            } else {
+                if (i != s.end() && *i >= 0x40 && *i <= 0x5f) e += *i++;
             }
-        } else {
-            r += c;
-            if (c >= 0x40 && c <= 0x7e) {
-                if (nixOnly && (c != 'p' && c != 'q' && c != 's' && c != 'a' && c != 'b'))
-                    t += r;
-                state = stTop;
-                r.clear();
+
+            if (last == 'm')
+                t += e;
+        }
+
+        else if (*i == '\t') {
+            i++; t += ' '; w++;
+            while (w < (size_t) width && w % 8) {
+                t += ' '; w++;
             }
         }
+
+        else if (*i == '\r')
+            // do nothing for now
+            ;
+
+        else {
+            t += *i++; w++;
+        }
     }
-    t += r;
+
     return t;
 }
 
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 75eb975152..47e02bc898 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -388,10 +388,12 @@ void ignoreException();
 #define ANSI_BLUE "\e[34;1m"
 
 
-/* Filter out ANSI escape codes from the given string. If ‘nixOnly’ is
-   set, only filter escape codes generated by Nixpkgs' stdenv (used to
-   denote nesting etc.). */
-string filterANSIEscapes(const string & s, bool nixOnly = false);
+/* Truncate a string to 'width' printable characters. Certain ANSI
+   escape sequences (such as colour setting) are copied but not
+   included in the character count. Other ANSI escape sequences are
+   filtered. Also, tabs are expanded to spaces. */
+std::string filterANSIEscapes(const std::string & s,
+    unsigned int width = std::numeric_limits<unsigned int>::max());
 
 
 /* Base64 encoding/decoding. */
diff --git a/src/nix/progress-bar.cc b/src/nix/progress-bar.cc
index 252d12c5d3..e6553c06f4 100644
--- a/src/nix/progress-bar.cc
+++ b/src/nix/progress-bar.cc
@@ -23,44 +23,6 @@ static uint64_t getI(const std::vector<Logger::Field> & fields, size_t n)
     return fields[n].i;
 }
 
-/* Truncate a string to 'width' printable characters. ANSI escape
-   sequences are copied but not included in the character count. Also,
-   tabs are expanded to spaces. */
-static std::string ansiTruncate(const std::string & s, int width)
-{
-    if (width <= 0) return s;
-
-    std::string t;
-    size_t w = 0;
-    auto i = s.begin();
-
-    while (w < (size_t) width && i != s.end()) {
-        if (*i == '\e') {
-            t += *i++;
-            if (i != s.end() && *i == '[') {
-                t += *i++;
-                while (i != s.end() && (*i < 0x40 || *i > 0x7e)) {
-                    t += *i++;
-                }
-                if (i != s.end()) t += *i++;
-            }
-        }
-
-        else if (*i == '\t') {
-            t += ' '; w++;
-            while (w < (size_t) width && w & 8) {
-                t += ' '; w++;
-            }
-        }
-
-        else {
-            t += *i++; w++;
-        }
-    }
-
-    return t;
-}
-
 class ProgressBar : public Logger
 {
 private:
@@ -343,7 +305,10 @@ public:
             }
         }
 
-        writeToStderr("\r" + ansiTruncate(line, getWindowSize().second) + "\e[K");
+        auto width = getWindowSize().second;
+        if (width <= 0) std::numeric_limits<decltype(width)>::max();
+
+        writeToStderr("\r" + filterANSIEscapes(line, width) + "\e[K");
     }
 
     std::string getStatus(State & state)
diff --git a/tests/misc.sh b/tests/misc.sh
index 6d0ab3adce..eda0164167 100644
--- a/tests/misc.sh
+++ b/tests/misc.sh
@@ -16,4 +16,4 @@ nix-env --foo 2>&1 | grep "no operation"
 nix-env -q --foo 2>&1 | grep "unknown flag"
 
 # Eval Errors.
-nix-instantiate --eval -E 'let a = {} // a; in a.foo' 2>&1 | grep "infinite recursion encountered, at (string):1:15$"
+nix-instantiate --eval -E 'let a = {} // a; in a.foo' 2>&1 | grep "infinite recursion encountered, at .*(string).*:1:15$"