aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libutil/escape-char.cc22
-rw-r--r--src/libutil/escape-char.hh22
-rw-r--r--src/libutil/meson.build2
-rw-r--r--tests/functional/repl_characterization/test-session.cc6
-rw-r--r--tests/unit/libutil-support/tests/cli-literate-parser.cc4
-rw-r--r--tests/unit/libutil-support/tests/debug-char.hh24
-rw-r--r--tests/unit/libutil-support/tests/terminal-code-eater.cc6
7 files changed, 54 insertions, 32 deletions
diff --git a/src/libutil/escape-char.cc b/src/libutil/escape-char.cc
new file mode 100644
index 000000000..132260286
--- /dev/null
+++ b/src/libutil/escape-char.cc
@@ -0,0 +1,22 @@
+#include <boost/io/ios_state.hpp>
+#include <iomanip>
+#include <iostream>
+
+#include "escape-char.hh"
+
+namespace nix {
+
+std::ostream & operator<<(std::ostream & s, MaybeHexEscapedChar c)
+{
+ boost::io::ios_flags_saver _ifs(s);
+
+ if (isprint(c.c)) {
+ s << static_cast<char>(c.c);
+ } else {
+ s << "\\x" << std::hex << std::setfill('0') << std::setw(2)
+ << (static_cast<unsigned int>(c.c) & 0xff);
+ }
+ return s;
+}
+
+} // namespace nix
diff --git a/src/libutil/escape-char.hh b/src/libutil/escape-char.hh
new file mode 100644
index 000000000..c7bae7ec0
--- /dev/null
+++ b/src/libutil/escape-char.hh
@@ -0,0 +1,22 @@
+#pragma once
+#include <ostream>
+
+namespace nix {
+
+/**
+ * A struct that prints a debug representation of a character, like `\x1f` for
+ * non-printable characters, or the character itself for other characters.
+ *
+ * Note that these are suitable for human readable output, but further care is
+ * necessary to include them in C++ strings to avoid running into adjacent
+ * hex-like characters. (`"puppy\x1bdoggy"` parses as `"puppy" "\x1bd" "oggy"`
+ * and errors because 0x1bd is too big for a `char`.)
+ */
+struct MaybeHexEscapedChar
+{
+ char c;
+};
+
+std::ostream & operator<<(std::ostream & s, MaybeHexEscapedChar c);
+
+} // namespace nix
diff --git a/src/libutil/meson.build b/src/libutil/meson.build
index 8e4b5211d..cdfda3cf5 100644
--- a/src/libutil/meson.build
+++ b/src/libutil/meson.build
@@ -8,6 +8,7 @@ libutil_sources = files(
'config.cc',
'english.cc',
'error.cc',
+ 'escape-char.cc',
'exit.cc',
'experimental-features.cc',
'filesystem.cc',
@@ -49,6 +50,7 @@ libutil_headers = files(
'config.hh',
'english.hh',
'error.hh',
+ 'escape-char.hh',
'exit.hh',
'experimental-features.hh',
'experimental-features-json.hh',
diff --git a/tests/functional/repl_characterization/test-session.cc b/tests/functional/repl_characterization/test-session.cc
index c35030fc7..50e27e58c 100644
--- a/tests/functional/repl_characterization/test-session.cc
+++ b/tests/functional/repl_characterization/test-session.cc
@@ -3,7 +3,7 @@
#include "test-session.hh"
#include "util.hh"
-#include "tests/debug-char.hh"
+#include "escape-char.hh"
namespace nix {
@@ -60,7 +60,7 @@ std::ostream & operator<<(std::ostream & os, ReplOutputParser::State s)
void ReplOutputParser::transition(State new_state, char responsible_char, bool wasPrompt)
{
if constexpr (DEBUG_REPL_PARSER) {
- std::cerr << "transition " << new_state << " for " << DebugChar{responsible_char}
+ std::cerr << "transition " << new_state << " for " << MaybeHexEscapedChar{responsible_char}
<< (wasPrompt ? " [prompt]" : "") << "\n";
}
state = new_state;
@@ -118,7 +118,7 @@ bool TestSession::waitForPrompt()
});
if constexpr (DEBUG_REPL_PARSER) {
- std::cerr << "raw " << DebugChar{buf[i]} << (wasEaten ? " [eaten]" : "") << "\n";
+ std::cerr << "raw " << MaybeHexEscapedChar{buf[i]} << (wasEaten ? " [eaten]" : "") << "\n";
}
}
diff --git a/tests/unit/libutil-support/tests/cli-literate-parser.cc b/tests/unit/libutil-support/tests/cli-literate-parser.cc
index b3830e32c..08ebddebc 100644
--- a/tests/unit/libutil-support/tests/cli-literate-parser.cc
+++ b/tests/unit/libutil-support/tests/cli-literate-parser.cc
@@ -1,6 +1,6 @@
#include "cli-literate-parser.hh"
#include "libexpr/print.hh"
-#include "debug-char.hh"
+#include "escape-char.hh"
#include "types.hh"
#include "util.hh"
#include <ranges>
@@ -77,7 +77,7 @@ CLILiterateParser::CLILiterateParser(std::string prompt, size_t indent)
void CLILiterateParser::feed(char c)
{
if constexpr (DEBUG_PARSER) {
- std::cout << stateDebug(state_) << " " << DebugChar{c} << "\n";
+ std::cout << stateDebug(state_) << " " << MaybeHexEscapedChar{c} << "\n";
}
if (c == '\n') {
diff --git a/tests/unit/libutil-support/tests/debug-char.hh b/tests/unit/libutil-support/tests/debug-char.hh
deleted file mode 100644
index 765d8553f..000000000
--- a/tests/unit/libutil-support/tests/debug-char.hh
+++ /dev/null
@@ -1,24 +0,0 @@
-///@file
-#include <ostream>
-#include <boost/io/ios_state.hpp>
-
-namespace nix {
-
-struct DebugChar
-{
- char c;
-};
-
-inline std::ostream & operator<<(std::ostream & s, DebugChar c)
-{
- boost::io::ios_flags_saver _ifs(s);
-
- if (isprint(c.c)) {
- s << static_cast<char>(c.c);
- } else {
- s << std::hex << "0x" << (static_cast<unsigned int>(c.c) & 0xff);
- }
- return s;
-}
-
-}
diff --git a/tests/unit/libutil-support/tests/terminal-code-eater.cc b/tests/unit/libutil-support/tests/terminal-code-eater.cc
index 51e1d565e..ad05ed1f6 100644
--- a/tests/unit/libutil-support/tests/terminal-code-eater.cc
+++ b/tests/unit/libutil-support/tests/terminal-code-eater.cc
@@ -1,5 +1,5 @@
#include "terminal-code-eater.hh"
-#include "debug-char.hh"
+#include "escape-char.hh"
#include <assert.h>
#include <cstdint>
#include <iostream>
@@ -14,7 +14,7 @@ void TerminalCodeEater::feed(char c, std::function<void(char)> on_char)
auto isIntermediateChar = [](char v) -> bool { return v >= 0x20 && v <= 0x2f; };
auto isFinalChar = [](char v) -> bool { return v >= 0x40 && v <= 0x7e; };
if constexpr (DEBUG_EATER) {
- std::cerr << "eater" << DebugChar{c} << "\n";
+ std::cerr << "eater" << MaybeHexEscapedChar{c} << "\n";
}
switch (state) {
@@ -28,7 +28,7 @@ void TerminalCodeEater::feed(char c, std::function<void(char)> on_char)
return;
}
if constexpr (DEBUG_EATER) {
- std::cerr << "eater uneat" << DebugChar{c} << "\n";
+ std::cerr << "eater uneat" << MaybeHexEscapedChar{c} << "\n";
}
on_char(c);
break;