-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix Default Subclass Properties in checkUnset (#475)
* auto-indent fillProps * auto-indent fillConstructor * make file objects non-handle * fix constrained processing for constructors Remove dependency on defined class names and instead operate on all properties. * Support Set appending when parsing constrained Originally was all-or-nothing. Now detects if a Set already exists and appends instead when that occurs. --------- Co-authored-by: Lawrence Niu <[email protected]>
- Loading branch information
1 parent
9400310
commit 16a5411
Showing
8 changed files
with
284 additions
and
254 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
classdef Attribute < handle | ||
classdef Attribute | ||
properties | ||
name; %attribute key | ||
doc; %doc string | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
classdef Dataset < handle | ||
classdef Dataset | ||
properties | ||
name; | ||
doc; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
classdef Group < handle | ||
classdef Group | ||
properties | ||
doc; | ||
name; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,175 +1,176 @@ | ||
function fcstr = fillConstructor(name, parentname, defaults, propnames, props, namespace) | ||
caps = upper(name); | ||
fcnbody = ['% ' caps ' Constructor for ' name]; | ||
function fcstr = fillConstructor(name, parentname, defaults, props, namespace) | ||
caps = upper(name); | ||
fcnbody = ['% ' caps ' Constructor for ' name]; | ||
|
||
txt = fillBody(parentname, defaults, propnames, props, namespace); | ||
if ~isempty(txt) | ||
fcnbody = [fcnbody newline txt]; | ||
end | ||
txt = fillBody(parentname, defaults, props, namespace); | ||
if ~isempty(txt) | ||
fcnbody = [fcnbody newline txt]; | ||
end | ||
|
||
fcnbody = strjoin({fcnbody,... | ||
sprintf('if strcmp(class(obj), ''%s'')', namespace.getFullClassName(name)),... | ||
' types.util.checkUnset(obj, unique(varargin(1:2:end)));',... | ||
'end'}, newline); | ||
fcnbody = strjoin({fcnbody,... | ||
sprintf('if strcmp(class(obj), ''%s'')', namespace.getFullClassName(name)),... | ||
' types.util.checkUnset(obj, unique(varargin(1:2:end)));',... | ||
'end'}, newline); | ||
|
||
% insert check for DynamicTable class and child classes | ||
txt = fillCheck(name, namespace); | ||
if ~isempty(txt) | ||
fcnbody = [fcnbody newline txt]; | ||
end | ||
% insert check for DynamicTable class and child classes | ||
txt = fillCheck(name, namespace); | ||
if ~isempty(txt) | ||
fcnbody = [fcnbody newline txt]; | ||
end | ||
|
||
fcstr = strjoin({... | ||
['function obj = ' name '(varargin)']... | ||
file.addSpaces(fcnbody, 4)... | ||
'end'}, newline); | ||
fcstr = strjoin({... | ||
['function obj = ' name '(varargin)']... | ||
file.addSpaces(fcnbody, 4)... | ||
'end'}, newline); | ||
|
||
end | ||
|
||
function bodystr = fillBody(pname, defaults, names, props, namespace) | ||
if isempty(defaults) | ||
bodystr = ''; | ||
else | ||
overridemap = containers.Map; | ||
for i=1:length(defaults) | ||
nm = defaults{i}; | ||
if strcmp(props(nm).dtype, 'char') | ||
overridemap(nm) = ['''' props(nm).value '''']; | ||
else | ||
overridemap(nm) =... | ||
sprintf('types.util.correctType(%d, ''%s'')',... | ||
props(nm).value,... | ||
props(nm).dtype); | ||
function bodystr = fillBody(pname, defaults, props, namespace) | ||
if isempty(defaults) | ||
bodystr = ''; | ||
else | ||
overridemap = containers.Map; | ||
for i=1:length(defaults) | ||
nm = defaults{i}; | ||
if strcmp(props(nm).dtype, 'char') | ||
overridemap(nm) = ['''' props(nm).value '''']; | ||
else | ||
overridemap(nm) =... | ||
sprintf('types.util.correctType(%d, ''%s'')',... | ||
props(nm).value,... | ||
props(nm).dtype); | ||
end | ||
end | ||
kwargs = io.map2kwargs(overridemap); | ||
%add surrounding quotes to kwargs so misc.cellPrettyPrint can print them correctly | ||
kwargs(1:2:end) = strcat('''', kwargs(1:2:end), ''''); | ||
bodystr = ['varargin = [{' misc.cellPrettyPrint(kwargs) '} varargin];' newline]; | ||
end | ||
kwargs = io.map2kwargs(overridemap); | ||
%add surrounding quotes to kwargs so misc.cellPrettyPrint can print them correctly | ||
kwargs(1:2:end) = strcat('''', kwargs(1:2:end), ''''); | ||
bodystr = ['varargin = [{' misc.cellPrettyPrint(kwargs) '} varargin];' newline]; | ||
end | ||
bodystr = [bodystr 'obj = obj@' pname '(varargin{:});']; | ||
bodystr = [bodystr 'obj = obj@' pname '(varargin{:});']; | ||
|
||
if isempty(names) | ||
return; | ||
end | ||
% if there's a root object that is a constrained set, let it be hoistable from dynamic arguments | ||
dynamicConstrained = false(size(names)); | ||
anon = false(size(names)); | ||
isattr = false(size(names)); | ||
typenames = repmat({''}, size(names)); | ||
varnames = repmat({''}, size(names)); | ||
for i=1:length(names) | ||
nm = names{i}; | ||
prop = props(nm); | ||
|
||
if isa(prop, 'file.Group') || isa(prop, 'file.Dataset') | ||
dynamicConstrained(i) = prop.isConstrainedSet && strcmpi(nm, prop.type); | ||
anon(i) = ~prop.isConstrainedSet && isempty(prop.name); | ||
|
||
if ~isempty(prop.type) | ||
varnames{i} = nm; | ||
try | ||
typenames{i} = namespace.getFullClassName(prop.type); | ||
catch ME | ||
if ~strcmp(ME.identifier, 'NWB:Scheme:Namespace:NotFound') | ||
rethrow(ME); | ||
names = keys(props); | ||
if isempty(names) | ||
return; | ||
end | ||
% if there's a root object that is a constrained set, let it be hoistable from dynamic arguments | ||
dynamicConstrained = false(size(names)); | ||
anon = false(size(names)); | ||
isattr = false(size(names)); | ||
typenames = repmat({''}, size(names)); | ||
varnames = repmat({''}, size(names)); | ||
for i=1:length(names) | ||
nm = names{i}; | ||
prop = props(nm); | ||
|
||
if isa(prop, 'file.Group') || isa(prop, 'file.Dataset') | ||
dynamicConstrained(i) = prop.isConstrainedSet && strcmpi(nm, prop.type); | ||
anon(i) = ~prop.isConstrainedSet && isempty(prop.name); | ||
|
||
if ~isempty(prop.type) | ||
varnames{i} = nm; | ||
try | ||
typenames{i} = namespace.getFullClassName(prop.type); | ||
catch ME | ||
if ~strcmp(ME.identifier, 'NWB:Scheme:Namespace:NotFound') | ||
rethrow(ME); | ||
end | ||
end | ||
end | ||
elseif isa(prop, 'file.Attribute') | ||
isattr(i) = true; | ||
end | ||
elseif isa(prop, 'file.Attribute') | ||
isattr(i) = true; | ||
end | ||
end | ||
|
||
%warn for missing namespaces/property types | ||
warnmsg = ['`' pname '`''s constructor is unable to check for type `%1$s` ' ... | ||
'because its namespace or type specifier could not be found. Try generating ' ... | ||
'the namespace or class definition for type `%1$s` or fix its schema.']; | ||
%warn for missing namespaces/property types | ||
warnmsg = ['`' pname '`''s constructor is unable to check for type `%1$s` ' ... | ||
'because its namespace or type specifier could not be found. Try generating ' ... | ||
'the namespace or class definition for type `%1$s` or fix its schema.']; | ||
|
||
invalid = cellfun('isempty', typenames); | ||
invalidWarn = invalid & (dynamicConstrained | anon) & ~isattr; | ||
invalidVars = varnames(invalidWarn); | ||
for i=1:length(invalidVars) | ||
warning(warnmsg, invalidVars{i}); | ||
end | ||
varnames = lower(varnames); | ||
|
||
%we delete the entry in varargin such that any conflicts do not show up in inputParser | ||
deleteFromVars = 'varargin(ivarargin) = [];'; | ||
%if constrained/anon sets exist, then check for nonstandard parameters and add as | ||
%container.map | ||
constrainedTypes = typenames(dynamicConstrained & ~invalid); | ||
constrainedVars = varnames(dynamicConstrained & ~invalid); | ||
methodCalls = strcat('[obj.', constrainedVars, ', ivarargin] = ',... | ||
' types.util.parseConstrained(obj,''', constrainedVars, ''', ''',... | ||
constrainedTypes, ''', varargin{:});'); | ||
fullBody = cell(length(methodCalls) * 2,1); | ||
fullBody(1:2:end) = methodCalls; | ||
fullBody(2:2:end) = {deleteFromVars}; | ||
fullBody = strjoin(fullBody, newline); | ||
bodystr(end+1:end+length(fullBody)+1) = [newline fullBody]; | ||
|
||
%if anonymous values exist, then check for nonstandard parameters and add | ||
%as Anon | ||
|
||
anonTypes = typenames(anon & ~invalid); | ||
anonVars = varnames(anon & ~invalid); | ||
methodCalls = strcat('[obj.', anonVars, ',ivarargin] = ',... | ||
' types.util.parseAnon(''', anonTypes, ''', varargin{:});'); | ||
fullBody = cell(length(methodCalls) * 2,1); | ||
fullBody(1:2:end) = methodCalls; | ||
fullBody(2:2:end) = {deleteFromVars}; | ||
fullBody = strjoin(fullBody, newline); | ||
bodystr(end+1:end+length(fullBody)+1) = [newline fullBody]; | ||
|
||
parser = {... | ||
'p = inputParser;',... | ||
'p.KeepUnmatched = true;',... | ||
'p.PartialMatching = false;',... | ||
'p.StructExpand = false;'}; | ||
|
||
names = names(~dynamicConstrained & ~anon); | ||
defaults = cell(size(names)); | ||
for i=1:length(names) | ||
prop = props(names{i}); | ||
if (isa(prop, 'file.Group') &&... | ||
(prop.hasAnonData || prop.hasAnonGroups || prop.isConstrainedSet)) ||... | ||
(isa(prop, 'file.Dataset') && prop.isConstrainedSet) | ||
defaults{i} = 'types.untyped.Set()'; | ||
else | ||
defaults{i} = '[]'; | ||
invalid = cellfun('isempty', typenames); | ||
invalidWarn = invalid & (dynamicConstrained | anon) & ~isattr; | ||
invalidVars = varnames(invalidWarn); | ||
for i=1:length(invalidVars) | ||
warning(warnmsg, invalidVars{i}); | ||
end | ||
end | ||
% add parameters | ||
parser = [parser, strcat('addParameter(p, ''', names, ''', ', defaults,');')]; | ||
% parse | ||
parser = [parser, {'misc.parseSkipInvalidName(p, varargin);'}]; | ||
% get results | ||
parser = [parser, strcat('obj.', names, ' = p.Results.', names, ';')]; | ||
parser = strjoin(parser, newline); | ||
bodystr(end+1:end+length(parser)+1) = [newline parser]; | ||
varnames = lower(varnames); | ||
|
||
%we delete the entry in varargin such that any conflicts do not show up in inputParser | ||
deleteFromVars = 'varargin(ivarargin) = [];'; | ||
%if constrained/anon sets exist, then check for nonstandard parameters and add as | ||
%container.map | ||
constrainedTypes = typenames(dynamicConstrained & ~invalid); | ||
constrainedVars = varnames(dynamicConstrained & ~invalid); | ||
methodCalls = strcat('[obj.', constrainedVars, ', ivarargin] = ',... | ||
' types.util.parseConstrained(obj,''', constrainedVars, ''', ''',... | ||
constrainedTypes, ''', varargin{:});'); | ||
fullBody = cell(length(methodCalls) * 2,1); | ||
fullBody(1:2:end) = methodCalls; | ||
fullBody(2:2:end) = {deleteFromVars}; | ||
fullBody = strjoin(fullBody, newline); | ||
bodystr(end+1:end+length(fullBody)+1) = [newline fullBody]; | ||
|
||
%if anonymous values exist, then check for nonstandard parameters and add | ||
%as Anon | ||
|
||
anonTypes = typenames(anon & ~invalid); | ||
anonVars = varnames(anon & ~invalid); | ||
methodCalls = strcat('[obj.', anonVars, ',ivarargin] = ',... | ||
' types.util.parseAnon(''', anonTypes, ''', varargin{:});'); | ||
fullBody = cell(length(methodCalls) * 2,1); | ||
fullBody(1:2:end) = methodCalls; | ||
fullBody(2:2:end) = {deleteFromVars}; | ||
fullBody = strjoin(fullBody, newline); | ||
bodystr(end+1:end+length(fullBody)+1) = [newline fullBody]; | ||
|
||
parser = {... | ||
'p = inputParser;',... | ||
'p.KeepUnmatched = true;',... | ||
'p.PartialMatching = false;',... | ||
'p.StructExpand = false;'}; | ||
|
||
names = names(~dynamicConstrained & ~anon); | ||
defaults = cell(size(names)); | ||
for i=1:length(names) | ||
prop = props(names{i}); | ||
if (isa(prop, 'file.Group') &&... | ||
(prop.hasAnonData || prop.hasAnonGroups || prop.isConstrainedSet)) ||... | ||
(isa(prop, 'file.Dataset') && prop.isConstrainedSet) | ||
defaults{i} = 'types.untyped.Set()'; | ||
else | ||
defaults{i} = '[]'; | ||
end | ||
end | ||
% add parameters | ||
parser = [parser, strcat('addParameter(p, ''', names, ''', ', defaults,');')]; | ||
% parse | ||
parser = [parser, {'misc.parseSkipInvalidName(p, varargin);'}]; | ||
% get results | ||
parser = [parser, strcat('obj.', names, ' = p.Results.', names, ';')]; | ||
parser = strjoin(parser, newline); | ||
bodystr(end+1:end+length(parser)+1) = [newline parser]; | ||
end | ||
|
||
function checkTxt = fillCheck(name, namespace) | ||
checkTxt = []; | ||
|
||
% find if a dynamic table ancestry exists | ||
ancestry = namespace.getRootBranch(name); | ||
isDynamicTableDescendent = false; | ||
for iAncestor = 1:length(ancestry) | ||
ParentRaw = ancestry{iAncestor}; | ||
% this is always true, we just use the proper index as typedefs may vary. | ||
typeDefInd = isKey(ParentRaw, namespace.TYPEDEF_KEYS); | ||
isDynamicTableDescendent = isDynamicTableDescendent ... | ||
|| strcmp('DynamicTable', ParentRaw(namespace.TYPEDEF_KEYS{typeDefInd})); | ||
end | ||
checkTxt = []; | ||
|
||
% find if a dynamic table ancestry exists | ||
ancestry = namespace.getRootBranch(name); | ||
isDynamicTableDescendent = false; | ||
for iAncestor = 1:length(ancestry) | ||
ParentRaw = ancestry{iAncestor}; | ||
% this is always true, we just use the proper index as typedefs may vary. | ||
typeDefInd = isKey(ParentRaw, namespace.TYPEDEF_KEYS); | ||
isDynamicTableDescendent = isDynamicTableDescendent ... | ||
|| strcmp('DynamicTable', ParentRaw(namespace.TYPEDEF_KEYS{typeDefInd})); | ||
end | ||
|
||
if ~isDynamicTableDescendent | ||
return; | ||
end | ||
if ~isDynamicTableDescendent | ||
return; | ||
end | ||
|
||
checkTxt = strjoin({ ... | ||
sprintf('if strcmp(class(obj), ''%s'')', namespace.getFullClassName(name)), ... | ||
' types.util.dynamictable.checkConfig(obj);', ... | ||
'end',... | ||
}, newline); | ||
checkTxt = strjoin({ ... | ||
sprintf('if strcmp(class(obj), ''%s'')', namespace.getFullClassName(name)), ... | ||
' types.util.dynamictable.checkConfig(obj);', ... | ||
'end',... | ||
}, newline); | ||
end |
Oops, something went wrong.