Skip to content

Commit

Permalink
Merge branch 'master' into 607-fix-namespace-embedding-in-file
Browse files Browse the repository at this point in the history
  • Loading branch information
ehennestad authored Dec 12, 2024
2 parents a467c46 + bb9acbf commit eeb2006
Show file tree
Hide file tree
Showing 443 changed files with 16,314 additions and 33,689 deletions.
26 changes: 26 additions & 0 deletions +file/+internal/getRequiredPropertyNames.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function requiredPropertyNames = getRequiredPropertyNames(classprops)
% getRequiredPropertyNames - Get name of required properties from props info

allProperties = keys(classprops);
requiredPropertyNames = {};

for iProp = 1:length(allProperties)

propertyName = allProperties{iProp};
prop = classprops(propertyName);

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

if isRequired || all(isPropertyRequired)
requiredPropertyNames = [requiredPropertyNames {propertyName}]; %#ok<AGROW>
end
end
end
18 changes: 18 additions & 0 deletions +file/+internal/mergeProps.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
function allProps = mergeProps(props, superClassProps)
% merge_props - Merge maps containing props info for class and it's superclasses

allPropsCell = [{props}, superClassProps];
allProps = containers.Map();

% Start from most remote ancestor and work towards current class.
% Important to go in this order because subclasses can override
% properties, and we need to keep the property definition for the superclass
% that is closest to the current class or the property definition for the
% class itself in the final map
for i = numel(allPropsCell):-1:1
superPropNames = allPropsCell{i}.keys;
for jProp = 1:numel(superPropNames)
allProps(superPropNames{jProp}) = allPropsCell{i}(superPropNames{jProp});
end
end
end
2 changes: 1 addition & 1 deletion +file/Attribute.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
else
obj.value = [];
obj.readonly = false;
end
end

if isKey(source, 'dims')
obj.dimnames = source('dims');
Expand Down
19 changes: 19 additions & 0 deletions +file/Dataset.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
dtype;
isConstrainedSet;
required;
value;
readonly; %determines whether value can be changed or not
scalar;
shape;
dimnames;
Expand All @@ -22,12 +24,15 @@
obj.type = '';
obj.dtype = 'any';
obj.required = true;
obj.value = [];
obj.readonly = false;
obj.scalar = true;
obj.definesType = false;

obj.shape = {};
obj.dimnames = {};
obj.attributes = [];


if nargin < 1
return;
Expand All @@ -42,6 +47,20 @@
if isKey(source, nameKey)
obj.name = source(nameKey);
end

% Todo: same as for attribute, should consolidate
valueKey = 'value';
defaultKey = 'default_value';
if isKey(source, defaultKey)
obj.value = source(defaultKey);
obj.readonly = false;
elseif isKey(source, valueKey)
obj.value = source(valueKey);
obj.readonly = true;
else
obj.value = [];
obj.readonly = false;
end

typeKeys = {'neurodata_type_def', 'data_type_def'};
parentKeys = {'neurodata_type_inc', 'data_type_inc'};
Expand Down
32 changes: 26 additions & 6 deletions +file/fillClass.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function template = fillClass(name, namespace, processed, classprops, inherited)
function template = fillClass(name, namespace, processed, classprops, inherited, superClassProps)
%name is the name of the scheme
%namespace is the namespace context for this class

Expand Down Expand Up @@ -33,16 +33,22 @@
optional = [optional {propertyName}];
end

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

if ~isempty(prop.value)
defaults = [defaults {propertyName}];
if isa(prop, 'file.Attribute')
defaults = [defaults {propertyName}];
else % file.Dataset
if isRequired || all(isPropertyRequired)
defaults = [defaults {propertyName}];
end
end
end

if ~isempty(prop.dependent)
if isa(prop, 'file.Attribute') && ~isempty(prop.dependent)
%extract prefix
parentName = strrep(propertyName, ['_' prop.name], '');
parent = classprops(parentName);
Expand Down Expand Up @@ -81,7 +87,20 @@
%% return classfile string
classDefinitionHeader = [...
'classdef ' name ' < ' depnm ' & ' classTag newline... %header, dependencies
'% ' upper(name) ' ' class.doc]; %name, docstr
'% ' upper(name) ' - ' class.doc]; %name, docstr

allClassProps = file.internal.mergeProps(classprops, superClassProps);
allRequiredPropertyNames = file.internal.getRequiredPropertyNames(allClassProps);
if isempty(allRequiredPropertyNames)
allRequiredPropertyNames = {'None'};
end

