Skip to content

Commit 8571b9a

Browse files
committed
normalize: avail in Mex, Java, Python, and native Matlab
1 parent 1e3c145 commit 8571b9a

File tree

7 files changed

+104
-61
lines changed

7 files changed

+104
-61
lines changed

+stdlib/+java/normalize.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
function n = normalize(p)
2+
3+
n = string(javaPathObject(p).normalize());
4+
5+
if stdlib.strempty(n)
6+
n = ".";
7+
else
8+
n = strip(n, 'right', '/');
9+
if ispc()
10+
n = strip(n, 'right', filesep);
11+
end
12+
end
13+
14+
15+
16+
end

+stdlib/+native/normalize.m

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
function n = normalize(p)
2+
3+
n = stdlib.posix(string(p));
4+
5+
uncslash = ispc() && startsWith(n, "//");
6+
7+
% use split to remove /../ and /./ and duplicated /
8+
parts = split(n, '/');
9+
i0 = 1;
10+
if strncmp(n, "/", 1)
11+
n = "/";
12+
elseif ispc() && strlength(n) >= 2 && ~stdlib.strempty(stdlib.root_name(p))
13+
n = parts(1);
14+
i0 = 2;
15+
else
16+
n = "";
17+
end
18+
19+
for i = i0:length(parts)
20+
if parts(i) == ".."
21+
if n == ""
22+
n = parts(i);
23+
elseif endsWith(n, "..")
24+
n = n + "/" + parts(i);
25+
else
26+
j = strfind(n, "/");
27+
if isempty(j)
28+
n = "";
29+
else
30+
n = n{1}(1:j(end)-1);
31+
end
32+
end
33+
elseif all(parts(i) ~= [".", ""])
34+
if n == ""
35+
n = parts(i);
36+
elseif n == "/"
37+
n = n + parts(i);
38+
else
39+
n = n + "/" + parts(i);
40+
end
41+
end
42+
end
43+
44+
if uncslash
45+
n = strcat("/", n);
46+
end
47+
48+
n = fullfile(n);
49+
50+
if stdlib.strempty(n)
51+
n = ".";
52+
end
53+
54+
end

+stdlib/+python/normalize.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
function n = normalize(p)
2+
3+
try
4+
n = string(py.os.path.normpath(p));
5+
n = strip(n, 'right', '/');
6+
if ispc()
7+
n = strip(n, 'right', filesep);
8+
end
9+
catch e
10+
warning(e.identifier, "%s", e.message)
11+
n = "";
12+
end

+stdlib/normalize.m

Lines changed: 6 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,57 +13,15 @@
1313

1414
function n = normalize(p)
1515
arguments
16-
p (1,1) string
16+
p {mustBeTextScalar}
1717
end
1818

19-
n = stdlib.posix(p);
20-
21-
uncslash = ispc() && startsWith(n, "//");
22-
23-
% use split to remove /../ and /./ and duplicated /
24-
parts = split(n, '/');
25-
i0 = 1;
26-
if strncmp(n, "/", 1)
27-
n = "/";
28-
elseif ispc() && strlength(n) >= 2 && ~stdlib.strempty(stdlib.root_name(p))
29-
n = parts(1);
30-
i0 = 2;
19+
if stdlib.has_java()
20+
n = stdlib.java.normalize(p);
21+
elseif stdlib.has_python()
22+
n = stdlib.python.normalize(p);
3123
else
32-
n = "";
33-
end
34-
35-
for i = i0:length(parts)
36-
if parts(i) == ".."
37-
if n == ""
38-
n = parts(i);
39-
elseif endsWith(n, "..")
40-
n = n + "/" + parts(i);
41-
else
42-
j = strfind(n, "/");
43-
if isempty(j)
44-
n = "";
45-
else
46-
n = n{1}(1:j(end)-1);
47-
end
48-
end
49-
elseif all(parts(i) ~= [".", ""])
50-
if n == ""
51-
n = parts(i);
52-
elseif n == "/"
53-
n = n + parts(i);
54-
else
55-
n = n + "/" + parts(i);
56-
end
57-
end
58-
end
59-
60-
if uncslash
61-
n = strcat("/", n);
62-
end
63-
64-
65-
if stdlib.strempty(n)
66-
n = ".";
24+
n = stdlib.native.normalize(p);
6725
end
6826

