Skip to content

Commit

Permalink
Allow string inputs for most interfaces (#522)
Browse files Browse the repository at this point in the history
* Support optional string arguments

Can now provide double-quoted string paths and options to generate,
export, and read functions.

* Adds string checks for all filename references

* Refactor generate, export, and read functions

---------

Co-authored-by: Lawrence Niu <[email protected]>
  • Loading branch information
lawrence-mbf and lawrence-mbf authored Jul 25, 2023
1 parent 461c5c4 commit 0d334ed
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 285 deletions.
8 changes: 6 additions & 2 deletions +types/+untyped/DataStub.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@

methods
function obj = DataStub(filename, path)
obj.filename = filename;
obj.path = path;
validateattributes(filename, {'char', 'string'}, {'scalartext'} ...
, 'types.untyped.DataStub', 'filename', 1);
validateattributes(path, {'char', 'string'}, {'scalartext'} ...
, 'types.untyped.DataStub', 'path', 2);
obj.filename = char(filename);
obj.path = char(path);
end

function sid = get_space(obj)
Expand Down
8 changes: 6 additions & 2 deletions +types/+untyped/ExternalLink.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@

methods
function obj = ExternalLink(filename, path)
obj.filename = filename;
obj.path = path;
validateattributes(filename, {'char', 'string'}, {'scalartext'} ...
, 'types.untyped.ExternalLink', 'filename', 1);
validateattributes(path, {'char', 'string'}, {'scalartext'} ...
, 'types.untyped.ExternalLink', 'path', 2);
obj.filename = char(filename);
obj.path = char(path);
end

function data = deref(obj)
Expand Down
8 changes: 5 additions & 3 deletions +types/+untyped/ObjectView.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
% target = A generated NWB object.

if ischar(target) || isstring(target)
validateattributes(target, {'char', 'string'}, {'scalartext'});
obj.path = target;
validateattributes(target, {'char', 'string'}, {'scalartext'} ...
, 'types.untyped.ObjectView', 'target string', 1);
obj.path = char(target);
else
validateattributes(target, {'types.untyped.MetaClass'}, {'scalar'});
validateattributes(target, {'types.untyped.MetaClass'}, {'scalar'} ...
, 'types.untyped.ObjectView', 'target object', 1);
obj.target = target;
end
end
Expand Down
11 changes: 7 additions & 4 deletions +types/+untyped/SoftLink.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@
% target = pre-generated NWB object.

if ischar(target) || isstring(target)
validateattributes(target, {'char', 'string'}, {'scalartext'});
obj.path = target;
validateattributes(target, {'char', 'string'}, {'scalartext'} ...
, 'types.untyped.SoftLink', 'target string', 1);
obj.path = char(target);
else
validateattributes(target, {'types.untyped.MetaClass'}, {'scalar'});
validateattributes(target, {'types.untyped.MetaClass'}, {'scalar'} ...
, 'types.untyped.SoftLink', 'target object', 2);
obj.target = target;
end
end

function set.path(obj, val)
validateattributes(val, {'char', 'string'}, {'scalartext'});
validateattributes(val, {'char', 'string'}, {'scalartext'} ...
, '(types.untyped.SoftLink).path', 'path', 2);
obj.path = val;
end

Expand Down
105 changes: 53 additions & 52 deletions generateCore.m
Original file line number Diff line number Diff line change
@@ -1,54 +1,55 @@
function generateCore(varargin)
% GENERATECORE Generate Matlab classes from NWB core schema files
% GENERATECORE() Generate classes (Matlab m-files) from the
% NWB:N core namespace file. By default, generates off of the most recent nwb-schema
% release.
%
% GENERATECORE(version) Generate classes for the
% core namespace of the listed version.
%
% A cache of schema data is generated in the 'namespaces' subdirectory in
% the current working directory. This is for allowing cross-referencing
% classes between multiple namespaces.
%
% Output files are generated placed in a '+types' subdirectory in the
% current working directory.
%
% GENERATECORE(__, 'savedir', saveDirectory) Generates the core class
% files in the specified directory.
%
% Example:
% generateCore();
% generateCore('2.2.3');
%
% See also GENERATEEXTENSION

latestVersion = '2.6.0';

if nargin == 0 || strcmp(varargin{1}, 'savedir')
version = latestVersion;
else
version = varargin{1};
varargin = varargin(2:end);
validateattributes(version, {'char'}, {'scalartext'});
end

if strcmp(version, 'latest')
version = latestVersion;
end

schemaPath = fullfile(misc.getMatnwbDir(), 'nwb-schema', version);
corePath = fullfile(schemaPath, 'core', 'nwb.namespace.yaml');
commonPath = fullfile(schemaPath,...
'hdmf-common-schema', ...
'common',...
'namespace.yaml');
assert(2 == exist(corePath, 'file'),...
'NWB:GenerateCore:MissingCoreSchema',...
'Cannot find suitable core namespace for schema version `%s`',...
version);
if 2 == exist(commonPath, 'file')
generateExtension(commonPath, varargin{:});
end
generateExtension(corePath, varargin{:});
% GENERATECORE Generate Matlab classes from NWB core schema files
% GENERATECORE() Generate classes (Matlab m-files) from the
% NWB:N core namespace file. By default, generates off of the most recent nwb-schema
% release.
%
% GENERATECORE(version) Generate classes for the
% core namespace of the listed version.
%
% A cache of schema data is generated in the 'namespaces' subdirectory in
% the current working directory. This is for allowing cross-referencing
% classes between multiple namespaces.
%
% Output files are generated placed in a '+types' subdirectory in the
% current working directory.
%
% GENERATECORE(__, 'savedir', saveDirectory) Generates the core class
% files in the specified directory.
%
% Example:
% generateCore();
% generateCore('2.2.3');
%
% See also GENERATEEXTENSION

latestVersion = '2.6.0';

if nargin == 0 || strcmp(varargin{1}, 'savedir')
version = latestVersion;
else
version = varargin{1};
validateattributes(version, {'char', 'string'}, {'scalartext'}, 'generateCore', 'version', 1);
version = char(version);
varargin = varargin(2:end);
end

if strcmp(version, 'latest')
version = latestVersion;
end

schemaPath = fullfile(misc.getMatnwbDir(), 'nwb-schema', version);
corePath = fullfile(schemaPath, 'core', 'nwb.namespace.yaml');
commonPath = fullfile(schemaPath,...
'hdmf-common-schema', ...
'common',...
'namespace.yaml');
assert(2 == exist(corePath, 'file'),...
'NWB:GenerateCore:MissingCoreSchema',...
'Cannot find suitable core namespace for schema version `%s`',...
version);
if 2 == exist(commonPath, 'file')
generateExtension(commonPath, varargin{:});
end
generateExtension(corePath, varargin{:});
end
108 changes: 57 additions & 51 deletions generateExtension.m
Original file line number Diff line number Diff line change
@@ -1,56 +1,62 @@
function generateExtension(varargin)
% GENERATEEXTENSION Generate Matlab classes from NWB extension schema file
% GENERATEEXTENSION(extension_path...) Generate classes
% (Matlab m-files) from one or more NWB:N schema extension namespace
% files. A registry of already generated core types is used to resolve
% dependent types.
%
% A cache of schema data is generated in the 'namespaces' subdirectory in
% the current working directory. This is for allowing cross-referencing
% classes between multiple namespaces.
%
% Output files are generated placed in a '+types' subdirectory in the
% current working directory.
%
% Example:
% generateExtension('schema\myext\myextension.namespace.yaml', 'schema\myext2\myext2.namespace.yaml');
%
% See also GENERATECORE
assert(iscellstr(varargin),...
'NWB:GenerateExtension:InvalidArguments',...
'Must be a cell array of strings.'); %#ok<ISCLSTR>

saveDirMask = strcmp(varargin, 'savedir');
if any(saveDirMask)
assert(~saveDirMask(end),...
'NWB:GenerateExtenion:InvalidParameter',...
'savedir must be paired with the desired save directory.');
saveDir = varargin{find(saveDirMask, 1, 'last') + 1};
saveDirParametersMask = saveDirMask | circshift(saveDirMask, 1);
sourceList = varargin(~saveDirParametersMask);
else
saveDir = misc.getMatnwbDir();
sourceList = varargin;
end

for iNamespaceFiles = 1:length(sourceList)
source = sourceList{iNamespaceFiles};
validateattributes(source, {'char', 'string'}, {'scalartext'});
% GENERATEEXTENSION Generate Matlab classes from NWB extension schema file
% GENERATEEXTENSION(extension_path...) Generate classes
% (Matlab m-files) from one or more NWB:N schema extension namespace
% files. A registry of already generated core types is used to resolve
% dependent types.
%
% A cache of schema data is generated in the 'namespaces' subdirectory in
% the current working directory. This is for allowing cross-referencing
% classes between multiple namespaces.
%
% Output files are generated placed in a '+types' subdirectory in the
% current working directory.
%
% Example:
% generateExtension('schema\myext\myextension.namespace.yaml', 'schema\myext2\myext2.namespace.yaml');
%
% See also GENERATECORE

[localpath, ~, ~] = fileparts(source);
assert(2 == exist(source, 'file'),...
'NWB:GenerateExtension:FileNotFound', 'Path to file `%s` could not be found.', source);
fid = fopen(source);
namespaceText = fread(fid, '*char') .';
fclose(fid);
for iOption = 1:length(varargin)
option = varargin{iOption};
validateattributes(option, {'char', 'string'}, {'scalartext'} ...
, 'generateExtension', 'extension name', iOption);
if isstring(option)
varargin{iOption} = char(option);
end
end

Namespaces = spec.generate(namespaceText, localpath);

for iNamespace = 1:length(Namespaces)
Namespace = Namespaces(iNamespace);
spec.saveCache(Namespace, saveDir);
file.writeNamespace(Namespace.name, saveDir);
rehash();
saveDirMask = strcmp(varargin, 'savedir');
if any(saveDirMask)
assert(~saveDirMask(end),...
'NWB:GenerateExtenion:InvalidParameter',...
'savedir must be paired with the desired save directory.');
saveDir = varargin{find(saveDirMask, 1, 'last') + 1};
saveDirParametersMask = saveDirMask | circshift(saveDirMask, 1);
sourceList = varargin(~saveDirParametersMask);
else
saveDir = misc.getMatnwbDir();
sourceList = varargin;
end

for iNamespaceFiles = 1:length(sourceList)
source = sourceList{iNamespaceFiles};
validateattributes(source, {'char', 'string'}, {'scalartext'});

[localpath, ~, ~] = fileparts(source);
assert(2 == exist(source, 'file'),...
'NWB:GenerateExtension:FileNotFound', 'Path to file `%s` could not be found.', source);
fid = fopen(source);
namespaceText = fread(fid, '*char') .';
fclose(fid);

Namespaces = spec.generate(namespaceText, localpath);

for iNamespace = 1:length(Namespaces)
Namespace = Namespaces(iNamespace);
spec.saveCache(Namespace, saveDir);
file.writeNamespace(Namespace.name, saveDir);
rehash();
end
end
end
end
74 changes: 40 additions & 34 deletions nwbExport.m
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
function nwbExport(nwb, filenames)
%NWBEXPORT Writes an NWB file.
% nwbRead(nwb,filename) Writes the nwb object to a file at filename.
%
% Example:
% % Generate Matlab code for the NWB objects from the core schema.
% % This only needs to be done once.
% generateCore('schema\core\nwb.namespace.yaml');
% % Create some fake fata and write
% nwb = NwbFile;
% nwb.session_start_time = datetime('now');
% nwb.identifier = 'EMPTY';
% nwb.session_description = 'empty test file';
% nwbExport(nwb, 'empty.nwb');
%
% See also GENERATECORE, GENERATEEXTENSION, NWBFILE, NWBREAD
validateattributes(nwb, {'NwbFile'}, {'nonempty'});
validateattributes(filenames, {'cell', 'string', 'char'}, {'nonempty'});
if iscell(filenames)
assert(iscellstr(filenames), 'filename cell array must consist of strings');
end
if ~isscalar(nwb)
assert(~ischar(filenames) && length(filenames) == length(nwb), ...
'NwbFile and filename array dimensions must match.');
end

for i=1:length(nwb)
if iscellstr(filenames)
filename = filenames{i};
elseif isstring(filenames)
filename = filenames(i);
else
filename = filenames;
%NWBEXPORT Writes an NWB file.
% nwbRead(nwb,filename) Writes the nwb object to a file at filename.
%
% Example:
% % Generate Matlab code for the NWB objects from the core schema.
% % This only needs to be done once.
% generateCore('schema\core\nwb.namespace.yaml');
% % Create some fake fata and write
% nwb = NwbFile;
% nwb.session_start_time = datetime('now');
% nwb.identifier = 'EMPTY';
% nwb.session_description = 'empty test file';
% nwbExport(nwb, 'empty.nwb');
%
% See also GENERATECORE, GENERATEEXTENSION, NWBFILE, NWBREAD
validateattributes(nwb, {'NwbFile'}, {'nonempty'}, 'nwbExport', 'nwb', 1);
validateattributes(filenames, {'cell', 'string', 'char'}, {'nonempty'}, 'nwbExport', 'filenames', 2);
if isstring(filenames)
filenames = convertStringsToChars(filenames);
end
if iscell(filenames)
for iName = 1:length(filenames)
name = filenames{iName};
validateattributes(name, {'string', 'char'}, {'scalartext', 'nonempty'} ...
, 'nwbExport', 'filenames', 2);
filenames{iName} = char(name);
end
end
if ~isscalar(nwb)
assert(~ischar(filenames) && length(filenames) == length(nwb), ...
'NwbFile and filename array dimensions must match.');
end

nwb(i).export(filename);
end
for iFiles = 1:length(nwb)
if iscellstr(filenames)
filename = filenames{iFiles};
else
filename = filenames;
end

nwb(iFiles).export(filename);
end
end
Loading

0 comments on commit 0d334ed

Please sign in to comment.