Skip to content

Commit 4691b76

Browse files
committed
mex: relative_to(), proximate_to() require MEX
relative_to, proximate_to require MEX for consistency across OS/platforms. The Java-based method was too shaky.
1 parent ed1dae9 commit 4691b76

File tree

8 files changed

+182
-98
lines changed

8 files changed

+182
-98
lines changed

+stdlib/proximate_to.m

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
1-
%% PROXIMATE_TO proximate path to base
2-
3-
function r = proximate_to(base, other)
4-
arguments
5-
base (1,1) string
6-
other (1,1) string
7-
end
8-
9-
r = stdlib.relative_to(base, other);
10-
11-
if ~stdlib.len(r) && ~stdlib.is_url(other)
12-
r = other;
13-
end
14-
15-
end
16-
17-
%!assert(proximate_to("/a/b", "/a/b"), ".")
18-
%!assert(proximate_to("/a/b", "/a/b/c"), "c")
19-
%!assert(proximate_to("/a/b", "/a/b/c/"), "c")
20-
%!assert(proximate_to("/a/b", "d"), "d")
1+
%% PROXIMATE_TO relative path to base
2+
%
3+
%%% Inputs
4+
% * base (1,1) string
5+
% * other (1,1) string
6+
%%% Outputs
7+
% * rel (1,1) string
8+
%
9+
% This function is written in C++ using STL <filesystem>
10+
11+
function proximate_to(~,~)
12+
error("buildtool mex")
13+
end

+stdlib/relative_to.m

Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,13 @@
11
%% RELATIVE_TO relative path to base
2-
3-
function r = relative_to(base, other)
4-
arguments
5-
base (1,1) string
6-
other (1,1) string
7-
end
8-
9-
r = "";
10-
11-
if stdlib.is_url(base) || stdlib.is_url(other), return, end
12-
13-
% must remove trailing slashes
14-
b1 = stdlib.drop_slash(base);
15-
o1 = stdlib.drop_slash(other);
16-
17-
b1 = strrep(b1, "/./", "/");
18-
o1 = strrep(o1, "/./", "/");
19-
20-
if strcmp(b1, o1)
21-
r = ".";
22-
return
2+
%
3+
%%% Inputs
4+
% * base (1,1) string
5+
% * other (1,1) string
6+
%%% Outputs
7+
% * rel (1,1) string
8+
%
9+
% This function is written in C++ using STL <filesystem>
10+
11+
function relative_to(~,~)
12+
error("buildtool mex")
2313
end
24-
25-
b = javaPathObject(b1);
26-
o = javaPathObject(o1);
27-
28-
if ~isempty(strfind(b1, "..")) %#ok<STREMP>
29-
warning("relative_to(%s) is ambiguous base with '..' consider using stdlib.canonical() first", b1)
30-
end
31-
32-
try
33-
r = jPosix(b.relativize(o));
34-
catch e
35-
r = "";
36-
if stdlib.isoctave()
37-
if isempty(strfind(e.message, "'other' is different type of Path")) && isempty(strfind(e.message, "'other' has different root"))
38-
rethrow(e);
39-
end
40-
else
41-
if ~any(contains(e.message, ["'other' is different type of Path", "'other' has different root"]))
42-
rethrow(e);
43-
end
44-
end
45-
end
46-
47-
end
48-
49-
%!assert(relative_to("/a/b", "/a/b"), ".")
50-
%!assert(relative_to("/a/b", "/a/b/c"), "c")
51-
%!assert(relative_to("/a/b", "/a/b/c/"), "c")
52-
%!assert(relative_to("/a/b", "d"), "")

+stdlib/set_permissions.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
%%% Outputs
99
% * ok (1,1) logical
1010
%
11-
% This function is written in C++ using STL <filesystem> and is only available in Matlab.
11+
% This function is written in C++ using STL <filesystem>
1212

1313
function set_permissions(~, ~, ~, ~)
1414
error("buildtool mex")

buildfile.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ function publishTask(context)
9797
srcs = {
9898
["src/is_admin.cpp", "src/admin_fs.cpp"] ...
9999
"src/is_char_device.cpp", ...
100+
"src/relative_to.cpp", ...
101+
"src/proximate_to.cpp", ...
100102
["src/is_wsl.cpp", linux], ...
101103
"src/set_permissions.cpp", ...
102104
["src/is_rosetta.cpp", mac], ...

src/proximate_to.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include "mex.hpp"
2+
#include "mexAdapter.hpp"
3+
4+
#include <string>
5+
6+
#include <vector>
7+
#include <memory>
8+
9+
#include <system_error>
10+
#include <filesystem>
11+
12+
#include "ffilesystem.h"
13+
14+
15+
16+
class MexFunction : public matlab::mex::Function {
17+
public:
18+
void operator()(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) {
19+
// boilerplate engine & ArrayFactory setup
20+
std::shared_ptr<matlab::engine::MATLABEngine> matlabEng = getEngine();
21+
22+
matlab::data::ArrayFactory factory;
23+
// wrangle inputs
24+
std::string base, other;
25+
26+
if (inputs.size() != 2) {
27+
matlabEng->feval(u"error", 0,
28+
std::vector<matlab::data::Array>({ factory.createScalar("Mex: Two inputs required") }));
29+
}
30+
31+
if ((inputs[0].getType() == matlab::data::ArrayType::MATLAB_STRING && inputs[0].getNumberOfElements() == 1)){
32+
matlab::data::TypedArray<matlab::data::MATLABString> stringArr = inputs[0];
33+
base = stringArr[0];
34+
} else if (inputs[0].getType() == matlab::data::ArrayType::CHAR){
35+
matlab::data::CharArray charArr = inputs[0];
36+
base.assign(charArr.begin(), charArr.end());
37+
} else {
38+
matlabEng->feval(u"error", 0,
39+
std::vector<matlab::data::Array>({ factory.createScalar("Mex: First input must be a scalar string or char vector") }));
40+
}
41+
42+
if ((inputs[1].getType() == matlab::data::ArrayType::MATLAB_STRING && inputs[1].getNumberOfElements() == 1)){
43+
matlab::data::TypedArray<matlab::data::MATLABString> stringArr = inputs[1];
44+
other = stringArr[0];
45+
} else if (inputs[1].getType() == matlab::data::ArrayType::CHAR){
46+
matlab::data::CharArray charArr = inputs[1];
47+
other.assign(charArr.begin(), charArr.end());
48+
} else {
49+
matlabEng->feval(u"error", 0,
50+
std::vector<matlab::data::Array>({ factory.createScalar("Mex: Second input must be a scalar string or char vector") }));
51+
}
52+
53+
// actual function algorithm / computation
54+
std::error_code ec;
55+
std::string out;
56+
57+
out = std::filesystem::proximate(other, base, ec).generic_string();
58+
59+
if(ec)
60+
matlabEng->feval(u"error", 0,
61+
std::vector<matlab::data::Array>({ factory.createScalar(ec.message()) }));
62+
63+
outputs[0] = factory.createScalar(out);
64+
}
65+
};

src/read_symlink.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ class MexFunction : public matlab::mex::Function {
3636
// actual function algorithm / computation
3737
std::string out = fs_read_symlink(in);
3838

39-
// convert to Matlab output
4039
outputs[0] = factory.createScalar(out);
4140
}
4241
};

src/relative_to.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include "mex.hpp"
2+
#include "mexAdapter.hpp"
3+
4+
#include <string>
5+
6+
#include <vector>
7+
#include <memory>
8+
9+
#include <system_error>
10+
#include <filesystem>
11+
12+
#include "ffilesystem.h"
13+
14+
15+
16+
class MexFunction : public matlab::mex::Function {
17+
public:
18+
void operator()(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) {
19+
// boilerplate engine & ArrayFactory setup
20+
std::shared_ptr<matlab::engine::MATLABEngine> matlabEng = getEngine();
21+
22+
matlab::data::ArrayFactory factory;
23+
// wrangle inputs
24+
std::string base, other;
25+
26+
if (inputs.size() != 2) {
27+
matlabEng->feval(u"error", 0,
28+
std::vector<matlab::data::Array>({ factory.createScalar("Mex: Two inputs required") }));
29+
}
30+
31+
if ((inputs[0].getType() == matlab::data::ArrayType::MATLAB_STRING && inputs[0].getNumberOfElements() == 1)){
32+
matlab::data::TypedArray<matlab::data::MATLABString> stringArr = inputs[0];
33+
base = stringArr[0];
34+
} else if (inputs[0].getType() == matlab::data::ArrayType::CHAR){
35+
matlab::data::CharArray charArr = inputs[0];
36+
base.assign(charArr.begin(), charArr.end());
37+
} else {
38+
matlabEng->feval(u"error", 0,
39+
std::vector<matlab::data::Array>({ factory.createScalar("Mex: First input must be a scalar string or char vector") }));
40+
}
41+
42+
if ((inputs[1].getType() == matlab::data::ArrayType::MATLAB_STRING && inputs[1].getNumberOfElements() == 1)){
43+
matlab::data::TypedArray<matlab::data::MATLABString> stringArr = inputs[1];
44+
other = stringArr[0];
45+
} else if (inputs[1].getType() == matlab::data::ArrayType::CHAR){
46+
matlab::data::CharArray charArr = inputs[1];
47+
other.assign(charArr.begin(), charArr.end());
48+
} else {
49+
matlabEng->feval(u"error", 0,
50+
std::vector<matlab::data::Array>({ factory.createScalar("Mex: Second input must be a scalar string or char vector") }));
51+
}
52+
53+
// actual function algorithm / computation
54+
std::error_code ec;
55+
std::string out;
56+
57+
out = std::filesystem::relative(other, base, ec).generic_string();
58+
59+
if(ec)
60+
matlabEng->feval(u"error", 0,
61+
std::vector<matlab::data::Array>({ factory.createScalar(ec.message()) }));
62+
63+
outputs[0] = factory.createScalar(out);
64+
}
65+
};

