Skip to content

Commit

Permalink
Improve filtering of ANSI escape sequences in build logs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
edolstra committed Feb 7, 2018
1 parent cfdfad5 commit 84989d3
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 72 deletions.
2 changes: 1 addition & 1 deletion src/libstore/build.cc
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion src/libutil/logging.cc
Expand Up @@ -44,7 +44,7 @@ class SimpleLogger : public Logger
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,
Expand Down
67 changes: 41 additions & 26 deletions src/libutil/util.cc
Expand Up @@ -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;
}

Expand Down
10 changes: 6 additions & 4 deletions src/libutil/util.hh
Expand Up @@ -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. */
Expand Down
43 changes: 4 additions & 39 deletions src/nix/progress-bar.cc
Expand Up @@ -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:
Expand Down Expand Up @@ -343,7 +305,10 @@ class ProgressBar : public Logger
}
}

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)
Expand Down
2 changes: 1 addition & 1 deletion tests/misc.sh
Expand Up @@ -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$"

0 comments on commit 84989d3

Please sign in to comment.