% Add list of required properties in class docstring
classDefinitionHeader = [classDefinitionHeader, newline...
'%', newline, ...
'% Required Properties:', newline, ...
sprintf('%% %s', strjoin(allRequiredPropertyNames, ', '))];

hiddenAndReadonly = intersect(hidden, readonly);
hidden = setdiff(hidden, hiddenAndReadonly);
readonly = setdiff(readonly, hiddenAndReadonly);
Expand Down Expand Up @@ -120,7 +139,8 @@
depnm,...
defaults,... %all defaults, regardless of inheritance
classprops,...
namespace);
namespace, ...
superClassProps);
setterFcns = file.fillSetters(setdiff(nonInherited, union(readonly, hiddenAndReadonly)));
validatorFcns = file.fillValidators(allProperties, classprops, namespace, namespace.getFullClassName(name), inherited);
exporterFcns = file.fillExport(nonInherited, class, depnm);
Expand Down
122 changes: 119 additions & 3 deletions +file/fillConstructor.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
function functionString = fillConstructor(name, parentname, defaults, props, namespace)
function functionString = fillConstructor(name, parentname, defaults, props, namespace, superClassProps)
caps = upper(name);
functionBody = ['% ' caps ' Constructor for ' name];
functionBody = ['% ' caps ' - Constructor for ' name];

docString = fillConstructorDocString(name, props, namespace, superClassProps);
if ~isempty(docString)
functionBody = [functionBody newline() docString];
end

bodyString = fillBody(parentname, defaults, props, namespace);
if ~isempty(bodyString)
Expand All @@ -23,7 +28,6 @@
['function obj = ' name '(varargin)']...
file.addSpaces(functionBody, 4)...
'end'}, newline());

end

function bodystr = fillBody(parentName, defaults, props, namespace)
Expand Down Expand Up @@ -198,4 +202,116 @@
' types.util.dynamictable.checkConfig(obj);', ...
'end',...
}, newline);
end

function docString = fillConstructorDocString(name, props, namespace, superClassProps)

classVarName = name; classVarName(1) = lower(classVarName(1));
fullClassName = sprintf('types.%s.%s', namespace.name, name);
fullClassNameUpper = sprintf('types.%s.%s', namespace.name, upper(name));

props = file.internal.mergeProps(props, superClassProps);
names = props.keys();

docString = [...
"%", ...
"% Syntax:", ...
sprintf("%% %s = %s() creates a %s object with unset property values.", classVarName, fullClassNameUpper, name), ...
"%", ...
];

if ~isempty(names)
docString = [docString, ...
sprintf("%% %s = %s(Name, Value) creates a %s object where one or more property values are specified using name-value pairs.", classVarName, fullClassNameUpper, name), ...
"%", ...
"% Input Arguments (Name-Value Arguments):", ...
];
end

for i = 1:numel(names)
propName = names{i};
thisProp = props(propName);
try
if isprop(thisProp, 'readonly') && thisProp.readonly
continue
end
catch
% pass
end

valueType = getTypeStr(thisProp);
try
description = thisProp.doc;
catch
description = 'No description';
end

docString = [docString, ...
sprintf("%% - %s (%s) - %s", propName, valueType, description), ...
"%"]; %#ok<AGROW>
end

docString = [docString, ...
"% Output Arguments:", ...
sprintf("%% - %s (%s) - A %s object", classVarName, fullClassName, name), ...
""
];

docString = char( strjoin(docString, newline) );
end

% Todo: Mostly duplicate code from file.fillProps. Should consolidate
function typeStr = getTypeStr(prop)
if ischar(prop)
typeStr = prop;
elseif isstruct(prop)
columnNames = fieldnames(prop);
columnDocStr = cell(size(columnNames));
for i=1:length(columnNames)
name = columnNames{i};
columnDocStr{i} = getTypeStr(prop.(name));
end
typeStr = ['Table with columns: (', strjoin(columnDocStr, ', '), ')'];
elseif isa(prop, 'file.Attribute')
if isa(prop.dtype, 'containers.Map')
assertValidRefType(prop.dtype('reftype'))
typeStr = sprintf('%s reference to %s', capitalize(prop.dtype('reftype')), prop.dtype('target_type'));
else
typeStr = prop.dtype;
end
elseif isa(prop, 'containers.Map')
assertValidRefType(prop('reftype'))
typeStr = sprintf('%s reference to %s', capitalize(prop('reftype')), prop('target_type'));
elseif isa(prop, 'file.interface.HasProps')
typeStrCell = cell(size(prop));
for iProp = 1:length(typeStrCell)
anonProp = prop(iProp);
if isa(anonProp, 'file.Dataset') && isempty(anonProp.type)
typeStrCell{iProp} = getTypeStr(anonProp.dtype);
elseif isempty(anonProp.type)
typeStrCell{iProp} = 'types.untyped.Set';
else
typeStrCell{iProp} = anonProp.type;
end
end
typeStr = strjoin(typeStrCell, '|');
else
typeStr = prop.type;
end
end

