forked from lix-project/lix
Allow special characters in flake paths
Support using nix flakes in paths with spaces or abitrary unicode characters. This introduces the convention that the path part of the URL should be percent-encoded when dealing with `path:` urls and not when using filepaths (following the convention of firefox). Co-authored-by: Rendal <rasmus@rend.al>
This commit is contained in:
parent
d8cebae939
commit
50e61f579c
|
@ -77,14 +77,6 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
{
|
{
|
||||||
using namespace fetchers;
|
using namespace fetchers;
|
||||||
|
|
||||||
static std::string fnRegex = "[0-9a-zA-Z-._~!$&'\"()*+,;=]+";
|
|
||||||
|
|
||||||
static std::regex pathUrlRegex(
|
|
||||||
"(/?" + fnRegex + "(?:/" + fnRegex + ")*/?)"
|
|
||||||
+ "(?:\\?(" + queryRegex + "))?"
|
|
||||||
+ "(?:#(" + queryRegex + "))?",
|
|
||||||
std::regex::ECMAScript);
|
|
||||||
|
|
||||||
static std::regex flakeRegex(
|
static std::regex flakeRegex(
|
||||||
"((" + flakeIdRegexS + ")(?:/(?:" + refAndOrRevRegex + "))?)"
|
"((" + flakeIdRegexS + ")(?:/(?:" + refAndOrRevRegex + "))?)"
|
||||||
+ "(?:#(" + queryRegex + "))?",
|
+ "(?:#(" + queryRegex + "))?",
|
||||||
|
@ -92,26 +84,23 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
|
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
|
|
||||||
/* Check if 'url' is a flake ID. This is an abbreviated syntax for
|
auto parsePathFlakeRef = [&]() {
|
||||||
'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
|
std::string path = url;
|
||||||
|
std::string fragment = "";
|
||||||
if (std::regex_match(url, match, flakeRegex)) {
|
std::map<std::string, std::string> query = {};
|
||||||
auto parsedURL = ParsedURL{
|
auto pathEnd = url.find_first_of("#?");
|
||||||
.url = url,
|
auto fragmentStart = pathEnd;
|
||||||
.base = "flake:" + match.str(1),
|
if (pathEnd != std::string::npos && url[pathEnd] == '?')
|
||||||
.scheme = "flake",
|
fragmentStart = url.find("#");
|
||||||
.authority = "",
|
if (pathEnd != std::string::npos) {
|
||||||
.path = match[1],
|
path = url.substr(0, pathEnd);
|
||||||
};
|
}
|
||||||
|
if (fragmentStart != std::string::npos) {
|
||||||
return std::make_pair(
|
fragment = percentDecode(url.substr(fragmentStart+1));
|
||||||
FlakeRef(Input::fromURL(parsedURL, isFlake), ""),
|
}
|
||||||
percentDecode(match.str(6)));
|
if (fragmentStart != std::string::npos && pathEnd != std::string::npos) {
|
||||||
}
|
query = decodeQuery(url.substr(pathEnd+1, fragmentStart));
|
||||||
|
}
|
||||||
else if (std::regex_match(url, match, pathUrlRegex)) {
|
|
||||||
std::string path = match[1];
|
|
||||||
std::string fragment = percentDecode(match.str(3));
|
|
||||||
|
|
||||||
if (baseDir) {
|
if (baseDir) {
|
||||||
/* Check if 'url' is a path (either absolute or relative
|
/* Check if 'url' is a path (either absolute or relative
|
||||||
|
@ -163,7 +152,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
.scheme = "git+file",
|
.scheme = "git+file",
|
||||||
.authority = "",
|
.authority = "",
|
||||||
.path = flakeRoot,
|
.path = flakeRoot,
|
||||||
.query = decodeQuery(match[2]),
|
.query = query,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (subdir != "") {
|
if (subdir != "") {
|
||||||
|
@ -188,7 +177,6 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
} else {
|
} else {
|
||||||
if (!hasPrefix(path, "/"))
|
if (!hasPrefix(path, "/"))
|
||||||
throw BadURL("flake reference '%s' is not an absolute path", url);
|
throw BadURL("flake reference '%s' is not an absolute path", url);
|
||||||
auto query = decodeQuery(match[2]);
|
|
||||||
path = canonPath(path + "/" + getOr(query, "dir", ""));
|
path = canonPath(path + "/" + getOr(query, "dir", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,19 +185,40 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
attrs.insert_or_assign("path", path);
|
attrs.insert_or_assign("path", path);
|
||||||
|
|
||||||
return std::make_pair(FlakeRef(Input::fromAttrs(std::move(attrs)), ""), fragment);
|
return std::make_pair(FlakeRef(Input::fromAttrs(std::move(attrs)), ""), fragment);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Check if 'url' is a flake ID. This is an abbreviated syntax for
|
||||||
|
'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
|
||||||
|
|
||||||
|
if (std::regex_match(url, match, flakeRegex)) {
|
||||||
|
auto parsedURL = ParsedURL{
|
||||||
|
.url = url,
|
||||||
|
.base = "flake:" + match.str(1),
|
||||||
|
.scheme = "flake",
|
||||||
|
.authority = "",
|
||||||
|
.path = match[1],
|
||||||
|
};
|
||||||
|
|
||||||
|
return std::make_pair(
|
||||||
|
FlakeRef(Input::fromURL(parsedURL), ""),
|
||||||
|
percentDecode(match.str(6)));
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
auto parsedURL = parseURL(url);
|
try {
|
||||||
std::string fragment;
|
auto parsedURL = parseURL(url);
|
||||||
std::swap(fragment, parsedURL.fragment);
|
std::string fragment;
|
||||||
|
std::swap(fragment, parsedURL.fragment);
|
||||||
|
|
||||||
auto input = Input::fromURL(parsedURL, isFlake);
|
auto input = Input::fromURL(parsedURL, isFlake);
|
||||||
input.parent = baseDir;
|
input.parent = baseDir;
|
||||||
|
|
||||||
return std::make_pair(
|
return std::make_pair(
|
||||||
FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")),
|
FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")),
|
||||||
fragment);
|
fragment);
|
||||||
|
} catch (BadURL &) {
|
||||||
|
return parsePathFlakeRef();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -335,4 +335,13 @@ namespace nix {
|
||||||
ASSERT_EQ(d, s);
|
ASSERT_EQ(d, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(percentEncode, yen) {
|
||||||
|
// https://en.wikipedia.org/wiki/Percent-encoding#Character_data
|
||||||
|
std::string s = reinterpret_cast<const char*>(u8"円");
|
||||||
|
std::string e = "%E5%86%86";
|
||||||
|
|
||||||
|
ASSERT_EQ(percentEncode(s), e);
|
||||||
|
ASSERT_EQ(percentDecode(e), s);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ std::string percentEncode(std::string_view s, std::string_view keep)
|
||||||
|| keep.find(c) != std::string::npos)
|
|| keep.find(c) != std::string::npos)
|
||||||
res += c;
|
res += c;
|
||||||
else
|
else
|
||||||
res += fmt("%%%02X", (unsigned int) c);
|
res += fmt("%%%02X", c & 0xFF);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue