diff --git a/+stdlib/absolute.m b/+stdlib/absolute.m index e4af35a..c052a46 100644 --- a/+stdlib/absolute.m +++ b/+stdlib/absolute.m @@ -17,14 +17,23 @@ % non-existant path is made absolute relative to pwd function c = absolute(p, base, expand_tilde, use_java) -arguments - p (1,1) string - base (1,1) string = "" - expand_tilde (1,1) logical = true - use_java (1,1) logical = false +% arguments +% p (1,1) string +% base (1,1) string = "" +% expand_tilde (1,1) logical = true +% use_java (1,1) logical = false +% end +if nargin < 2, base = ""; end +if nargin < 3, expand_tilde = true; end +if nargin < 4, use_java = false; end + +c = pwd(); + +try %#ok + c = string(c); end -cwd = stdlib.posix(pwd()); +cwd = stdlib.posix(c); Lb = stdlib.len(base); @@ -70,5 +79,5 @@ end -%!assert(absolute('', '', 0,0), posix(pwd)) -%!assert(absolute('a/b', '', 0,0), posix(fullfile(pwd, 'a/b'))) +%!assert(absolute('', ''), posix(pwd)) +%!assert(absolute('a/b', ''), posix(fullfile(pwd, 'a/b'))) diff --git a/+stdlib/canonical.m b/+stdlib/canonical.m index 997e663..c6ed337 100644 --- a/+stdlib/canonical.m +++ b/+stdlib/canonical.m @@ -14,11 +14,13 @@ % https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/File.html#getCanonicalPath() function c = canonical(p, expand_tilde, use_java) -arguments - p (1,1) string - expand_tilde (1,1) logical = true - use_java (1,1) logical = false -end +% arguments +% p (1,1) string +% expand_tilde (1,1) logical = true +% use_java (1,1) logical = false +% end +if nargin < 2, expand_tilde = true; end +if nargin < 3, use_java = false; end if expand_tilde c = stdlib.expanduser(p, use_java); @@ -59,6 +61,6 @@ end -%!assert(canonical("", 0,0), "") -%!assert(canonical("~",1,0), homedir()) -%!assert(canonical("a/b/..",0,0), "a") +%!assert(canonical(""), "") +%!assert(canonical("~"), homedir()) +%!assert(canonical("a/b/.."), "a") diff --git a/+stdlib/create_symlink.m b/+stdlib/create_symlink.m index 45ddd20..62a343d 100644 --- a/+stdlib/create_symlink.m +++ b/+stdlib/create_symlink.m @@ -9,18 +9,17 @@ % Ref: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html#createSymbolicLink(java.nio.file.Path,java.nio.file.Path,java.nio.file.attribute.FileAttribute...) function ok = create_symlink(target, link) - -arguments - target (1,1) string - link (1,1) string -end +% arguments +% target (1,1) string +% link (1,1) string +% end if stdlib.isoctave() [err, msg] = symlink(target, link); ok = err == 0; if ~ok - warning("symlink: %s", msg) + warning("create_symlink: %s", msg) end elseif ispc || isMATLABReleaseOlderThan("R2024b") @@ -61,5 +60,5 @@ %!test %! if !ispc -%! create_symlink(tempname, tempname) +%! assert(create_symlink(tempname, tempname)) %! endif diff --git a/+stdlib/diskfree.m b/+stdlib/diskfree.m index 58e58b3..0b9b766 100644 --- a/+stdlib/diskfree.m +++ b/+stdlib/diskfree.m @@ -6,9 +6,9 @@ % Ref: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/File.html#getUsableSpace() function f = diskfree(d) -arguments - d (1,1) string {mustBeFolder} -end +% arguments +% d (1,1) string +% end if stdlib.isoctave() o = javaObject("java.io.File", d); diff --git a/+stdlib/drop_slash.m b/+stdlib/drop_slash.m index 04ffc87..9231bde 100644 --- a/+stdlib/drop_slash.m +++ b/+stdlib/drop_slash.m @@ -1,9 +1,9 @@ %% DROP_SLASH drop repeated and trailing slash function d = drop_slash(p) -arguments - p (1,1) string -end +% arguments +% p (1,1) string +% end s = stdlib.posix(p); @@ -12,7 +12,7 @@ L = stdlib.len(d); -if d == '/' || ~L +if strcmp(d, '/') || ~L return; end diff --git a/+stdlib/exists.m b/+stdlib/exists.m index a3da74f..071b722 100644 --- a/+stdlib/exists.m +++ b/+stdlib/exists.m @@ -7,11 +7,11 @@ % function ok = exists(p, use_java) -arguments - p (1,1) string - use_java (1,1) logical = false -end - +% arguments +% p (1,1) string +% use_java (1,1) logical = false +% end +if nargin < 2, use_java = false; end if stdlib.isoctave() ok = javaObject("java.io.File", p).exists(); diff --git a/+stdlib/expanduser.m b/+stdlib/expanduser.m index d9cd4a0..57b68f5 100644 --- a/+stdlib/expanduser.m +++ b/+stdlib/expanduser.m @@ -8,10 +8,11 @@ % * e: expanded path function e = expanduser(p, use_java) -arguments - p (1,1) string - use_java (1,1) logical = false -end +% arguments +% p (1,1) string +% use_java (1,1) logical = false +% end +if nargin < 2, use_java = false; end e = stdlib.drop_slash(p); @@ -49,9 +50,9 @@ end -%!assert(expanduser('',0), '') -%!assert(expanduser("~",0), homedir()) -%!assert(expanduser("~/",0), homedir()) -%!assert(expanduser("~user",0), "~user") -%!assert(expanduser("~user/",0), "~user") -%!assert(expanduser("~///c",0), strcat(homedir(), "/c")) +%!assert(expanduser(''), '') +%!assert(expanduser("~"), homedir()) +%!assert(expanduser("~/"), homedir()) +%!assert(expanduser("~user"), "~user") +%!assert(expanduser("~user/"), "~user") +%!assert(expanduser("~///c"), strcat(homedir(), "/c")) diff --git a/+stdlib/file_size.m b/+stdlib/file_size.m index cc97c16..ddabdc1 100644 --- a/+stdlib/file_size.m +++ b/+stdlib/file_size.m @@ -1,10 +1,11 @@ % FILE_SIZE size in bytes of file function s = file_size(p, use_java) -arguments - p (1,1) string {mustBeFile} - use_java (1,1) logical = false -end +% arguments +% p (1,1) string {mustBeFile} +% use_java (1,1) logical = false +% end +if nargin < 2, use_java = false; end if stdlib.isoctave() s = javaObject("java.io.File", p).length(); diff --git a/+stdlib/filename.m b/+stdlib/filename.m index 212712b..3dc8e7c 100644 --- a/+stdlib/filename.m +++ b/+stdlib/filename.m @@ -2,9 +2,9 @@ % filename (including suffix) without directory function f = filename(p) -arguments - p (1,1) string -end +% arguments +% p (1,1) string +% end % NOT https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/File.html#getName() % because by our definition, a trailing directory component is not part of the filename diff --git a/+stdlib/get_pid.m b/+stdlib/get_pid.m index 2109d99..0277da6 100644 --- a/+stdlib/get_pid.m +++ b/+stdlib/get_pid.m @@ -2,7 +2,7 @@ function pid = get_pid() -if stdlib.isoctave +if stdlib.isoctave() pid = getpid(); else pid = feature("getpid"); diff --git a/+stdlib/get_shell.m b/+stdlib/get_shell.m index 1bdd6a2..4c24e81 100644 --- a/+stdlib/get_shell.m +++ b/+stdlib/get_shell.m @@ -2,8 +2,12 @@ % return value of environment variable SHELL % this is mostly relevant on unix-like systems -function shell = get_shell() +function s = get_shell() -shell = string(getenv("SHELL")); +s = getenv("SHELL"); + +try %#ok + s = string(s); +end end diff --git a/+stdlib/h5exists.m b/+stdlib/h5exists.m index 4305313..8d6d10d 100644 --- a/+stdlib/h5exists.m +++ b/+stdlib/h5exists.m @@ -7,10 +7,10 @@ % * exists: boolean function exists = h5exists(file, variable) -arguments - file (1,1) string {mustBeFile} - variable (1,1) string -end +% arguments +% file (1,1) string {mustBeFile} +% variable (1,1) string +% end exists = false; @@ -19,7 +19,8 @@ exists = true; catch e if stdlib.isoctave - if ~strfind(e.message, 'does not exist') + disp(e.message) + if strcmp(e.identifier, "Octave:undefined-function") || isempty(strfind(e.message, 'does not exist')) rethrow(e) end else @@ -35,7 +36,7 @@ %! pkg load hdf5oct %! fn = 'test_h5exists.h5'; %! ds = '/a'; -%! delete(fn) +%! if isfile(fn), delete(fn); end %! h5create(fn, ds, [1]) %! assert(h5exists(fn, ds)) %! delete(fn) diff --git a/+stdlib/h5save_exist.m b/+stdlib/h5save_exist.m index 28661b1..751bb9c 100644 --- a/+stdlib/h5save_exist.m +++ b/+stdlib/h5save_exist.m @@ -32,11 +32,12 @@ function h5save_exist(filename, varname, A, sizeA) end %!test +%! pkg load hdf5oct %! fn = 'test_h5save_exist.h5'; %! ds = '/a'; %! a = [1,2]; %! b = [3,4]; -%! delete(fn) +%! if isfile(fn), delete(fn); end %! h5save_new(fn, ds, a, size(a), 0) %! h5save_exist(fn, ds, b, size(b)) %! assert(h5read(fn, ds), b) diff --git a/+stdlib/h5save_new.m b/+stdlib/h5save_new.m index 92bdd3b..f09822b 100644 --- a/+stdlib/h5save_new.m +++ b/+stdlib/h5save_new.m @@ -31,10 +31,11 @@ function h5save_new(filename, varname, A, sizeA, compressLevel) end %!test +%! pkg load hdf5oct %! fn = 'test_h5save_new.h5'; %! ds = '/a'; %! a = [1,2]; -%! delete(fn) +%! if isfile(fn), delete(fn); end %! h5save_new(fn, ds, a, size(a), 0) %! assert(h5read(fn, ds), a) %! delete(fn) diff --git a/+stdlib/h5size.m b/+stdlib/h5size.m index e8e0f99..b3e77dc 100644 --- a/+stdlib/h5size.m +++ b/+stdlib/h5size.m @@ -7,10 +7,10 @@ % fsize: vector of variable size per dimension. Empty if scalar variable. function fsize = h5size(file, variable) -arguments - file (1,1) string {mustBeFile} - variable (1,1) string {mustBeNonzeroLengthText} -end +% arguments +% file (1,1) string {mustBeFile} +% variable (1,1) string {mustBeNonzeroLengthText} +% end dsi = h5info(file, variable).Dataspace; @@ -28,7 +28,7 @@ %! fn = 'test_h5size.h5'; %! ds = '/a'; %! a = [1,2]; -%! delete(fn) +%! if isfile(fn), delete(fn); end %! h5save_new(fn, ds, a, size(a), 0) %! assert(h5size(fn, ds), uint64([1,2])) %! delete(fn) diff --git a/+stdlib/h5variables.m b/+stdlib/h5variables.m index f00b493..51a7551 100644 --- a/+stdlib/h5variables.m +++ b/+stdlib/h5variables.m @@ -9,12 +9,13 @@ % * names: variable names function names = h5variables(file, group) -arguments - file (1,1) string {mustBeFile} - group (1,1) string = "" -end +% arguments +% file (1,1) string {mustBeFile} +% group (1,1) string = "" +% end +if nargin < 2, group = ""; end -if stdlib.len(group) == 0 +if ~stdlib.len(group) finf = h5info(file); else finf = h5info(file, group); @@ -23,7 +24,7 @@ ds = finf.Datasets; if ischar(file) - if isempty(ds) %#ok + if isempty(ds) names = []; else names = {ds.Name}; @@ -43,7 +44,7 @@ %! pkg load hdf5oct %! fn = 'test_h5variables.h5'; %! ds = '/a'; -%! delete(fn) +%! if isfile(fn), delete(fn); end %! h5create(fn, ds, [1]) %! assert(h5variables(fn, ''), {'/a'}) %! delete(fn) diff --git a/+stdlib/handle2filename.m b/+stdlib/handle2filename.m index 5d041f7..3eb01f5 100644 --- a/+stdlib/handle2filename.m +++ b/+stdlib/handle2filename.m @@ -1,14 +1,14 @@ %% HANDLE2FILENAME Convert a file handle to a filename -function name = handle2filename(fileHandle) +function n = handle2filename(fileHandle) arguments - fileHandle (1,1) {mustBeInteger} + fileHandle (1,1) {mustBeInteger} end if fileHandle >= 0 - name = stdlib.posix(fopen(fileHandle)); + n = stdlib.posix(string(fopen(fileHandle))); else - name = string.empty; + n = string.empty; end end diff --git a/+stdlib/has_java.m b/+stdlib/has_java.m index 2fb992e..0871415 100644 --- a/+stdlib/has_java.m +++ b/+stdlib/has_java.m @@ -11,3 +11,5 @@ ok = h; end + +%!assert(islogical(has_java())) diff --git a/+stdlib/homedir.m b/+stdlib/homedir.m index 9693865..f007ece 100644 --- a/+stdlib/homedir.m +++ b/+stdlib/homedir.m @@ -5,9 +5,10 @@ % * https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html#getProperties() function h = homedir(use_java) -arguments - use_java (1,1) logical = false -end +% arguments +% use_java (1,1) logical = false +% end +if nargin < 1, use_java = false; end if stdlib.isoctave() h = javaMethod("getProperty", "java.lang.System", "user.home"); @@ -19,6 +20,10 @@ h = getenv("HOME"); end +try %#ok + h = string(h); +end + h = stdlib.posix(h); end diff --git a/+stdlib/hostname.m b/+stdlib/hostname.m index b969240..05e7d5b 100644 --- a/+stdlib/hostname.m +++ b/+stdlib/hostname.m @@ -12,3 +12,5 @@ end end + +%!assert (!isempty(hostname())) diff --git a/+stdlib/is_absolute.m b/+stdlib/is_absolute.m index e7e7ae4..e88252c 100644 --- a/+stdlib/is_absolute.m +++ b/+stdlib/is_absolute.m @@ -4,10 +4,11 @@ function isabs = is_absolute(p, use_java) -arguments - p (1,1) - use_java (1,1) logical = false -end +% arguments +% p (1,1) +% use_java (1,1) logical = false +% end +if nargin < 2, use_java = false; end if stdlib.isoctave() % not is_absolute_filename() because this is a stricter check for "c:" false @@ -31,7 +32,7 @@ end -%!assert(is_absolute('', false), false) +%!assert(is_absolute(''), false) %!test %! if ispc %! assert(is_absolute('C:\')) diff --git a/+stdlib/is_exe.m b/+stdlib/is_exe.m index d0ce23e..e2d5565 100644 --- a/+stdlib/is_exe.m +++ b/+stdlib/is_exe.m @@ -4,10 +4,11 @@ function ok = is_exe(p, use_java) -arguments - p (1,1) string - use_java (1,1) logical = false -end +% arguments +% p (1,1) string +% use_java (1,1) logical = false +% end +if nargin < 2, use_java = false; end if stdlib.isoctave() ok = javaObject("java.io.File", p).canExecute(); @@ -15,7 +16,7 @@ % about the same time as fileattrib % doesn't need absolute path like other Java functions % https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/io/File.html#canExecute() -ok = java.io.File(p).canExecute(); + ok = java.io.File(p).canExecute(); % more complicated % ok = java.nio.file.Files.isExecutable(java.io.File(stdlib.canonical(p)).toPath()); @@ -35,6 +36,6 @@ end -%!assert (is_exe(''), false) -%!assert (is_exe(tempname), false) -%!assert (is_exe(program_invocation_name), true) +%!assert (!is_exe('')) +%!assert (!is_exe(tempname)) +%!assert (is_exe(program_invocation_name)) diff --git a/+stdlib/is_parallel_worker.m b/+stdlib/is_parallel_worker.m index 6c9e354..de471b7 100644 --- a/+stdlib/is_parallel_worker.m +++ b/+stdlib/is_parallel_worker.m @@ -23,4 +23,4 @@ ispar = ~isempty(getCurrentWorker()); end -end % function +end diff --git a/+stdlib/is_readable.m b/+stdlib/is_readable.m index 1611228..2efbef9 100644 --- a/+stdlib/is_readable.m +++ b/+stdlib/is_readable.m @@ -3,10 +3,11 @@ % non-existant file is false function ok = is_readable(file, use_java) -arguments - file (1,1) string - use_java (1,1) logical = false -end +% arguments +% file (1,1) string +% use_java (1,1) logical = false +% end +if nargin < 2, use_java = false; end if use_java % java is about 10x slower than fileattrib @@ -23,5 +24,5 @@ end -%!assert (is_readable('is_readable.m', false)) -%!assert (is_readable('', false), false) +%!assert (is_readable('is_readable.m')) +%!assert (!is_readable('')) diff --git a/+stdlib/is_rosetta.m b/+stdlib/is_rosetta.m index 7faa216..7911ea2 100644 --- a/+stdlib/is_rosetta.m +++ b/+stdlib/is_rosetta.m @@ -11,6 +11,12 @@ % uname -m reports "x86_64" from within Matlab on Apple Silicon if using Rosetta [ret, raw] = system("sysctl -n sysctl.proc_translated"); -r = ret == 0 && startsWith(raw, "1"); +r = ret == 0 && strncmp(raw, '1', 1); end + +%!assert(islogical(is_rosetta())) +%!test +%! if ~ismac +%! assert(!is_rosetta()) +%! endif diff --git a/+stdlib/is_subdir.m b/+stdlib/is_subdir.m index 72731fe..c8d73b5 100644 --- a/+stdlib/is_subdir.m +++ b/+stdlib/is_subdir.m @@ -3,17 +3,17 @@ % duplicated slashes are dropped function s = is_subdir(subdir, dir) -arguments - subdir (1,1) string - dir (1,1) string -end +% arguments +% subdir (1,1) string +% dir (1,1) string +% end s = stdlib.drop_slash(subdir); d = stdlib.drop_slash(dir); if ischar(subdir) - w = ~isempty(strfind(d, "..")) || ~isempty(strfind(s, "..")); %#ok + w = ~isempty(strfind(d, "..")) || ~isempty(strfind(s, "..")); %#ok s = strfind(s, d) == 1 && (length(s) > length(d)); else w = contains(d, "..") || contains(s, ".."); diff --git a/+stdlib/is_windows_powershell.m b/+stdlib/is_windows_powershell.m deleted file mode 100644 index 5f4ed0a..0000000 --- a/+stdlib/is_windows_powershell.m +++ /dev/null @@ -1,17 +0,0 @@ -%% IS_WINDOWS_POWERSHELL detects Windows powershell vs. Command Prompt -% detects Windows powershell vs. Command Prompt -% Matlab on Windows Powershell vs Comspec for system() -% would impact syntax of some system() commands -% -% adapted from: https://stackoverflow.com/a/34480405/7703794 - -function iswps = is_windows_powershell() -iswps = false; -if ~ispc - return -end - -[ret, ~] = system("dir 2>&1 *`"); -iswps = ret == 0; - -end diff --git a/+stdlib/is_writable.m b/+stdlib/is_writable.m index 84c9bdc..2e772ec 100644 --- a/+stdlib/is_writable.m +++ b/+stdlib/is_writable.m @@ -3,10 +3,11 @@ % non-existant file is false function ok = is_writable(file, use_java) -arguments - file (1,1) string - use_java (1,1) logical = false -end +% arguments +% file (1,1) string +% use_java (1,1) logical = false +% end +if nargin < 2, use_java = false; end if use_java % java is about 10x slower than fileattrib @@ -23,5 +24,5 @@ end -%!assert (is_writable('is_writable.m', false)) -%!assert (is_writable('', false), false) +%!assert (is_writable('is_writable.m')) +%!assert (!is_writable('')) diff --git a/+stdlib/is_wsl_path.m b/+stdlib/is_wsl_path.m index 821edb1..2705e15 100644 --- a/+stdlib/is_wsl_path.m +++ b/+stdlib/is_wsl_path.m @@ -4,7 +4,7 @@ function iswsl = is_wsl_path(p) arguments - p (1,1) string {mustBeNonzeroLengthText} + p (1,1) string end if ispc diff --git a/+stdlib/isinteractive.m b/+stdlib/isinteractive.m index 2ab56e2..194d771 100644 --- a/+stdlib/isinteractive.m +++ b/+stdlib/isinteractive.m @@ -1,9 +1,15 @@ %% ISINTERACTIVE tell if being run interactively % -% Matlab-only as this test doesn't work for Octave. -% % NOTE: don't use batchStartupOptionUsed as it neglects the "-nodesktop" case -function isinter = isinteractive() -isinter = usejava('desktop'); +function g = isinteractive() + +if stdlib.isoctave() + g = ~isempty(graphics_toolkit()); +else + g = usejava('desktop'); end + +end + +%!assert (islogical(isinteractive())) diff --git a/+stdlib/isoctave.m b/+stdlib/isoctave.m index b8157b0..d4d313b 100644 --- a/+stdlib/isoctave.m +++ b/+stdlib/isoctave.m @@ -11,3 +11,5 @@ isoct = o; end + +%!assert (isoctave()) diff --git a/+stdlib/join.m b/+stdlib/join.m index 9de88e5..b71b8db 100644 --- a/+stdlib/join.m +++ b/+stdlib/join.m @@ -1,35 +1,38 @@ %% JOIN join two paths with posix file separator function p = join(base, other, use_java) -arguments - base (1,1) string - other (1,1) string - use_java (1,1) logical = false -end +% arguments +% base (1,1) string +% other (1,1) string +% use_java (1,1) logical = false +% end +if nargin < 3, use_java = false; end + +b = stdlib.drop_slash(base); +o = stdlib.drop_slash(other); + +Lb = stdlib.len(b); +Lo = stdlib.len(o); -if base == "" && other == "" +if ~Lb && ~Lo p = ""; return end -b = stdlib.drop_slash(base); -o = stdlib.drop_slash(other); - -if b == "" +if ~Lb p = o; return end -if o == "" +if ~stdlib.len(o) p = b; return end - -if use_java - -p = java.io.File(b).toPath().resolve(o); - +if stdlib.isoctave() + p = javaObject("java.io.File", b).toPath().resolve(o).toString(); +elseif use_java + p = java.io.File(b).toPath().resolve(o); else if startsWith(o, "/") || (ispc && stdlib.is_absolute(o)) diff --git a/+stdlib/makedir.m b/+stdlib/makedir.m index b8dda84..d31cdb5 100644 --- a/+stdlib/makedir.m +++ b/+stdlib/makedir.m @@ -3,9 +3,9 @@ % This function works around that bug in Matlab mkdir(). function makedir(d) -arguments - d (1,1) string {mustBeNonzeroLengthText} -end +% arguments +% d (1,1) string {mustBeNonzeroLengthText} +% end %% to avoid confusing making ./~/mydir d = stdlib.expanduser(d); @@ -16,6 +16,6 @@ function makedir(d) mkdir(d); -mustBeFolder(d) +assert(isfolder(d), "stdlib:makedir:mkdir", "Failed to create %s", d) end diff --git a/+stdlib/ncexists.m b/+stdlib/ncexists.m index 1051d16..1d518a5 100644 --- a/+stdlib/ncexists.m +++ b/+stdlib/ncexists.m @@ -7,10 +7,10 @@ % * exists: boolean function exists = ncexists(file, variable) -arguments - file (1,1) string {mustBeFile} - variable (1,1) string -end +% arguments +% file (1,1) string {mustBeFile} +% variable (1,1) string +% end exists = false; @@ -20,7 +20,7 @@ catch e if stdlib.isoctave() disp(e) - if ~strfind(e.message, 'No such file or directory') + if strcmp(e.identifier, "Octave:undefined-function") || isempty(strfind(e.message, 'No such file or directory')) rethrow(e) end else diff --git a/+stdlib/ncsave_exist.m b/+stdlib/ncsave_exist.m index 1dccb3d..f435f7e 100644 --- a/+stdlib/ncsave_exist.m +++ b/+stdlib/ncsave_exist.m @@ -16,11 +16,12 @@ function ncsave_exist(filename, varname, A, sizeA) end %!test +%! pkg load netcdf %! fn = 'test_ncsave_exist.nc'; %! ds = 'a'; %! a = [1,2]; %! b = [3,4]; -%! delete(fn) +%! if isfile(fn), delete(fn); end %! ncsave_new(fn, ds, a, size(a), {"x", 1, "y", 2}, 0) %! ncsave_exist(fn, ds, b, size(b)) %! assert(ncread(fn, ds), b) diff --git a/+stdlib/ncsave_new.m b/+stdlib/ncsave_new.m index caa7dd3..09d4854 100644 --- a/+stdlib/ncsave_new.m +++ b/+stdlib/ncsave_new.m @@ -22,10 +22,11 @@ function ncsave_new(file, varname, A, sizeA, ncdims, compressLevel) end %!test +%! pkg load netcdf %! fn = 'test_ncsave_new.nc'; %! ds = 'a'; %! a = [1,2]; -%! delete(fn) +%! if isfile(fn), delete(fn); end %! ncsave_new(fn, ds, a, size(a), {"x", 1, "y", 2}, 0) %! assert(ncread(fn, ds), a) %! delete(fn) diff --git a/+stdlib/ncsize.m b/+stdlib/ncsize.m index 90ab945..e4436bf 100644 --- a/+stdlib/ncsize.m +++ b/+stdlib/ncsize.m @@ -8,10 +8,10 @@ % fsize: vector of variable size per dimension. Empty if scalar variable. function fsize = ncsize(file, variable) -arguments - file (1,1) string {mustBeFile} - variable (1,1) string {mustBeNonzeroLengthText} -end +% arguments +% file (1,1) string {mustBeFile} +% variable (1,1) string +% end dsi = ncinfo(file, variable); if isempty(dsi.Dimensions) @@ -25,7 +25,7 @@ %!test %! pkg load netcdf %! fn = 'test_size.nc'; -%! delete(fn) +%! if isfile(fn), delete(fn); end %! nccreate(fn, 'a') %! assert(ncsize(fn, 'a'), []) %! nccreate(fn, 'b', 'Dimensions', {'x', 2, 'y', 3}) diff --git a/+stdlib/ncvariables.m b/+stdlib/ncvariables.m index aefbb29..5dfe5a5 100644 --- a/+stdlib/ncvariables.m +++ b/+stdlib/ncvariables.m @@ -9,10 +9,12 @@ % * names: variable names function names = ncvariables(file, group) -arguments - file (1,1) string {mustBeFile} - group (1,1) string = "" -end +% arguments +% file (1,1) string {mustBeFile} +% group (1,1) string = "" +% end +if nargin < 2, group = ""; end + if stdlib.len(group) == 0 finf = ncinfo(file); @@ -23,7 +25,7 @@ ds = finf.Variables(:); if ischar(file) - if isempty(ds) %#ok + if isempty(ds) names = []; else names = {ds.Name}; @@ -44,7 +46,7 @@ %! pkg load netcdf %! fn = 'test_variables.nc'; %! ds = 'a'; -%! delete(fn) +%! if isfile(fn), delete(fn); end %! nccreate(fn, ds) %! assert(ncvariables(fn, ''), {'a'}) %! delete(fn) diff --git a/+stdlib/normalize.m b/+stdlib/normalize.m index 046dea1..88781ef 100644 --- a/+stdlib/normalize.m +++ b/+stdlib/normalize.m @@ -9,10 +9,11 @@ % https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Path.html#normalize() function n = normalize(p, use_java) -arguments - p (1,1) string - use_java (1,1) logical = false -end +% arguments +% p (1,1) string +% use_java (1,1) logical = false +% end +if nargin < 2, use_java = false; end if stdlib.isoctave() n = stdlib.posix(javaObject("java.io.File", p).toPath().normalize().toString()); diff --git a/+stdlib/parent.m b/+stdlib/parent.m index ea9e171..eda14c6 100644 --- a/+stdlib/parent.m +++ b/+stdlib/parent.m @@ -2,9 +2,9 @@ % function p = parent(pth) -arguments - pth (1,1) string -end +% arguments +% pth (1,1) string +% end % https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/File.html#getParent() % java was 10x slower and not correct for input like C: diff --git a/+stdlib/posix.m b/+stdlib/posix.m index 10608fe..de5a07f 100644 --- a/+stdlib/posix.m +++ b/+stdlib/posix.m @@ -4,9 +4,9 @@ % function r = posix(p) -arguments - p (1,1) string -end +% arguments +% p (1,1) string +% end if ispc r = strrep(p, '\', '/'); diff --git a/+stdlib/proximate_to.m b/+stdlib/proximate_to.m index 4633ddb..f9ca02b 100644 --- a/+stdlib/proximate_to.m +++ b/+stdlib/proximate_to.m @@ -1,10 +1,10 @@ %% PROXIMATE_TO proximate path to base function r = proximate_to(base, other) -arguments - base (1,1) string - other (1,1) string -end +% arguments +% base (1,1) string +% other (1,1) string +% end r = stdlib.relative_to(base, other); diff --git a/+stdlib/ram_total.m b/+stdlib/ram_total.m index a3e5d10..2282bd0 100644 --- a/+stdlib/ram_total.m +++ b/+stdlib/ram_total.m @@ -6,6 +6,7 @@ % * bytes: total physical RAM [bytes] function bytes = ram_total() + b = java.lang.management.ManagementFactory.getOperatingSystemMXBean(); if stdlib.java_api() < 14 diff --git a/+stdlib/read_symlink.m b/+stdlib/read_symlink.m index 2c98e3f..b5b8c20 100644 --- a/+stdlib/read_symlink.m +++ b/+stdlib/read_symlink.m @@ -3,30 +3,38 @@ % empty string if path is not a symlink function r = read_symlink(p) -arguments - p (1,1) string -end - -r = string.empty; +% arguments +% p (1,1) string +% end -if isMATLABReleaseOlderThan("R2024b") +r = ""; -if ~stdlib.is_symlink(p) - return -end +if stdlib.isoctave() + t = readlink(p); +elseif isMATLABReleaseOlderThan("R2024b") + if ~stdlib.is_symlink(p) + return + end -% must be absolute path -% must not be .canonical or symlink is gobbled! -r = stdlib.absolute(p, "", false, true); - -% https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html#readSymbolicLink(java.nio.file.Path) -t = java.nio.file.Files.readSymbolicLink(java.io.File(r).toPath()); + % must be absolute path + % must not be .canonical or symlink is gobbled! + r = stdlib.absolute(p, "", false, true); + % https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html#readSymbolicLink(java.nio.file.Path) + t = string(java.nio.file.Files.readSymbolicLink(java.io.File(r).toPath())); else [ok, t] = isSymbolicLink(p); if ~ok, return, end + t = string(t); end r = stdlib.posix(t); end + +%!test +%! p = tempname(); +%! this = strcat(mfilename("fullpath"), '.m'); +%! assert (read_symlink(p), "") +%! assert (create_symlink(this, p)) +%! assert (read_symlink(p), this) diff --git a/+stdlib/relative_to.m b/+stdlib/relative_to.m index c05cf2d..39516dc 100644 --- a/+stdlib/relative_to.m +++ b/+stdlib/relative_to.m @@ -1,10 +1,10 @@ %% RELATIVE_TO relative path to base function r = relative_to(base, other) -arguments - base (1,1) string - other (1,1) string -end +% arguments +% base (1,1) string +% other (1,1) string +% end % must remove trailing slashes b1 = stdlib.drop_slash(base); @@ -33,7 +33,7 @@ end try - r = stdlib.posix(b.relativize(o).toString()); + r = b.relativize(o).toString(); catch e r = ""; if stdlib.isoctave() @@ -47,6 +47,12 @@ end end +if isstring(b1) + r = string(r); +end + +r = stdlib.posix(r); + end %!assert(relative_to("/a/b", "/a/b"), ".") diff --git a/+stdlib/resolve.m b/+stdlib/resolve.m index 079ffc0..b09b1c6 100644 --- a/+stdlib/resolve.m +++ b/+stdlib/resolve.m @@ -10,12 +10,16 @@ % non-existant path is made absolute relative to pwd function r = resolve(p, expand_tilde, use_java) -arguments - p (1,1) string - expand_tilde (1,1) logical = true - use_java (1,1) logical = false -end +% arguments +% p (1,1) string +% expand_tilde (1,1) logical = true +% use_java (1,1) logical = false +% end +if nargin < 2, expand_tilde = true; end +if nargin < 3, use_java = false; end r = stdlib.canonical(stdlib.absolute(p, "", expand_tilde, use_java), false, use_java); end + +%!assert (resolve(''), pwd()) diff --git a/+stdlib/root.m b/+stdlib/root.m index 01dbcdd..87151c8 100644 --- a/+stdlib/root.m +++ b/+stdlib/root.m @@ -4,10 +4,11 @@ function r = root(p, use_java) -arguments - p (1,1) string - use_java (1,1) logical = false -end +% arguments +% p (1,1) string +% use_java (1,1) logical = false +% end +if nargin < 2, use_java = false; end r = ""; if stdlib.len(p) == 0 @@ -24,21 +25,21 @@ r = stdlib.posix(java.io.File(p).toPath().getRoot().toString()); else -r = stdlib.root_name(p); + r = stdlib.root_name(p); -if strlength(r) == 0 - if startsWith(p, "/") - r = "/"; - end + if strlength(r) == 0 + if startsWith(p, "/") + r = "/"; + end - return -end + return + end -if ispc && r == p - return -end + if ispc && r == p + return + end -r = r + "/"; + r = r + "/"; end diff --git a/+stdlib/root_name.m b/+stdlib/root_name.m index bb95fd7..cfae919 100644 --- a/+stdlib/root_name.m +++ b/+stdlib/root_name.m @@ -6,9 +6,9 @@ function r = root_name(p) -arguments - p (1,1) string -end +% arguments +% p (1,1) string +% end r = ""; @@ -17,7 +17,7 @@ end if ischar(p) - if p(2) == ':' && isletter(p(1)) %#ok + if p(2) == ':' && isletter(p(1)) r = p(1:2); end else diff --git a/+stdlib/samepath.m b/+stdlib/samepath.m index b54bd59..4830282 100644 --- a/+stdlib/samepath.m +++ b/+stdlib/samepath.m @@ -10,10 +10,10 @@ function issame = samepath(path1, path2) -arguments - path1 (1,1) string - path2 (1,1) string -end +% arguments +% path1 (1,1) string +% path2 (1,1) string +% end % simpler our way than % https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html#isSameFile(java.nio.file.Path,java.nio.file.Path) @@ -25,4 +25,4 @@ %!assert(samepath(".", ".")) %!assert(samepath(".", "./")) -%!assert(samepath(".", "a/..")) +%!assert(!samepath(".", "not-exist/..")) diff --git a/+stdlib/stem.m b/+stdlib/stem.m index 726a89d..2a4cb51 100644 --- a/+stdlib/stem.m +++ b/+stdlib/stem.m @@ -1,9 +1,9 @@ %% STEM filename without directory or suffix function p = stem(p) -arguments - p (1,1) string -end +% arguments +% p (1,1) string +% end [~, p] = fileparts(p); diff --git a/+stdlib/suffix.m b/+stdlib/suffix.m index e960ade..391f07a 100644 --- a/+stdlib/suffix.m +++ b/+stdlib/suffix.m @@ -1,9 +1,9 @@ %% SUFFIX last suffix of filename function s = suffix(p) -arguments - p (1,1) string -end +% arguments +% p (1,1) string +% end [~, ~, s] = fileparts(p); diff --git a/+stdlib/touch.m b/+stdlib/touch.m index 735fb07..9e9de21 100644 --- a/+stdlib/touch.m +++ b/+stdlib/touch.m @@ -1,9 +1,9 @@ %% TOUCH create file if not exists, else update modification time function ok = touch(p) -arguments - p (1,1) string -end +% arguments +% p (1,1) string +% end if stdlib.exists(p) ok = stdlib.set_modtime(p); @@ -17,3 +17,8 @@ end end + +%!test +%! f = tempname(); +%! assert (touch(f)) +%! assert (isfile(f)) diff --git a/+stdlib/version_atleast.m b/+stdlib/version_atleast.m index e39652f..1938d39 100644 --- a/+stdlib/version_atleast.m +++ b/+stdlib/version_atleast.m @@ -9,10 +9,10 @@ % * r: logical function r = version_atleast(in, ref) -arguments - in (1,1) string - ref (1,1) string -end +% arguments +% in (1,1) string +% ref (1,1) string +% end if stdlib.isoctave() r = compare_versions(in, ref, '>='); diff --git a/+stdlib/with_suffix.m b/+stdlib/with_suffix.m index 9c752c9..1f5f1fc 100644 --- a/+stdlib/with_suffix.m +++ b/+stdlib/with_suffix.m @@ -7,10 +7,10 @@ % * f: modified filename function f = with_suffix(p, suffix) -arguments - p (1,1) string - suffix (1,1) string -end +% arguments +% p (1,1) string +% suffix (1,1) string +% end r = stdlib.parent(p); s = stdlib.stem(p); diff --git a/Readme.md b/Readme.md index 22579c7..bf9b693 100644 --- a/Readme.md +++ b/Readme.md @@ -5,10 +5,12 @@ [![ci](https://github.com/geospace-code/matlab-stdlib/actions/workflows/ci.yml/badge.svg)](https://github.com/geospace-code/matlab-stdlib/actions/workflows/ci.yml) [![ci-nojvm](https://github.com/geospace-code/matlab-stdlib/actions/workflows/ci-nojvm.yml/badge.svg)](https://github.com/geospace-code/matlab-stdlib/actions/workflows/ci-nojvm.yml) -Matlab users coming from other languages often notice the missing functionality contained within this user-developed, unofficial "stdlib" for Matlab. +Matlab or GNU Octave users coming from other languages often notice the missing functionality contained within this user-developed, unofficial "stdlib" standard library of functions for users of Matlab or GNU Octave. These system, filesystem, and HDF5 / HDF4 / NetCDF functions are useful across several of our own and others projects. -The absolute minimum Matlab release is R2021a. +Matlab ≥ R2021a has full functionality. +Older versions of Matlab work back to about R2017b. +If using GNU Octave, the minimum version is 6.0. Self-tests can be run from that matlab-stdlib/ directory: @@ -18,13 +20,34 @@ buildtool [API Documentation](https://geospace-code.github.io/matlab-stdlib) -Many Matlab-Stdlib functions use the factory JRE in Matlab, and have been tested with JVM versions 8 and 17. +Many Matlab-Stdlib functions use the factory JRE in Matlab or GNU Octave, and have been tested with JVM versions 8 and 17. For reference, we further [discuss Java implementation details](./Readme_java.md). + If Matlab was started with -[-nojvm](https://www.mathworks.com/help/matlab/matlab_env/commonly-used-startup-options.html), -some Matlab-stdlib functions do not work. -We have a [CI job that tests without Java](https://github.com/geospace-code/matlab-stdlib/actions/workflows/ci-nojvm.yml). +[-nojvm](https://www.mathworks.com/help/matlab/matlab_env/commonly-used-startup-options.html) +or GNU Octave started without Java, +most Matlab-stdlib functions still work. +This [CI job](https://github.com/geospace-code/matlab-stdlib/actions/workflows/ci-nojvm.yml) +tests without Java. + +## GNU Octave + +For HDF5 h5*() functions, install +[hdf5oct](https://gnu-octave.github.io/packages/hdf5oct/) +package from Octave prompt: + +```octave +pkg install -forge hdf5oct +``` + +For NetCDF4 nc*() functions, install +[netcdf](https://gnu-octave.github.io/packages/netcdf/) +package from Octave prompt: + +```octave +pkg install -forge netcdf +``` ## Acknowledgments diff --git a/Readme_java.md b/Readme_java.md index ac6ebca..aeb7538 100644 --- a/Readme_java.md +++ b/Readme_java.md @@ -1,37 +1,15 @@ # Matlab-Stdlib Java implementation -Matlab has used Java extensively internally for over a decade. -While the "New Desktop" is HTML and JavaScript based, and already other new GUI elements in Matlab were not using Java, the underlying JRE interface is treated at least like other Matlab external languages such as C++ and Python. +Matlab has used Java since before Matlab R2006a. +GNU Octave also can [use Java](https://docs.octave.org/latest/Set-up-the-JVM.html). Matlab-Stdlib uses only factory JRE classes. -For reference, it is readily possible in general to use non-factory -[Java classes](https://www.mathworks.com/help/matlab/matlab_external/static-path-of-java-class-path.html) -in Matlab. - -The Matlab-Stdlib package uses Java functions throughout, including: - -* [java.lang.ProcessBuilder](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/ProcessBuilder.html) -* [java.lang.System](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/System.html) -* [java.nio.file](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/nio/file/Files.html) -* [java.io.File](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/io/File.html) -* [java.net.InetAddress](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/net/InetAddress.html) -* [java.lang.management.ManagementFactory](https://docs.oracle.com/en/java/javase/21/docs/api/jdk.management/com/sun/management/OperatingSystemMXBean.html) -* [java.security.MessageDigest](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/security/MessageDigest.html) - -## Java version - -Get the JVM version with +Tell JVM version ```matlab version("-java") ``` -Get the JVM details with - -```matlab -jenv -```. - Get the Java API level: ```matlab @@ -44,6 +22,15 @@ Get the Java version: stdlib.java_version ``` +If desired (not used by Matlab-stdlib), one can use non-factory Java classes in +[Matlab](](https://www.mathworks.com/help/matlab/matlab_external/static-path-of-java-class-path.html)) +and +[GNU Octave](https://docs.octave.org/interpreter/Making-Java-Classes-Available.html). + + +## Configure Matlab JVM + +The Matlab Java interface is like other Matlab external languages such as Python. The Matlab default [JVM can be configured](https://www.mathworks.com/help/matlab/matlab_external/configure-your-system-to-use-java.html) to @@ -54,6 +41,12 @@ by using the [jenv](https://www.mathworks.com/help/matlab/ref/jenv.html) Matlab function. +Tell JVM details: + +```matlab +jenv +``` + For example, to use the [JDK 17 on macOS](https://www.oracle.com/java/technologies/downloads/#jdk17-mac) download and extract the ARM64 Compressed Archive. @@ -70,3 +63,14 @@ if Matlab can't start or has problems, from system Terminal (not within Matlab): ```sh matlab_jenv factory ``` + +## Configure GNU Octave JVM + +GNU [Octave JVM](https://docs.octave.org/latest/Set-up-the-JVM.html) +can be configured with the JAVA_HOME environment variable. +Some install packages don't include Java. +For example, with Homebrew: + +```sh +brew install octave openjdk +``` diff --git a/test/TestCanonical.m b/test/TestCanonical.m new file mode 100644 index 0000000..41f9a07 --- /dev/null +++ b/test/TestCanonical.m @@ -0,0 +1,44 @@ +classdef TestCanonical < matlab.unittest.TestCase + +properties (TestParameter) +p_canonical +end + +methods (TestParameterDefinition, Static) +% needs to be here so that setup_path() runs first, +% otherwise whole file may be silently skipped +function p_canonical = init_canonical() +p_canonical = ... +{{"", ""}, {"not-exist", "not-exist"}, {"a/../b", "b"}, ... +{"~", stdlib.homedir()}, {"~/", stdlib.homedir()}, ... +{"~/..", stdlib.parent(stdlib.homedir())}, ...f +{mfilename("fullpath") + ".m/..", string(fileparts(mfilename("fullpath")))}}; +end + +end + + +methods(TestClassSetup) + +function setup_path(tc) +top = fullfile(fileparts(mfilename("fullpath")), ".."); +tc.applyFixture(matlab.unittest.fixtures.PathFixture(top)) +end + +end + + +methods(Test) + +function test_canonical(tc, p_canonical) +tc.verifyEqual(stdlib.canonical(p_canonical{1}), p_canonical{2}) +end + +function test_static(tc) +% ~ is expanded even without expanduser when path exists +tc.verifyEqual(stdlib.canonical("~/nobody/a/..", false), "~/nobody") +end + +end + +end diff --git a/test/TestResolve.m b/test/TestResolve.m index 583a98b..5aac81c 100644 --- a/test/TestResolve.m +++ b/test/TestResolve.m @@ -12,7 +12,6 @@ function setup_path(tc) methods(Test) - function test_absolute(tc) import matlab.unittest.fixtures.TemporaryFolderFixture import matlab.unittest.fixtures.CurrentFolderFixture @@ -40,46 +39,6 @@ function test_absolute(tc) end - -function test_canonical(tc) -import matlab.unittest.fixtures.TemporaryFolderFixture -import matlab.unittest.fixtures.CurrentFolderFixture -import matlab.unittest.constraints.StartsWithSubstring - -td = tc.applyFixture(TemporaryFolderFixture).Folder; -tc.applyFixture(CurrentFolderFixture(td)) - -% all non-existing files - -tc.verifyEqual(stdlib.canonical(""), "") - -pabs = stdlib.canonical('2foo'); -tc.verifyThat(pabs, StartsWithSubstring("2foo")) - -par1 = stdlib.canonical("../2foo"); -tc.verifyThat(par1, StartsWithSubstring("..")) - -pt1 = stdlib.canonical("bar/../2foo"); -tc.verifyEqual(pt1, "2foo") - -% test existing file -r = stdlib.parent(mfilename('fullpath')); -tc.verifyEqual(stdlib.canonical(fullfile(r, "..")), stdlib.parent(r)) - -% ~ is expanded even without expanduser when path exists -tc.verifyEqual(stdlib.canonical("~/nobody/a/..", false), "~/nobody") - -h = stdlib.homedir; -tc.verifyEqual(stdlib.canonical("~"), h) -tc.verifyEqual(stdlib.canonical("~/"), h) -tc.verifyEqual(stdlib.canonical("~/.."), stdlib.parent(h)) - -tc.verifyEqual(stdlib.canonical("nobody.txt"), "nobody.txt") -tc.verifyEqual(stdlib.canonical("../nobody.txt"), "../nobody.txt") - -end - - function test_resolve(tc) import matlab.unittest.fixtures.TemporaryFolderFixture import matlab.unittest.fixtures.CurrentFolderFixture diff --git a/test/TestSymlink.m b/test/TestSymlink.m index 4aa10ab..96ed3dc 100644 --- a/test/TestSymlink.m +++ b/test/TestSymlink.m @@ -51,8 +51,8 @@ function test_is_symlink(tc, p_is_symlink) function test_read_symlink(tc) import matlab.unittest.constraints.IsOfClass -tc.verifyEmpty(stdlib.read_symlink("not-exist")) -tc.verifyEmpty(stdlib.read_symlink(tc.d.this)) +tc.verifyEqual(stdlib.read_symlink("not-exist"), "") +tc.verifyEqual(stdlib.read_symlink(tc.d.this), "") t = stdlib.read_symlink(tc.d.link); tc.verifyNotEmpty(t) diff --git a/test/TestSys.m b/test/TestSys.m index 1d20442..fc822c9 100644 --- a/test/TestSys.m +++ b/test/TestSys.m @@ -21,8 +21,6 @@ function test_platform(tc) tc.verifyThat(stdlib.is_rosetta, IsOfClass('logical')) tc.verifyThat(stdlib.isinteractive, IsOfClass('logical')) - -tc.verifyThat(stdlib.is_windows_powershell, IsOfClass('logical')) end end