function assertValidRefType(referenceType)
arguments
referenceType (1,1) string
end
assert( ismember(referenceType, ["region", "object"]), ...
'NWB:ClassGenerator:InvalidRefType', ...
'Invalid reftype found while filling description for class properties.')
end

function word = capitalize(word)
arguments
word (1,:) char
end
word(1) = upper(word(1));
end
31 changes: 29 additions & 2 deletions +file/fillValidators.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
nm = propnames{i};
prop = props(nm);


if isa(prop, 'file.Attribute') && prop.readonly && ~isempty(prop.value)
if (isa(prop, 'file.Attribute') || isa(prop, 'file.Dataset')) ...
&& prop.readonly && ~isempty(prop.value)
% Need to add a validator for inherited and readonly properties. In
% the superclass these properties might not be read only and due to
% inheritance its not possible to change property attributes
Expand All @@ -14,6 +14,8 @@
else
continue
end
elseif isa(prop, 'file.Link')
validationBody = fillLinkValidation(nm, prop, namespacereg);
else
if startsWith(class(prop), 'file.')
validationBody = fillUnitValidation(nm, prop, namespacereg);
Expand Down Expand Up @@ -173,6 +175,31 @@
end
end

function validationStr = fillLinkValidation(name, prop, namespacereg)
fullName = namespacereg.getFullClassName(prop.type);

% Create a validation function body that 1) checks (validates) the
% target if the input is a SoftLink type, otherwise 2) checks (validates)
% if the expected (target) type is provided. If the validation passes
% and the value is not empty, it is wrapped in a SoftLink.

validationStr = sprintf([ ...
'if isa(val, ''types.untyped.SoftLink'')\n', ...
' if isprop(val, ''target'')\n', ...
' types.util.checkDtype(''%s'', ''%s'', val.target);\n', ...
' end\n', ...
'else\n', ...
' %s\n', ...
' if ~isempty(val)\n', ...
' val = types.untyped.SoftLink(val);\n', ...
' end\n', ...
'end' ...
], ...
name, fullName, ...
fillDtypeValidation(name, fullName) ...
);
end

function fdvstr = fillDimensionValidation(type, shape)
if strcmp(type, 'any')
fdvstr = '';
Expand Down
7 changes: 6 additions & 1 deletion +file/writeNamespace.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ function writeNamespace(namespaceName, saveDir)
[processed, classprops, inherited] = file.processClass(className, Namespace, pregenerated);

if ~isempty(processed)
superClassProps = cell(1, numel(processed)-1);
for iSuper = 2:numel(processed)
[~, superClassProps{iSuper-1}, ~] = file.processClass(processed(iSuper).type, Namespace, pregenerated);
end

fid = fopen(fullfile(classFileDir, [className '.m']), 'W');
% Create cleanup object to close to file in case the write operation fails.
fileCleanupObj = onCleanup(@(id) fclose(fid));
fwrite(fid, file.fillClass(className, Namespace, processed, ...
classprops, inherited), 'char');
classprops, inherited, superClassProps), 'char');
else
% pass
end
Expand Down
16 changes: 16 additions & 0 deletions +matnwb/+utility/isNeurodataType.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
function tf = isNeurodataType(value)
% isNeurodataType - Check if a value / object is a neurodata type.
%
% tf = matnwb.utility.isNeurodataType(value) returns true if the value
% is an object of a class representing a neurodata type of the NWB Format.
% If the input is a string representing the class name of a neurodata
% type, the function will also return true.

tf = false;
if isa(value, 'char') || isa(value, 'string')
tf = matnwb.utility.isNeurodataTypeClassName(value);
elseif isa(value, 'types.untyped.MetaClass')
className = class(value);
tf = matnwb.utility.isNeurodataTypeClassName(className);
end
end
Loading

0 comments on commit eeb2006

Please sign in to comment.