authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-11-13 17:36:49-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-11-15 10:53:06-07:00
log7d0c461b77890847899548cc69a701bcb0a3a6a6
treef2704a3ef8a5836fddfc74887a50a3197a9da7e4
parent2d280825cdde1d546f80d12b333bf670ac3bfbeb

std.fs.path.dirname: return null when input path is root

This intentionally diverges from the unix dirname command, as well as Python and Node.js standard libraries, which all have this edge case return the input path, unmodified. This is a footgun, and nobody should have ever done it this way. Even the man page contradicts the behavior. It says: "strip last component from file name". Now consider, if you remove the last item from an array of length 1, then you have now an array of length 0. After you strip the last component, there should be no components remaining. Clearly, returning the input parameter unmodified in this case does not match the documented behavior. This is my justification for taking a stand on this API design. closes #6746 closes #6727 closes #6584 closes #6592 closes #6602

1 files changed, 22 insertions(+), 16 deletions(-)

lib/std/fs/path.zig+22-16
...@@ -749,8 +749,12 @@ fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void {...@@ -749,8 +749,12 @@ fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void {
749 return testing.expect(mem.eql(u8, actual, expected));749 return testing.expect(mem.eql(u8, actual, expected));
750}750}
751751
752/// Strip the last component from a file path.
753///
752/// If the path is a file in the current directory (no directory component)754/// If the path is a file in the current directory (no directory component)
753/// then returns null755/// then returns null.
756///
757/// If the path is the root directory, returns null.
754pub fn dirname(path: []const u8) ?[]const u8 {758pub fn dirname(path: []const u8) ?[]const u8 {
755 if (builtin.os.tag == .windows) {759 if (builtin.os.tag == .windows) {
756 return dirnameWindows(path);760 return dirnameWindows(path);
...@@ -765,19 +769,19 @@ pub fn dirnameWindows(path: []const u8) ?[]const u8 {...@@ -765,19 +769,19 @@ pub fn dirnameWindows(path: []const u8) ?[]const u8 {
765769
766 const root_slice = diskDesignatorWindows(path);770 const root_slice = diskDesignatorWindows(path);
767 if (path.len == root_slice.len)771 if (path.len == root_slice.len)
768 return path;772 return null;
769773
770 const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\');774 const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\');
771775
772 var end_index: usize = path.len - 1;776 var end_index: usize = path.len - 1;
773777
774 while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > root_slice.len) {778 while (path[end_index] == '/' or path[end_index] == '\\') {
775 if (end_index == 0)779 if (end_index == 0)
776 return null;780 return null;
777 end_index -= 1;781 end_index -= 1;
778 }782 }
779783
780 while (path[end_index] != '/' and path[end_index] != '\\' and end_index > root_slice.len) {784 while (path[end_index] != '/' and path[end_index] != '\\') {
781 if (end_index == 0)785 if (end_index == 0)
782 return null;786 return null;
783 end_index -= 1;787 end_index -= 1;
...@@ -800,7 +804,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 {...@@ -800,7 +804,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 {
800 var end_index: usize = path.len - 1;804 var end_index: usize = path.len - 1;
801 while (path[end_index] == '/') {805 while (path[end_index] == '/') {
802 if (end_index == 0)806 if (end_index == 0)
803 return path[0..1];807 return null;
804 end_index -= 1;808 end_index -= 1;
805 }809 }
806810
...@@ -810,7 +814,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 {...@@ -810,7 +814,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 {
810 end_index -= 1;814 end_index -= 1;
811 }815 }
812816
813 if (end_index == 0 and path[end_index] == '/')817 if (end_index == 0 and path[0] == '/')
814 return path[0..1];818 return path[0..1];
815819
816 if (end_index == 0)820 if (end_index == 0)
...@@ -823,8 +827,10 @@ test "dirnamePosix" {...@@ -823,8 +827,10 @@ test "dirnamePosix" {
823 testDirnamePosix("/a/b/c", "/a/b");827 testDirnamePosix("/a/b/c", "/a/b");
824 testDirnamePosix("/a/b/c///", "/a/b");828 testDirnamePosix("/a/b/c///", "/a/b");
825 testDirnamePosix("/a", "/");829 testDirnamePosix("/a", "/");
826 testDirnamePosix("/", "/");830 testDirnamePosix("/", null);
827 testDirnamePosix("////", "/");831 testDirnamePosix("//", null);
832 testDirnamePosix("///", null);
833 testDirnamePosix("////", null);
828 testDirnamePosix("", null);834 testDirnamePosix("", null);
829 testDirnamePosix("a", null);835 testDirnamePosix("a", null);
830 testDirnamePosix("a/", null);836 testDirnamePosix("a/", null);
...@@ -832,27 +838,27 @@ test "dirnamePosix" {...@@ -832,27 +838,27 @@ test "dirnamePosix" {
832}838}
833839
834test "dirnameWindows" {840test "dirnameWindows" {
835 testDirnameWindows("c:\\", "c:\\");841 testDirnameWindows("c:\\", null);
836 testDirnameWindows("c:\\foo", "c:\\");842 testDirnameWindows("c:\\foo", "c:\\");
837 testDirnameWindows("c:\\foo\\", "c:\\");843 testDirnameWindows("c:\\foo\\", "c:\\");
838 testDirnameWindows("c:\\foo\\bar", "c:\\foo");844 testDirnameWindows("c:\\foo\\bar", "c:\\foo");
839 testDirnameWindows("c:\\foo\\bar\\", "c:\\foo");845 testDirnameWindows("c:\\foo\\bar\\", "c:\\foo");
840 testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar");846 testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar");
841 testDirnameWindows("\\", "\\");847 testDirnameWindows("\\", null);
842 testDirnameWindows("\\foo", "\\");848 testDirnameWindows("\\foo", "\\");
843 testDirnameWindows("\\foo\\", "\\");849 testDirnameWindows("\\foo\\", "\\");
844 testDirnameWindows("\\foo\\bar", "\\foo");850 testDirnameWindows("\\foo\\bar", "\\foo");
845 testDirnameWindows("\\foo\\bar\\", "\\foo");851 testDirnameWindows("\\foo\\bar\\", "\\foo");
846 testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar");852 testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar");
847 testDirnameWindows("c:", "c:");853 testDirnameWindows("c:", null);
848 testDirnameWindows("c:foo", "c:");854 testDirnameWindows("c:foo", null);
849 testDirnameWindows("c:foo\\", "c:");855 testDirnameWindows("c:foo\\", null);
850 testDirnameWindows("c:foo\\bar", "c:foo");856 testDirnameWindows("c:foo\\bar", "c:foo");
851 testDirnameWindows("c:foo\\bar\\", "c:foo");857 testDirnameWindows("c:foo\\bar\\", "c:foo");
852 testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar");858 testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar");
853 testDirnameWindows("file:stream", null);859 testDirnameWindows("file:stream", null);
854 testDirnameWindows("dir\\file:stream", "dir");860 testDirnameWindows("dir\\file:stream", "dir");
855 testDirnameWindows("\\\\unc\\share", "\\\\unc\\share");861 testDirnameWindows("\\\\unc\\share", null);
856 testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\");862 testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\");
857 testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\");863 testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\");
858 testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo");864 testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo");
...@@ -862,8 +868,8 @@ test "dirnameWindows" {...@@ -862,8 +868,8 @@ test "dirnameWindows" {
862 testDirnameWindows("/a/b", "/a");868 testDirnameWindows("/a/b", "/a");
863 testDirnameWindows("/a", "/");869 testDirnameWindows("/a", "/");
864 testDirnameWindows("", null);870 testDirnameWindows("", null);
865 testDirnameWindows("/", "/");871 testDirnameWindows("/", null);
866 testDirnameWindows("////", "/");872 testDirnameWindows("////", null);
867 testDirnameWindows("foo", null);873 testDirnameWindows("foo", null);
868}874}
869875