Skip to content

Commit

Permalink
Merge pull request #506 from NeurodataWithoutBorders/238-allow-unit-v…
Browse files Browse the repository at this point in the history
…ectordata

Allows Vectordata objects to support optional Units-column properties `unit`, `resolution`, and `sampling_rate`
  • Loading branch information
lawrence-mbf authored May 26, 2023
2 parents 442c604 + 235c16f commit 43cdffb
Show file tree
Hide file tree
Showing 13 changed files with 696 additions and 536 deletions.
224 changes: 123 additions & 101 deletions +file/fillClass.m
Original file line number Diff line number Diff line change
@@ -1,119 +1,141 @@
function template = fillClass(name, namespace, processed, classprops, inherited)
%name is the name of the scheme
%namespace is the namespace context for this class
%name is the name of the scheme
%namespace is the namespace context for this class

%% PROCESSING
class = processed(1);
%% PROCESSING
class = processed(1);

allprops = keys(classprops);
required = {};
optional = {};
readonly = {};
defaults = {};
dependent = {};
%separate into readonly, required, and optional properties
for i=1:length(allprops)
pnm = allprops{i};
prop = classprops(pnm);
allProperties = keys(classprops);
required = {};
optional = {};
readonly = {};
defaults = {};
dependent = {};
hidden = {}; % special hidden properties for hard-coded workarounds
%separate into readonly, required, and optional properties
for iGroup = 1:length(allProperties)
propertyName = allProperties{iGroup};
prop = classprops(propertyName);

isRequired = ischar(prop) || isa(prop, 'containers.Map') || isstruct(prop);
iIsPropertyRequired = false;
if isa(prop, 'file.interface.HasProps')
iIsPropertyRequired = false(size(prop));
for iProp = 1:length(prop)
p = prop(iProp);
iIsPropertyRequired(iProp) = p.required;
end
end

if isRequired || all(iIsPropertyRequired)
required = [required {pnm}];
else
optional = [optional {pnm}];
end

if isa(prop, 'file.Attribute')
if prop.readonly
readonly = [readonly {pnm}];
isRequired = ischar(prop) || isa(prop, 'containers.Map') || isstruct(prop);
isPropertyRequired = false;
if isa(prop, 'file.interface.HasProps')
isPropertyRequired = false(size(prop));
for iProp = 1:length(prop)
p = prop(iProp);
isPropertyRequired(iProp) = p.required;
end
end

if ~isempty(prop.value)
defaults = [defaults {pnm}];

if isRequired || all(isPropertyRequired)
required = [required {propertyName}];
else
optional = [optional {propertyName}];
end

if ~isempty(prop.dependent)
%extract prefix
parentName = strrep(pnm, ['_' prop.name], '');
parent = classprops(parentName);
if ~parent.required
dependent = [dependent {pnm}];

if isa(prop, 'file.Attribute')
if prop.readonly
readonly = [readonly {propertyName}];
end

if ~isempty(prop.value)
defaults = [defaults {propertyName}];
end

if ~isempty(prop.dependent)
%extract prefix
parentName = strrep(propertyName, ['_' prop.name], '');
parent = classprops(parentName);
if ~parent.required
dependent = [dependent {propertyName}];
end
end

if strcmp(namespace.name, 'hdmf_common') ...
&& strcmp(name, 'VectorData') ...
&& any(strcmp(prop.name, {'unit', 'sampling_rate', 'resolution'}))
hidden{end+1} = propertyName;
end
end
end
end
nonInherited = setdiff(allprops, inherited);
readonly = intersect(readonly, nonInherited);
required = intersect(required, nonInherited);
optional = intersect(optional, nonInherited);
nonInherited = setdiff(allProperties, inherited);
readonly = intersect(readonly, nonInherited);
exclusivePropertyGroups = union(readonly, hidden);
required = setdiff(intersect(required, nonInherited), exclusivePropertyGroups);
optional = setdiff(intersect(optional, nonInherited), exclusivePropertyGroups);

%% CLASSDEF
if length(processed) <= 1
depnm = 'types.untyped.MetaClass'; %WRITE
else
parentName = processed(2).type; %WRITE
depnm = namespace.getFullClassName(parentName);
end
%% CLASSDEF
if length(processed) <= 1
depnm = 'types.untyped.MetaClass'; %WRITE
else
parentName = processed(2).type; %WRITE
depnm = namespace.getFullClassName(parentName);
end

if isa(processed, 'file.Group')
classTag = 'types.untyped.GroupClass';
else
classTag = 'types.untyped.DatasetClass';
end
if isa(processed, 'file.Group')
classTag = 'types.untyped.GroupClass';
else
classTag = 'types.untyped.DatasetClass';
end

