Skip to content

Commit 268eae2

Browse files
committed
Merge branch 'main' into feature/create-html-workflow
2 parents ddbfa90 + 1db12e4 commit 268eae2

File tree

692 files changed

+32383
-40276
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

692 files changed

+32383
-40276
lines changed

+file/+interface/HasQuantity.m

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
classdef HasQuantity
2+
% HasQuantity - Provide methods for parsing quantity specification value
3+
4+
% properties
5+
% QuantityKey = 'quantity'
6+
% end
7+
8+
methods (Static, Access = protected)
9+
function isRequired = isRequired(source)
10+
if isKey(source, 'quantity')
11+
quantity = source('quantity');
12+
file.interface.HasQuantity.validateQuantity(quantity)
13+
14+
if ischar(quantity)
15+
switch quantity
16+
case {'?', 'zero_or_one'}
17+
isRequired = false;
18+
case {'*', 'zero_or_many'}
19+
isRequired = false;
20+
case {'+', 'one_or_many'}
21+
isRequired = true;
22+
end
23+
elseif isnumeric(quantity)
24+
isRequired = quantity >= 1;
25+
end
26+
else
27+
isRequired = true; % Default
28+
end
29+
end
30+
31+
function isScalar = isScalar(source)
32+
if isKey(source, 'quantity')
33+
quantity = source('quantity');
34+
file.interface.HasQuantity.validateQuantity(quantity)
35+
36+
if ischar(quantity)
37+
switch quantity
38+
case {'?', 'zero_or_one'}
39+
isScalar = true;
40+
case {'*', 'zero_or_many'}
41+
isScalar = false;
42+
case {'+', 'one_or_many'}
43+
isScalar = false;
44+
end
45+
elseif isnumeric(quantity)
46+
if quantity == 1
47+
isScalar = true;
48+
else
49+
isScalar = false;
50+
end
51+
end
52+
else
53+
isScalar = true; % Default
54+
end
55+
end
56+
end
57+
58+
methods (Static, Access = private)
59+
function validateQuantity(quantity)
60+
% validateQuantity - Validate quantity specification value
61+
if ischar(quantity)
62+
validQuantities = [ ...
63+
"?", "zero_or_one", ...
64+
"*", "zero_or_many", ...
65+
"+", "one_or_many" ...
66+
];
67+
if ~any(strcmp(validQuantities, quantity))
68+
validQuantitiesStr = strjoin(" " + validQuantities, newline);
69+
ME = MException('NWB:Schema:UnsupportedQuantity', ...
70+
['Quantity is "%s", but expected quantity to be one of the ' ...
71+
'following values:\n%s\n'], quantity, validQuantitiesStr);
72+
throwAsCaller(ME)
73+
end
74+
75+
elseif isnumeric(quantity)
76+
assert( mod(quantity,1) == 0 && quantity > 0, ...
77+
'NWB:Schema:UnsupportedQuantity', ...
78+
'Expected quantity to positive integer')
79+
else
80+
ME = MException('NWB:Schema:UnsupportedQuantity', ...
81+
'Expected quantity to be text or numeric.');
82+
throwAsCaller(ME)
83+
end
84+
end
85+
end
86+
end

