parseShebangs: Make strings with backtick sequences representable

This commit is contained in:
Robert Hensing 2023-11-06 18:19:14 +01:00 committed by tomberek
parent ffd414eb75
commit 589d338776
4 changed files with 129 additions and 3 deletions

View file

@ -189,12 +189,40 @@ void ParseQuoted::operator()(std::shared_ptr<Parser> &state, Strings & r) {
throw Error("unterminated quoted string in nix shebang"); throw Error("unterminated quoted string in nix shebang");
} }
switch (remaining[0]) { switch (remaining[0]) {
case ' ':
if ((remaining.size() == 3 && remaining[1] == '`' && remaining[2] == '`')
|| (remaining.size() > 3 && remaining[1] == '`' && remaining[2] == '`' && remaining[3] != '`')) {
// exactly two backticks mark the end of a quoted string, but a preceding space is ignored if present.
state = std::make_shared<ParseUnquoted>(ParseUnquoted(remaining.substr(3)));
r.push_back(acc);
return;
}
else {
// just a normal space
acc += remaining[0];
remaining = remaining.substr(1);
return;
}
case '`': case '`':
if (remaining.size() > 1 && remaining[1] == '`') { // exactly two backticks mark the end of a quoted string
if ((remaining.size() == 2 && remaining[1] == '`')
|| (remaining.size() > 2 && remaining[1] == '`' && remaining[2] != '`')) {
state = std::make_shared<ParseUnquoted>(ParseUnquoted(remaining.substr(2))); state = std::make_shared<ParseUnquoted>(ParseUnquoted(remaining.substr(2)));
r.push_back(acc); r.push_back(acc);
return; return;
} }
// a sequence of at least 3 backticks is one escape-backtick which is ignored, followed by any number of backticks, which are verbatim
else if (remaining.size() >= 3 && remaining[1] == '`' && remaining[2] == '`') {
// ignore "escape" backtick
remaining = remaining.substr(1);
// add the rest
while (remaining.size() > 0 && remaining[0] == '`') {
acc += '`';
remaining = remaining.substr(1);
}
return;
}
else { else {
acc += remaining[0]; acc += remaining[0];
remaining = remaining.substr(1); remaining = remaining.substr(1);
@ -208,7 +236,7 @@ void ParseQuoted::operator()(std::shared_ptr<Parser> &state, Strings & r) {
assert(false); assert(false);
} }
static Strings parseShebangContent(std::string_view s) { Strings parseShebangContent(std::string_view s) {
Strings result; Strings result;
std::shared_ptr<Parser> parserState(std::make_shared<ParseUnquoted>(ParseUnquoted(s))); std::shared_ptr<Parser> parserState(std::make_shared<ParseUnquoted>(ParseUnquoted(s)));

View file

@ -409,4 +409,6 @@ public:
virtual void add(std::string completion, std::string description = "") = 0; virtual void add(std::string completion, std::string description = "") = 0;
}; };
Strings parseShebangContent(std::string_view s);
} }

94
src/libutil/tests/args.cc Normal file
View file

@ -0,0 +1,94 @@
#include "../args.hh"
#include <list>
#include <gtest/gtest.h>
namespace nix {
TEST(parseShebangContent, basic) {
std::list<std::string> r = parseShebangContent("hi there");
ASSERT_EQ(r.size(), 2);
auto i = r.begin();
ASSERT_EQ(*i++, "hi");
ASSERT_EQ(*i++, "there");
}
TEST(parseShebangContent, empty) {
std::list<std::string> r = parseShebangContent("");
ASSERT_EQ(r.size(), 0);
}
TEST(parseShebangContent, doubleBacktick) {
std::list<std::string> r = parseShebangContent("``\"ain't that nice\"``");
ASSERT_EQ(r.size(), 1);
auto i = r.begin();
ASSERT_EQ(*i++, "\"ain't that nice\"");
}
TEST(parseShebangContent, doubleBacktickEmpty) {
std::list<std::string> r = parseShebangContent("````");
ASSERT_EQ(r.size(), 1);
auto i = r.begin();
ASSERT_EQ(*i++, "");
}
TEST(parseShebangContent, doubleBacktickMarkdownInlineCode) {
std::list<std::string> r = parseShebangContent("``# I'm markdown section about `coolFunction` ``");
ASSERT_EQ(r.size(), 1);
auto i = r.begin();
ASSERT_EQ(*i++, "# I'm markdown section about `coolFunction`");
}
TEST(parseShebangContent, doubleBacktickMarkdownCodeBlockNaive) {
std::list<std::string> r = parseShebangContent("``Example 1\n```nix\na: a\n``` ``");
auto i = r.begin();
ASSERT_EQ(r.size(), 1);
ASSERT_EQ(*i++, "Example 1\n``nix\na: a\n``");
}
TEST(parseShebangContent, doubleBacktickMarkdownCodeBlockCorrect) {
std::list<std::string> r = parseShebangContent("``Example 1\n````nix\na: a\n```` ``");
auto i = r.begin();
ASSERT_EQ(r.size(), 1);
ASSERT_EQ(*i++, "Example 1\n```nix\na: a\n```");
}
TEST(parseShebangContent, doubleBacktickMarkdownCodeBlock2) {
std::list<std::string> r = parseShebangContent("``Example 1\n````nix\na: a\n````\nExample 2\n````nix\na: a\n```` ``");
auto i = r.begin();
ASSERT_EQ(r.size(), 1);
ASSERT_EQ(*i++, "Example 1\n```nix\na: a\n```\nExample 2\n```nix\na: a\n```");
}
TEST(parseShebangContent, singleBacktickInDoubleBacktickQuotes) {
std::list<std::string> r = parseShebangContent("``` ``");
auto i = r.begin();
ASSERT_EQ(r.size(), 1);
ASSERT_EQ(*i++, "`");
}
TEST(parseShebangContent, singleBacktickAndSpaceInDoubleBacktickQuotes) {
std::list<std::string> r = parseShebangContent("``` ``");
auto i = r.begin();
ASSERT_EQ(r.size(), 1);
ASSERT_EQ(*i++, "` ");
}
TEST(parseShebangContent, doubleBacktickInDoubleBacktickQuotes) {
std::list<std::string> r = parseShebangContent("````` ``");
auto i = r.begin();
ASSERT_EQ(r.size(), 1);
ASSERT_EQ(*i++, "``");
}
TEST(parseShebangContent, increasingQuotes) {
std::list<std::string> r = parseShebangContent("```` ``` `` ````` `` `````` ``");
auto i = r.begin();
ASSERT_EQ(r.size(), 4);
ASSERT_EQ(*i++, "");
ASSERT_EQ(*i++, "`");
ASSERT_EQ(*i++, "``");
ASSERT_EQ(*i++, "```");
}
}

View file

@ -243,7 +243,9 @@ in [`nix help-stores`](./nix3-help-stores.md).
The `nix` command can be used as a `#!` interpreter. The `nix` command can be used as a `#!` interpreter.
Arguments to Nix can be passed on subsequent lines in the script. Arguments to Nix can be passed on subsequent lines in the script.
Verbatim strings may be passed in double backtick (```` `` ````) quotes. Verbatim strings may be passed in double backtick (```` `` ````) quotes. <!-- that's markdown for two backticks in inline code. -->
Sequences of _n_ backticks of 3 or longer are parsed as _n-1_ literal backticks.
A single space before the closing ```` `` ```` is ignored if present.
`--file` and `--expr` resolve relative paths based on the script location. `--file` and `--expr` resolve relative paths based on the script location.