test/TestRelative.m

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
end
77

88
methods(TestClassSetup)
9-
function java_required(tc)
10-
tc.assumeTrue(stdlib.has_java())
9+
function mex_required(tc)
10+
import matlab.unittest.fixtures.CurrentFolderFixture
11+
12+
tc.applyFixture(CurrentFolderFixture(".."))
13+
% matlab exist() doesn't work for MEX detection with ".." leading path
14+
15+
tc.assumeEqual(exist("+stdlib/relative_to", "file"), 3)
16+
tc.assumeEqual(exist("+stdlib/proximate_to", "file"), 3)
1117
end
1218
end
1319

@@ -35,28 +41,30 @@ function test_proximate_to(tc, pp)
3541
{"Hello", "Hello/", "."}, ...
3642
{"a/./b", "a/b", "."}, ...
3743
{"a/b", "a/./b", "."}, ...
38-
{"./this/one", "./this/two", "../two"}, ...
39-
{"/path/same", "/path/same/hi/..", "hi/.."}, ...
40-
{"", "/", ""}, ...
41-
{"/", "", ""}, ...
44+
{"./a/b", "./a/c", "../c"}, ...
4245
{"/", "/", "."}, ...
43-
{"/dev/null", "/dev/null", "."}, ...
44-
{"/a/b", "c", ""}, ...
45-
{"c", "/a/b", ""}, ...
46-
{"/a/b", "/a/b", "."}, ...
47-
{"/a/b", "/a", ".."}, ...
48-
{"/a/b/c/d", "/a/b", "../.."}, ...
49-
{"this/one", "this/two", "../two"}};
46+
{"a/b/c/d", "a/b", "../.."}, ...
47+
{"a/b", "a/c", "../c"}, ...
48+
{"a/b", "c", "../../c"}, ...
49+
{"c", "a/b", "../a/b"}, ...
50+
{"a/b", "a/b", "."}, ...
51+
{"a/b", "a", ".."}
52+
};
5053
% NOTE: ".." in relative_to(base) is ambiguous including for python.pathlib, C++ <filesystem>, etc.
5154

5255
if ispc
5356
p = [p, ...
54-
{{"c:\a\b", "c:/", "../.."}, ...
55-
{"c:\", "c:/a/b", "a/b"}, ...
57+
{{"C:/a/b", "C:/", "../.."}, ...
58+
{"C:/", "C:/a/b", "a/b"}, ...
5659
{"c:/a/b", "c:/a/b", "."}, ...
5760
{"c:/a/b", "c:/a", ".."}, ...
5861
{"c:\a/b\c/d", "c:/a\b", "../.."}, ...
59-
{"c:/path", "d:/path", ""}}];
62+
{"C:/path", "D:/path", ""}}];
63+
% note: on Windows the drive letter should be uppercase!
64+
else
65+
p = [p, ...
66+
{{"", "a", "a"}, ...
67+
{"/dev/null", "/dev/null", "."}}];
6068
end
6169

6270
end
@@ -66,18 +74,9 @@ function test_proximate_to(tc, pp)
6674

6775
p = init_rel();
6876

69-
p{8}{3} = "/";
70-
p{12}{3} = "c";
71-
p{13}{3} = "/a/b";
7277

7378
if ispc
74-
p{8}{3} = "/";
75-
p{12}{3} = "c";
76-
p{13}{3} = "/a/b";
77-
78-
p{end}{3} = "d:/path";
79+
p{end}{3} = "D:/path";
7980
end
8081

81-
p{end+1} = {"file:///", "file:///", ""};
82-
8382
end

0 commit comments

Comments
 (0)