+file/+internal/isPropertyHidden.m

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
function result = isPropertyHidden(propertyInfo, className, namespace)
2+
% isPropertyHidden - Determine if a property is hidden
3+
4+
if isa(propertyInfo, 'file.Attribute') || isa(propertyInfo, 'file.Dataset')
5+
if strcmp(namespace.name, 'hdmf_common') ...
6+
&& strcmp(className, 'VectorData') ...
7+
&& any(strcmp(propertyInfo.name, {'unit', 'sampling_rate', 'resolution'}))
8+
result = true;
9+
else
10+
result = false;
11+
end
12+
else
13+
result = false;
14+
end
15+
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function result = isPropertyReadonly(propertyInfo)
2+
% isPropertyReadonly - Determine if a property is read-only
3+
if isa(propertyInfo, 'file.Attribute') || isa(propertyInfo, 'file.Dataset')
4+
result = propertyInfo.readonly;
5+
else
6+
result = false;
7+
end
8+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
function result = isPropertyRequired(propInfo, fullPropertyName, allClassprops)
2+
% isPropertyRequired - Determine if a property is required
3+
4+
if ischar(propInfo) || isa(propInfo, 'containers.Map') || isstruct(propInfo)
5+
result = true;
6+
elseif isa(propInfo, 'file.interface.HasProps')
7+
isSubPropertyRequired = false(size(propInfo));
8+
for iSubProp = 1:length(propInfo)
9+
p = propInfo(iSubProp);
10+
isSubPropertyRequired(iSubProp) = p.required;
11+
end
12+
result = all(isSubPropertyRequired);
13+
elseif isa(propInfo, 'file.Attribute')
14+
if isempty(propInfo.dependent)
15+
result = propInfo.required;
16+
else
17+
result = resolveRequiredForDependentProp(propInfo, fullPropertyName, allClassprops);
18+
end
19+
elseif isa(propInfo, 'file.Link')
20+
result = propInfo.required;
21+
else
22+
result = false;
23+
end
24+
end
25+
26+
function tf = resolveRequiredForDependentProp(propInfo, propertyName, allProps)
27+
% resolveRequiredForDependentProp - If a dependent property is required,
28+
% whether it is required on object level also depends on whether it's parent
29+
% property is required.
30+
if ~propInfo.required
31+
tf = false;
32+
else % Check if parent is required
33+
parentName = strrep(propertyName, ['_' propInfo.name], '');
34+
parentInfo = allProps(parentName);
35+
tf = parentInfo.required;
36+
end
37+
end

+file/+internal/mergeProps.m

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function allProps = mergeProps(props, superClassProps)
2+
% merge_props - Merge maps containing props info for class and it's superclasses
3+
4+
allPropsCell = [{props}, superClassProps];
5+
allProps = containers.Map();
6+
7+
% Start from most remote ancestor and work towards current class.
8+
% Important to go in this order because subclasses can override
9+
% properties, and we need to keep the property definition for the superclass
10+
% that is closest to the current class or the property definition for the
11+
% class itself in the final map
12+
for i = numel(allPropsCell):-1:1
13+
superPropNames = allPropsCell{i}.keys;
14+
for jProp = 1:numel(superPropNames)
15+
allProps(superPropNames{jProp}) = allPropsCell{i}(superPropNames{jProp});
16+
end
17+
end
18+
end

+file/Attribute.m

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
readonly; %determines whether value can be changed or not
88
dtype; %type of value
99
dependent; %set externally. If the attribute is actually dependent on an untyped dataset/group
10+
dependent_fullname; %set externally. This is the full name, including names of potential parent groups separated by underscore. A value will only be present if it would differ from dependent.
1011
scalar; %if the value is scalar or an array
1112
dimnames;
1213
shape;
@@ -22,6 +23,7 @@
2223
obj.readonly = false;
2324
obj.dtype = '';
2425
obj.dependent = '';
26+
obj.dependent_fullname = '';
2527
obj.scalar = true;
2628
obj.shape = {};
2729
obj.dimnames = {};
@@ -37,18 +39,20 @@
3739
obj.required = source(requiredKey);
3840
end
3941

42+
% Use either 'value' or 'default_value' (not both).
43+
% If both are present, 'value' takes precedence.
4044
valueKey = 'value';
4145
defaultKey = 'default_value';
42-
if isKey(source, defaultKey)
43-
obj.value = source(defaultKey);
44-
obj.readonly = false;
45-
elseif isKey(source, valueKey)
46+
if isKey(source, valueKey)
4647
obj.value = source(valueKey);
4748
obj.readonly = true;
49+
elseif isKey(source, defaultKey)
50+
obj.value = source(defaultKey);
51+
obj.readonly = false;
4852
else
4953
obj.value = [];
5054
obj.readonly = false;
51-
end
55+
end
5256

5357
if isKey(source, 'dims')
5458
obj.dimnames = source('dims');

+file/Dataset.m

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
classdef Dataset < file.interface.HasProps
1+
classdef Dataset < file.interface.HasProps & file.interface.HasQuantity
22
properties
33
name;
44
doc;
55
type;
66
dtype;
77
isConstrainedSet;
88
required;
9+
value;
10+
readonly; %determines whether value can be changed or not
911
scalar;
1012
shape;
1113
dimnames;
@@ -22,12 +24,15 @@
2224
obj.type = '';
2325
obj.dtype = 'any';
2426
obj.required = true;
27+
obj.value = [];
28+
obj.readonly = false;
2529
obj.scalar = true;
2630
obj.definesType = false;
2731

2832
obj.shape = {};
2933
obj.dimnames = {};
3034
obj.attributes = [];
35+
3136

