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 {
749749 return testing.expect(mem.eql(u8, actual, expected));
750750}
751751
752/// Strip the last component from a file path.
753///
752754/// If the path is a file in the current directory (no directory component)
753/// then returns null
755/// then returns null.
756///
757/// If the path is the root directory, returns null.
754758pub fn dirname(path: []const u8) ?[]const u8 {
755759 if (builtin.os.tag == .windows) {
756760 return dirnameWindows(path);
......@@ -765,19 +769,19 @@ pub fn dirnameWindows(path: []const u8) ?[]const u8 {
765769
766770 const root_slice = diskDesignatorWindows(path);
767771 if (path.len == root_slice.len)
768 return path;
772 return null;
769773
770774 const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\');
771775
772776 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] == '\\') {
775779 if (end_index == 0)
776780 return null;
777781 end_index -= 1;
778782 }
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] != '\\') {
781785 if (end_index == 0)
782786 return null;
783787 end_index -= 1;
......@@ -800,7 +804,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 {
800804 var end_index: usize = path.len - 1;
801805 while (path[end_index] == '/') {
802806 if (end_index == 0)
803 return path[0..1];
807 return null;
804808 end_index -= 1;
805809 }
806810
......@@ -810,7 +814,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 {
810814 end_index -= 1;
811815 }
812816
813 if (end_index == 0 and path[end_index] == '/')
817 if (end_index == 0 and path[0] == '/')
814818 return path[0..1];
815819
816820 if (end_index == 0)
......@@ -823,8 +827,10 @@ test "dirnamePosix" {
823827 testDirnamePosix("/a/b/c", "/a/b");
824828 testDirnamePosix("/a/b/c///", "/a/b");
825829 testDirnamePosix("/a", "/");
826 testDirnamePosix("/", "/");
827 testDirnamePosix("////", "/");
830 testDirnamePosix("/", null);
831 testDirnamePosix("//", null);
832 testDirnamePosix("///", null);
833 testDirnamePosix("////", null);
828834 testDirnamePosix("", null);
829835 testDirnamePosix("a", null);
830836 testDirnamePosix("a/", null);
......@@ -832,27 +838,27 @@ test "dirnamePosix" {
832838}
833839
834840test "dirnameWindows" {
835 testDirnameWindows("c:\\", "c:\\");
841 testDirnameWindows("c:\\", null);
836842 testDirnameWindows("c:\\foo", "c:\\");
837843 testDirnameWindows("c:\\foo\\", "c:\\");
838844 testDirnameWindows("c:\\foo\\bar", "c:\\foo");
839845 testDirnameWindows("c:\\foo\\bar\\", "c:\\foo");
840846 testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar");
841 testDirnameWindows("\\", "\\");
847 testDirnameWindows("\\", null);
842848 testDirnameWindows("\\foo", "\\");
843849 testDirnameWindows("\\foo\\", "\\");
844850 testDirnameWindows("\\foo\\bar", "\\foo");
845851 testDirnameWindows("\\foo\\bar\\", "\\foo");
846852 testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar");
847 testDirnameWindows("c:", "c:");
848 testDirnameWindows("c:foo", "c:");
849 testDirnameWindows("c:foo\\", "c:");
853 testDirnameWindows("c:", null);
854 testDirnameWindows("c:foo", null);
855 testDirnameWindows("c:foo\\", null);
850856 testDirnameWindows("c:foo\\bar", "c:foo");
851857 testDirnameWindows("c:foo\\bar\\", "c:foo");
852858 testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar");
853859 testDirnameWindows("file:stream", null);
854860 testDirnameWindows("dir\\file:stream", "dir");
855 testDirnameWindows("\\\\unc\\share", "\\\\unc\\share");
861 testDirnameWindows("\\\\unc\\share", null);
856862 testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\");
857863 testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\");
858864 testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo");
......@@ -862,8 +868,8 @@ test "dirnameWindows" {
862868 testDirnameWindows("/a/b", "/a");
863869 testDirnameWindows("/a", "/");
864870 testDirnameWindows("", null);
865 testDirnameWindows("/", "/");
866 testDirnameWindows("////", "/");
871 testDirnameWindows("/", null);
872 testDirnameWindows("////", null);
867873 testDirnameWindows("foo", null);
868874}
869875