Skip to content

Commit

Permalink
allow ^ in URLs
Browse files Browse the repository at this point in the history
Users may select specific outputs using the ^output syntax or selecting
any output using ^*.

URL parsing currently doesn't support these kinds of output references:
parsing will fail.

Currently `queryRegex` was reused for URL fragments, which didn't
include support for ^. Now queryRegex has been split from fragmentRegex,
where only the fragmentRegex supports ^.
  • Loading branch information
bobvanderlinden authored and iFreilicht committed Nov 1, 2023
1 parent 34c4e74 commit cfb52e6
Show file tree
Hide file tree
Showing 6 changed files with 10 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/libexpr/flake/flakeref.cc
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ std::optional<std::pair<FlakeRef, std::string>> parseFlakeIdRef(

static std::regex flakeRegex(
"((" + flakeIdRegexS + ")(?:/(?:" + refAndOrRevRegex + "))?)"
+ "(?:#(" + queryRegex + "))?",
+ "(?:#(" + fragmentRegex + "))?",
std::regex::ECMAScript);

if (std::regex_match(url, match, flakeRegex)) {
Expand Down
3 changes: 3 additions & 0 deletions src/libutil/tests/url-name.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace nix {
ASSERT_EQ(getNameFromURL(parseURL("path:~/repos/nixpkgs#packages.x86_64-linux.hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("path:.#nonStandardAttr.mylaptop")), "nonStandardAttr.mylaptop");
ASSERT_EQ(getNameFromURL(parseURL("path:./repos/myflake#nonStandardAttr.mylaptop")), "nonStandardAttr.mylaptop");
ASSERT_EQ(getNameFromURL(parseURL("path:./nixpkgs#packages.x86_64-linux.complex^bin,man")), "complex");
ASSERT_EQ(getNameFromURL(parseURL("path:./myproj#packages.x86_64-linux.default^*")), "myproj");

ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nixpkgs#packages.x86_64-linux.hello")), "hello");
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nixpkgs#hello")), "hello");
Expand Down Expand Up @@ -60,5 +62,6 @@ namespace nix {
ASSERT_EQ(getNameFromURL(parseURL("path:.")), std::nullopt);
ASSERT_EQ(getNameFromURL(parseURL("file:.#")), std::nullopt);
ASSERT_EQ(getNameFromURL(parseURL("path:.#packages.x86_64-linux.default")), std::nullopt);
ASSERT_EQ(getNameFromURL(parseURL("path:.#packages.x86_64-linux.default^*")), std::nullopt);
}
}
5 changes: 3 additions & 2 deletions src/libutil/url-name.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
namespace nix {

static const std::string attributeNamePattern("[a-z0-9_-]+");
static const std::regex lastAttributeRegex("(?:" + attributeNamePattern + "\\.)*(?!default)(" + attributeNamePattern +")");
static const std::regex lastAttributeRegex("(?:" + attributeNamePattern + "\\.)*(?!default)(" + attributeNamePattern +")(\\^.*)?");
static const std::string pathSegmentPattern("[a-zA-Z0-9_-]+");
static const std::regex lastPathSegmentRegex(".*/(" + pathSegmentPattern +")");
static const std::regex secondPathSegmentRegex("(?:" + pathSegmentPattern + ")/(" + pathSegmentPattern +")(?:/.*)?");
static const std::regex gitProviderRegex("github|gitlab|sourcehut");
static const std::regex gitSchemeRegex("git($|\\+.*)");
static const std::regex defaultOutputRegex(".*\\.default($|\\^.*)");

std::optional<std::string> getNameFromURL(ParsedURL url) {
std::smatch match;
Expand All @@ -32,7 +33,7 @@ std::optional<std::string> getNameFromURL(ParsedURL url) {
return match.str(1);

/* If everything failed but there is a non-default fragment, use it in full */
if (!url.fragment.empty() && !hasSuffix(url.fragment, "default"))
if (!url.fragment.empty() && !std::regex_match(url.fragment, defaultOutputRegex))
return url.fragment;

/* If there is no fragment, take the last element of the path */
Expand Down
1 change: 1 addition & 0 deletions src/libutil/url-parts.hh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const static std::string userRegex = "(?:(?:" + unreservedRegex + "|" + pctEncod
const static std::string authorityRegex = "(?:" + userRegex + "@)?" + hostRegex + "(?::[0-9]+)?";
const static std::string pcharRegex = "(?:" + unreservedRegex + "|" + pctEncoded + "|" + subdelimsRegex + "|[:@])";
const static std::string queryRegex = "(?:" + pcharRegex + "|[/? \"])*";
const static std::string fragmentRegex = "(?:" + pcharRegex + "|[/? \"^])*";
const static std::string segmentRegex = "(?:" + pcharRegex + "*)";
const static std::string absPathRegex = "(?:(?:/" + segmentRegex + ")*/?)";
const static std::string pathRegex = "(?:" + segmentRegex + "(?:/" + segmentRegex + ")*/?)";
Expand Down
2 changes: 1 addition & 1 deletion src/libutil/url.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ ParsedURL parseURL(const std::string & url)
"((" + schemeRegex + "):"
+ "(?:(?://(" + authorityRegex + ")(" + absPathRegex + "))|(/?" + pathRegex + ")))"
+ "(?:\\?(" + queryRegex + "))?"
+ "(?:#(" + queryRegex + "))?",
+ "(?:#(" + fragmentRegex + "))?",
std::regex::ECMAScript);

std::smatch match;
Expand Down
1 change: 1 addition & 0 deletions tests/functional/nix-profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ nix profile install "$flake1Dir^*"
[ -e $TEST_HOME/.nix-profile/include ]

printf Nix > $flake1Dir/who
nix profile list
nix profile upgrade flake1
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Nix" ]]
[ -e $TEST_HOME/.nix-profile/share/man ]
Expand Down

0 comments on commit cfb52e6

Please sign in to comment.