Move escapeString to its own file and add tests

Change-Id: Ie5c954ec73c46c9d3c679ef99a83a29cc7a08352
This commit is contained in:
Rebecca Turner 2024-03-22 16:44:39 -07:00
parent 9a781c32fe
commit 5f6e0b3a8e
8 changed files with 202 additions and 58 deletions

View file

@ -4,6 +4,7 @@
#include "symbol-table.hh"
#include "util.hh"
#include "print.hh"
#include "escape-string.hh"
#include <cstdlib>
@ -36,7 +37,7 @@ void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
{
printLiteralString(str, s);
escapeString(str, s);
}
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const

View file

@ -2,6 +2,7 @@
#include "print.hh"
#include "eval.hh"
#include "signals.hh"
#include "escape-string.hh"
namespace nix {
@ -27,7 +28,7 @@ void printAmbiguous(
printLiteralBool(str, v.boolean);
break;
case nString:
printLiteralString(str, v.string.s);
escapeString(str, v.string.s);
break;
case nPath:
str << v.path().to_string(); // !!! escaping?

View file

@ -1,6 +1,7 @@
#include <limits>
#include <unordered_set>
#include "escape-string.hh"
#include "print.hh"
#include "ansicolor.hh"
#include "store-api.hh"
@ -27,40 +28,6 @@ void printElided(
}
std::ostream &
printLiteralString(std::ostream & str, const std::string_view string, size_t maxLength, bool ansiColors)
{
size_t charsPrinted = 0;
if (ansiColors)
str << ANSI_MAGENTA;
str << "\"";
for (auto i = string.begin(); i != string.end(); ++i) {
if (charsPrinted >= maxLength) {
str << "\" ";
printElided(str, string.length() - charsPrinted, "byte", "bytes", ansiColors);
return str;
}
if (*i == '\"' || *i == '\\') str << "\\" << *i;
else if (*i == '\n') str << "\\n";
else if (*i == '\r') str << "\\r";
else if (*i == '\t') str << "\\t";
else if (*i == '$' && *(i+1) == '{') str << "\\" << *i;
else str << *i;
charsPrinted++;
}
str << "\"";
if (ansiColors)
str << ANSI_NORMAL;
return str;
}
std::ostream &
printLiteralString(std::ostream & str, const std::string_view string)
{
return printLiteralString(str, string, std::numeric_limits<size_t>::max(), false);
}
std::ostream &
printLiteralBool(std::ostream & str, bool boolean)
{
@ -92,7 +59,7 @@ printIdentifier(std::ostream & str, std::string_view s) {
else {
char c = s[0];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) {
printLiteralString(str, s);
escapeString(str, s);
return str;
}
for (auto c : s)
@ -100,7 +67,7 @@ printIdentifier(std::ostream & str, std::string_view s) {
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' || c == '\'' || c == '-')) {
printLiteralString(str, s);
escapeString(str, s);
return str;
}
str << s;
@ -128,7 +95,7 @@ printAttributeName(std::ostream & str, std::string_view name) {
if (isVarName(name))
str << name;
else
printLiteralString(str, name);
escapeString(str, name);
return str;
}
@ -247,7 +214,15 @@ private:
void printString(Value & v)
{
printLiteralString(output, v.string.s, options.maxStringLength, options.ansiColors);
escapeString(
output,
v.string.s,
{
.maxLength = options.maxStringLength,
.ansiColors = options.ansiColors,
// NB: Non-printing characters won't be escaped.
}
);
}
void printPath(Value & v)

View file

@ -17,22 +17,6 @@ namespace nix {
class EvalState;
struct Value;
/**
* Print a string as a Nix string literal.
*
* Quotes and fairly minimal escaping are added.
*
* @param o The output stream to print to
* @param s The logical string
*/
std::ostream & printLiteralString(std::ostream & o, std::string_view s);
inline std::ostream & printLiteralString(std::ostream & o, const char * s) {
return printLiteralString(o, std::string_view(s));
}
inline std::ostream & printLiteralString(std::ostream & o, const std::string & s) {
return printLiteralString(o, std::string_view(s));
}
/** Print `true` or `false`. */
std::ostream & printLiteralBool(std::ostream & o, bool b);

View file

@ -0,0 +1,80 @@
#include <boost/io/ios_state.hpp>
#include <iomanip>
#include <ostream>
#include <sstream>
#include "ansicolor.hh"
#include "debug-char.hh"
#include "english.hh"
#include "escape-string.hh"
namespace nix {
void printElided(
std::ostream & output,
unsigned int value,
const std::string_view single,
const std::string_view plural,
bool ansiColors
)
{
if (ansiColors) {
output << ANSI_FAINT;
}
output << "«";
pluralize(output, value, single, plural);
output << " elided»";
if (ansiColors) {
output << ANSI_NORMAL;
}
}
std::ostream &
escapeString(std::ostream & output, std::string_view string, EscapeStringOptions options)
{
size_t charsPrinted = 0;
if (options.ansiColors) {
output << ANSI_MAGENTA;
}
output << "\"";
for (auto i = string.begin(); i != string.end(); ++i) {
if (charsPrinted >= options.maxLength) {
output << "\" ";
printElided(
output, string.length() - charsPrinted, "byte", "bytes", options.ansiColors
);
return output;
}
if (*i == '\"' || *i == '\\') {
output << "\\" << *i;
} else if (*i == '\n') {
output << "\\n";
} else if (*i == '\r') {
output << "\\r";
} else if (*i == '\t') {
output << "\\t";
} else if (*i == '$' && *(i + 1) == '{') {
output << "\\" << *i;
} else if (options.escapeNonPrinting && !isprint(*i)) {
output << DebugChar{*i};
} else {
output << *i;
}
charsPrinted++;
}
output << "\"";
if (options.ansiColors) {
output << ANSI_NORMAL;
}
return output;
}
std::string escapeString(std::string_view s, EscapeStringOptions options)
{
std::ostringstream output;
escapeString(output, s, options);
return output.str();
}
}; // namespace nix

View file

@ -0,0 +1,68 @@
#pragma once
#include <limits>
#include <ostream>
namespace nix {
/**
* Options for escaping strings in `escapeString`.
*
* With default optional parameters, the output string will round-trip through
* the Nix evaluator (i.e. you can copy/paste this function's output into the
* REPL and have it evaluate as the string that got passed in).
*
* With non-default optional parameters, the output string will be
* human-readable.
*/
struct EscapeStringOptions {
/**
* If `maxLength` is decreased, some trailing portion of the string may be
* omitted with a message like `«123 bytes elided»`.
*/
size_t maxLength = std::numeric_limits<size_t>::max();
/**
* If `ansiColors` is set, the output will contain ANSI terminal escape
* sequences.
*/
bool ansiColors = false;
/**
* If `escapeNonPrinting` is set, non-printing ASCII characters (i.e. with
* byte values less than 0x20) will be printed in `\xhh` format, like
* `\x1d` (other than those that Nix supports, like `\n`, `\r`, `\t`).
* Note that this format is not yet supported by the Lix parser/evaluator!
*
* See: https://git.lix.systems/lix-project/lix/issues/149
*/
bool escapeNonPrinting = false;
};
/**
* Escape a string for output.
*
* With default optional parameters, the output string will round-trip through
* the Nix evaluator (i.e. you can copy/paste this function's output into the
* REPL and have it evaluate as the string that got passed in).
*
* With non-default optional parameters, the output string will be
* human-readable.
*
* See `EscapeStringOptions` for more details on customizing the output.
*/
std::ostream &escapeString(std::ostream &output, std::string_view s,
EscapeStringOptions options = {});
inline std::ostream &escapeString(std::ostream &output, const char *s) {
return escapeString(output, std::string_view(s));
}
inline std::ostream &escapeString(std::ostream &output, const std::string &s) {
return escapeString(output, std::string_view(s));
}
/**
* Escape a string for output, writing the escaped result to a new string.
*/
std::string escapeString(std::string_view s, EscapeStringOptions options = {});
} // namespace nix

View file

@ -16,10 +16,10 @@ using namespace std::string_literals;
namespace nix {
static constexpr const char * REPL_PROMPT = "nix-repl> ";
static constexpr const std::string_view REPL_PROMPT = "nix-repl> ";
// ASCII ENQ character
static constexpr const char * AUTOMATION_PROMPT = "\x05";
static constexpr const std::string_view AUTOMATION_PROMPT = "\x05";
static std::string_view trimOutLog(std::string_view outLog)
{

View file

@ -0,0 +1,35 @@
#include "escape-string.hh"
#include "ansicolor.hh"
#include <gtest/gtest.h>
namespace nix {
TEST(EscapeString, simple) {
auto escaped = escapeString("puppy");
ASSERT_EQ(escaped, "\"puppy\"");
}
TEST(EscapeString, escaping) {
auto escaped = escapeString("\n\r\t \" \\ ${ooga booga}");
ASSERT_EQ(escaped, "\"\\n\\r\\t \\\" \\\\ \\${ooga booga}\"");
}
TEST(EscapeString, maxLength) {
auto escaped = escapeString("puppy", {.maxLength = 5});
ASSERT_EQ(escaped, "\"puppy\"");
escaped = escapeString("puppy doggy", {.maxLength = 5});
ASSERT_EQ(escaped, "\"puppy\" «6 bytes elided»");
}
TEST(EscapeString, ansiColors) {
auto escaped = escapeString("puppy doggy", {.maxLength = 5, .ansiColors = true});
ASSERT_EQ(escaped, ANSI_MAGENTA "\"puppy\" " ANSI_FAINT "«6 bytes elided»" ANSI_NORMAL);
}
TEST(EscapeString, escapeNonPrinting) {
auto escaped = escapeString("puppy\u0005doggy", {.escapeNonPrinting = true});
ASSERT_EQ(escaped, "\"puppy\\x05doggy\"");
}
} // namespace nix