%% return classfile string
classDef = [...
'classdef ' name ' < ' depnm ' & ' classTag newline... %header, dependencies
'% ' upper(name) ' ' class.doc]; %name, docstr
propgroups = {...
@()file.fillProps(classprops, readonly, 'PropertyAttributes', 'SetAccess=protected')...
@()file.fillProps(classprops, setdiff(required, readonly), 'IsRequired', true)...
@()file.fillProps(classprops, setdiff(optional, readonly))...
};
docsep = {...
'% READONLY PROPERTIES'...
'% REQUIRED PROPERTIES'...
'% OPTIONAL PROPERTIES'...
};
propsDef = '';
for i=1:length(propgroups)
pg = propgroups{i};
pdef = pg();
if ~isempty(pdef)
propsDef = strjoin({propsDef docsep{i} pdef}, newline);
%% return classfile string
classDefinitionHeader = [...
'classdef ' name ' < ' depnm ' & ' classTag newline... %header, dependencies
'% ' upper(name) ' ' class.doc]; %name, docstr
hiddenAndReadonly = intersect(hidden, readonly);
hidden = setdiff(hidden, hiddenAndReadonly);
readonly = setdiff(readonly, hiddenAndReadonly);
PropertyGroups = struct(...
'Function', {...
@()file.fillProps(classprops, hiddenAndReadonly, 'PropertyAttributes', 'Hidden, SetAccess = protected') ...
, @()file.fillProps(classprops, hidden, 'PropertyAttributes', 'Hidden') ...
, @()file.fillProps(classprops, readonly, 'PropertyAttributes', 'SetAccess = protected') ...
, @()file.fillProps(classprops, required, 'IsRequired', true) ...
, @()file.fillProps(classprops, optional)...
} ...
, 'Separator', { ...
'% HIDDEN READONLY PROPERTIES' ...
, '% HIDDEN PROPERTIES' ...
, '% READONLY PROPERTIES' ...
, '% REQUIRED PROPERTIES' ...
, '% OPTIONAL PROPERTIES' ...
} ...
);
fullPropertyDefinition = '';
for iGroup = 1:length(PropertyGroups)
Group = PropertyGroups(iGroup);
propertyDefinitionBody = Group.Function();
if isempty(propertyDefinitionBody)
continue;
end
fullPropertyDefinition = strjoin({...
fullPropertyDefinition ...
, Group.Separator ...
, propertyDefinitionBody ...
}, newline);
end
end

constructorBody = file.fillConstructor(...
name,...
depnm,...
defaults,... %all defaults, regardless of inheritance
classprops,...
namespace);
setterFcns = file.fillSetters(setdiff(nonInherited, readonly));
validatorFcns = file.fillValidators(allprops, classprops, namespace);
exporterFcns = file.fillExport(nonInherited, class, depnm);
methodBody = strjoin({constructorBody...
'%% SETTERS' setterFcns...
'%% VALIDATORS' validatorFcns...
'%% EXPORT' exporterFcns}, newline);
constructorBody = file.fillConstructor(...
name,...
depnm,...
defaults,... %all defaults, regardless of inheritance
classprops,...
namespace);
setterFcns = file.fillSetters(setdiff(nonInherited, union(readonly, hiddenAndReadonly)));
validatorFcns = file.fillValidators(allProperties, classprops, namespace);
exporterFcns = file.fillExport(nonInherited, class, depnm);
methodBody = strjoin({constructorBody...
'%% SETTERS' setterFcns...
'%% VALIDATORS' validatorFcns...
'%% EXPORT' exporterFcns}, newline);

if strcmp(name, 'DynamicTable')
methodBody = strjoin({methodBody, '%% TABLE METHODS', file.fillDynamicTableMethods()}, newline);
end
if strcmp(name, 'DynamicTable')
methodBody = strjoin({methodBody, '%% TABLE METHODS', file.fillDynamicTableMethods()}, newline);
end

fullMethodBody = strjoin({'methods' ...
file.addSpaces(methodBody, 4) 'end'}, newline);
template = strjoin({classDef propsDef fullMethodBody 'end'}, ...
[newline newline]);
fullMethodBody = strjoin({'methods' ...
file.addSpaces(methodBody, 4) 'end'}, newline);
template = strjoin({classDefinitionHeader fullPropertyDefinition fullMethodBody 'end'}, ...
[newline newline]);
end

Loading

0 comments on commit 43cdffb

Please sign in to comment.