diff --git a/pkgs/path/CHANGELOG.md b/pkgs/path/CHANGELOG.md index 9ad90d26..09668fbc 100644 --- a/pkgs/path/CHANGELOG.md +++ b/pkgs/path/CHANGELOG.md @@ -1,5 +1,6 @@ ## 1.9.2-wip +- `join('C:', r'\Users\name')` keeps the drive letter. - Make `Url` style better at recognizing schemes and authorities. Only consider a path as being a schemed URL if it starts with a valid scheme. diff --git a/pkgs/path/lib/src/context.dart b/pkgs/path/lib/src/context.dart index a036b61c..d790c896 100644 --- a/pkgs/path/lib/src/context.dart +++ b/pkgs/path/lib/src/context.dart @@ -323,7 +323,13 @@ final class Context { var isAbsoluteAndNotRootRelative = false; for (var part in parts.where((part) => part != '')) { - if (isRootRelative(part) && isAbsoluteAndNotRootRelative) { + if (isRootRelative(part) && _isWindowsDriveOnly(buffer.toString())) { + // `HOMEDRIVE` is `C:` and `HOMEPATH` is `\Users\name`. `C:` is not an + // absolute root, so a following root-relative part would otherwise + // discard the drive. + buffer.write(part); + isAbsoluteAndNotRootRelative = true; + } else if (isRootRelative(part) && isAbsoluteAndNotRootRelative) { // If the new part is root-relative, it preserves the previous root but // replaces the path after it. final parsed = _parse(part); @@ -360,6 +366,15 @@ final class Context { return buffer.toString(); } + /// A Windows path that is only a drive letter, such as `C:`. + bool _isWindowsDriveOnly(String path) { + if (style.name != 'windows' || path.length != 2) return false; + final letter = path.codeUnitAt(0); + final isLetter = (letter >= chars.upperA && letter <= chars.upperZ) || + (letter >= chars.lowerA && letter <= chars.lowerZ); + return isLetter && path.codeUnitAt(1) == chars.colon; + } + /// Splits [path] into its components using the current platform's /// [separator]. Example: /// diff --git a/pkgs/path/test/windows_test.dart b/pkgs/path/test/windows_test.dart index 88ceb3a0..9a17f951 100644 --- a/pkgs/path/test/windows_test.dart +++ b/pkgs/path/test/windows_test.dart @@ -334,6 +334,12 @@ void main() { expect(context.join('a/', 'b'), r'a/b'); }); + test('keeps a drive letter when the next part is root-relative', () { + expect(context.join('C:', r'\Users\danny'), r'C:\Users\danny'); + expect(context.join('c:', r'\Users\danny', 'docs'), r'c:\Users\danny\docs'); + expect(context.joinAll(['C:', r'\Users\danny']), r'C:\Users\danny'); + }); + test('ignores parts before an absolute path', () { expect(context.join('a', r'\b', r'\c', 'd'), r'\c\d'); expect(context.join('a', '/b', '/c', 'd'), r'/c\d');