3237
if nargin < 1
3338
return;
@@ -42,6 +47,22 @@
4247
if isKey(source, nameKey)
4348
obj.name = source(nameKey);
4449
end
50+
51+
% Todo: same as for attribute, should consolidate
52+
% Use either 'value' or 'default_value' (not both).
53+
% If both are present, 'value' takes precedence.
54+
valueKey = 'value';
55+
defaultKey = 'default_value';
56+
if isKey(source, valueKey)
57+
obj.value = source(valueKey);
58+
obj.readonly = true;
59+
elseif isKey(source, defaultKey)
60+
obj.value = source(defaultKey);
61+
obj.readonly = false;
62+
else
63+
obj.value = [];
64+
obj.readonly = false;
65+
end
4566

4667
typeKeys = {'neurodata_type_def', 'data_type_def'};
4768
parentKeys = {'neurodata_type_inc', 'data_type_inc'};
@@ -59,19 +80,13 @@
5980
obj.dtype = file.mapType(source(dataTypeKey));
6081
end
6182

62-
if isKey(source, 'quantity')
63-
quantity = source('quantity');
64-
switch quantity
65-
case '?'
66-
obj.required = false;
67-
obj.scalar = true;
68-
case '*'
69-
obj.required = false;
70-
obj.scalar = false;
71-
case '+'
72-
obj.required = true;
73-
obj.scalar = false;
74-
end
83+
% If a value key is specified, the resulting property is a
84+
% constant (and by definition required). Therefore we will only
85+
% update the required flag based on the `quantity` key when the
86+
% `value` key is missing.
87+
if ~isKey(source, valueKey) && isKey(source, 'quantity')
88+
obj.required = obj.isRequired(source);
89+
obj.scalar = obj.isScalar(source);
7590
end
7691

7792
obj.isConstrainedSet = ~isempty(obj.type) && ~obj.scalar;

+file/Group.m

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
classdef Group < file.interface.HasProps
1+
classdef Group < file.interface.HasProps & file.interface.HasQuantity
22
properties
33
doc;
44
name;
@@ -65,18 +65,8 @@
6565
end
6666

6767
if isKey(source, 'quantity')
68-
quantity = source('quantity');
69-
switch quantity
70-
case '?'
71-
obj.required = false;
72-
obj.scalar = true;
73-
case '*'
74-
obj.required = false;
75-
obj.scalar = false;
76-
case '+'
77-
obj.required = true;
78-
obj.scalar = false;
79-
end
68+
obj.required = obj.isRequired(source);
69+
obj.scalar = obj.isScalar(source);
8070
end
8171

8272
obj.isConstrainedSet = ~obj.scalar && ~isempty(obj.type);
@@ -144,7 +134,7 @@
144134
PropertyMap = containers.Map;
145135
%typed + constrained
146136
%should never happen
147-
137+
148138
if obj.isConstrainedSet && ~obj.definesType
149139
error('NWB:Group:UnsupportedOperation', ...
150140
'The method `getProps` should not be called on a constrained dataset.');
@@ -245,6 +235,17 @@
245235
end
246236
PropertyMap(groupName) = [SetType, Descendant];
247237
else
238+
if isa(Descendant, 'file.Attribute')
239+
% Ad hoc convenience step: We need the parent's
240+
% expanded property name when populating the
241+
% type's class definition. Here, we construct a full
242+
% name from groupName + descendantName, then remove
243+
% the descendantName (and its underscore) and
244+
% add the result to the attribute object for
245+
% easy retrieval when needed.
246+
fullName = [groupName, '_', descendantName];
247+
Descendant.dependent_fullname = strrep(fullName, ['_', Descendant.name], '');
248+
end
248249
PropertyMap([groupName, '_', descendantName]) = Descendant;
249250
end
250251
end

+file/Link.m

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
classdef Link
1+
classdef Link < file.interface.HasQuantity
22
properties (SetAccess = private)
33
doc;
44
name;
@@ -19,12 +19,7 @@
1919
obj.doc = source('doc');
2020
obj.name = source('name');
2121
obj.type = source('target_type');
22-
23-
quantityKey = 'quantity';
24-
if isKey(source, quantityKey)
25-
quantity = source(quantityKey);
26-
obj.required = strcmp(quantity, '+');
27-
end
22+
obj.required = obj.isRequired(source);
2823
end
2924
end
3025
end

0 commit comments

Comments
 (0)