6927
end

src/normalize_fs.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ std::string fs_normalize(std::string_view path)
3030
{
3131
std::filesystem::path p(path);
3232

33-
std::string r = p.lexically_normal().generic_string();
33+
std::string r = p.lexically_normal().string();
3434

3535
// no trailing slash
36-
if (r.length() > 1 && r.back() == '/' && (!fs_is_windows() || p != p.root_path()))
36+
if (r.length() > 1 && (r.back() == '/' || (fs_is_windows() && r.back() == '\\')) && (!fs_is_windows() || p != p.root_path()))
3737
r.pop_back();
3838

3939
if (r.empty())

test/TestCanonical.m

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
{"a/../b", "b"}, ...
88
{strcat(mfilename("fullpath"), '.m/..'), string(fileparts(mfilename("fullpath")))}, ...
99
{"not-exist/a/..", "not-exist"}, ...
10-
{"./not-exist", "not-exist"}, ...
11-
{"../not-exist", "../not-exist"}
10+
{"./not-exist", "not-exist"}
1211
};
1312
end
1413

test/TestNormalize.m

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
properties (TestParameter)
44
p = init_norm()
55
d = init_drop_slash()
6+
norm_fun = {@stdlib.normalize, @stdlib.native.normalize, @stdlib.java.normalize, @stdlib.python.normalize}
67
end
78

89
methods(TestClassSetup)
@@ -13,8 +14,11 @@ function pkg_path(tc)
1314
end
1415

1516
methods (Test, TestTags="pure")
16-
function test_normalize(tc, p)
17-
tc.verifyEqual(stdlib.normalize(p{1}), p{2}, ...
17+
18+
function test_normalize(tc, p, norm_fun)
19+
is_capable(tc, norm_fun)
20+
21+
tc.verifyEqual(norm_fun(p{1}), p{2}, ...
1822
sprintf("normalize(%s) mex: %d", p{1}, stdlib.is_mex_fun("stdlib.normalize")))
1923
end
2024

@@ -32,26 +36,26 @@ function test_drop_slash(tc, d)
3236
p = {
3337
{"", "."}, ...
3438
{"a/..", "."}, ...
35-
{"//a/b/", "/a/b"}, ...
36-
{"/a/b/", "/a/b"}, ...
37-
{"a/b/", "a/b"}, ...
39+
{"/a/b/", fullfile("/a", "b")}, ...
40+
{"a/b/", fullfile("a", "b")}, ...
3841
{"a/../c", "c"}, ...
39-
{"a/b/../c", "a/c"}, ...
42+
{"a/b/../c", fullfile("a", "c")}, ...
4043
{"a/b/../../c", "c"}, ...
4144
{"a/b/../../c/..", "."}, ...
4245
{"a/b/../../c/../..", ".."}, ...
43-
{"a////b", "a/b"}, ...
46+
{"a////b", fullfile("a", "b")}, ...
4447
{".a", ".a"}, ...
4548
{"..a", "..a"}, ...
4649
{"a.", "a."}, ...
4750
{"a..", "a.."}, ...
4851
{"./a/.", "a"}, ...
49-
{"../a", "../a"}
52+
{"../a", fullfile("..", "a")}
5053
};
5154

5255
if ispc
53-
p{3}{2} = "//a/b";
56+
p{end+1} = {"//a/b/", "\\a\b"};
5457
end
58+
5559
end
5660

5761

0 commit comments

Comments
 (0)