Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions for inferring directory of generated types #681

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
20 changes: 20 additions & 0 deletions +matnwb/+common/+internal/classname2path.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
function pathName = classname2path(typeName)
% classname2path - Return relative path name for a class name with namespaces
%
% Example:
%
% pathName = matnwb.common.internal.classname2path('types.core.NWBFile')
%
% pathName =
% '+types/+core/NWBFile.m'

arguments
typeName (1,1) string
end

typeName = split(typeName, ".");
typeName(1:end-1) = "+" + typeName(1:end-1);
typeName(end) = typeName(end) + ".m";

pathName = fullfile(typeName{:});
end
33 changes: 33 additions & 0 deletions +schemes/+utility/findRootDirectoryForGeneratedTypes.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
function rootDirectoryForGeneration = findRootDirectoryForGeneratedTypes()
% findRootDirectoryForGeneratedTypes - Find directory where generated types are located
%
% Check if any instances of types.core.NWBFile are present on MATLAB's
% search path. If types.core.NWBFile is found, the function returns the root
% directory where generated types are located, otherwise the function
% throws an error

generatedTypeName = 'types.core.NWBFile';
relPathForGeneratedType = matnwb.common.internal.classname2path(generatedTypeName);

generatedTypeLocation = which(relPathForGeneratedType, '-all');

if isempty(generatedTypeLocation)
ME = MException('NWB:Types:GeneratedTypesNotFound', ...
['Could not find a location for generated classes of neurodata ', ...
'types. Please check if MatNWB is properly added to MATLAB''s ' ...
'search path and/or run generateCore() to re-generate classes.'] );
throwAsCaller(ME)
end

if numel(generatedTypeLocation) > 1
warning('NWB:Types:MultipleGeneratedTypesFound', ...
['Multiple generated types was found for %s.\n', ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
['Multiple generated types was found for %s.\n', ...
['Multiple generated types were found for %s.\n', ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you indicate the paths for each?

'This may indicate duplicate definitions that could lead to ', ...
'unexpected behavior. Please ensure that only one instance of MatNWB is ', ...
'present on MATLAB''s search path at any time.'], generatedTypeName)
end

% Remove the namespace folders from the path location of the
% "types.core.NWBFile" class to obtain the root directory for generated types.
rootDirectoryForGeneration = replace(generatedTypeLocation{1}, relPathForGeneratedType, '');
end
8 changes: 4 additions & 4 deletions +schemes/Namespace.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
classdef Namespace < handle
properties (SetAccess=private)
name = '' % name of this namespace
version = '' % version of this namespace
dependencies = [] % parent namespaces by [Namespace]
registry = [] % maps name to class
name char = '' % name of this namespace
version char = '' % version of this namespace
dependencies = [] % parent namespaces by [Namespace]
registry = [] % maps name to class
end

properties (Constant)
Expand Down
48 changes: 33 additions & 15 deletions +schemes/loadNamespace.m
Copy link
Collaborator Author

@ehennestad ehennestad Mar 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes to loadNamespace function

  • Make generatedTypesDirectory optional. If not provided, it will be inferred from MATLAB's search path
  • Improve docstring, variablenames and error message

Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
function Namespace = loadNamespace(name, saveDir)
%LOADNAMESPACE loads dependent class metadata
% name is the pregenerated namespace name
% Namespaces a schemes.Namespace object with dependency graph
function namespace = loadNamespace(namespaceName, generatedTypesDirectory)
%LOADNAMESPACE - Load cached specifications for a namespace with dependency graph
%
% Input Arguments:
% namespaceName (string) : Name of a format specification namespace, i.e "core"
% generatedTypesDirectory (string) : Optional, path name of directory
% where generated classes for neurodata types are located
%
% Output Arguments:
% namespace (schemes.Namespace) - Namespace object with a dependency graph

Cache = spec.loadCache(name, 'savedir', saveDir);
assert(~isempty(Cache), 'NWB:Namespace:CacheMissing',...
['No cache found for namespace `%s`. '...
'Please generate any missing dependencies before generating this namespace.'], name);
ancestry = schemes.Namespace.empty(length(Cache.dependencies), 0);
arguments
namespaceName (1,1) string
generatedTypesDirectory (1,1) string {mustBeFolder} = ...
schemes.utility.findRootDirectoryForGeneratedTypes()

Check warning on line 15 in +schemes/loadNamespace.m

View check run for this annotation

Codecov / codecov/patch

+schemes/loadNamespace.m#L15

Added line #L15 was not covered by tests
end

for i=length(Cache.dependencies):-1:1
ancestorName = Cache.dependencies{i};
ancestry(i) = schemes.loadNamespace(ancestorName, saveDir);
cachedNamespaceSpecification = spec.loadCache(namespaceName, 'savedir', generatedTypesDirectory);
assert( ~isempty(cachedNamespaceSpecification), ...
'NWB:Namespace:CacheMissing',...
['No cache found for namespace `%s`.\nPlease use generateCore or ' ...
'generateExtension to initialize a cache for the `%s` namespace.'], ...
namespaceName, namespaceName);

ancestry = schemes.Namespace.empty(length(cachedNamespaceSpecification.dependencies), 0);
for i = length(cachedNamespaceSpecification.dependencies):-1:1
ancestorName = cachedNamespaceSpecification.dependencies{i};
ancestry(i) = schemes.loadNamespace(ancestorName, generatedTypesDirectory);
end

namespace = schemes.Namespace(...
namespaceName, ...
cachedNamespaceSpecification.version, ...
ancestry, ...
cachedNamespaceSpecification.schema);
end

Namespace = schemes.Namespace(name, Cache.version, ancestry, Cache.schema);
end
29 changes: 29 additions & 0 deletions +tests/+unit/+schemes/SchemesFunctionsTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
classdef SchemesFunctionsTest < tests.abstract.NwbTestCase
% SchemesFunctionsTest - Unit tests for functions in the schemes namespace
methods (Test)
function testFindRootDirectoryForGeneratedTypes(testCase)
generatedTypesFolder = schemes.utility.findRootDirectoryForGeneratedTypes();
testCase.verifyTrue(isfolder(generatedTypesFolder))
end

function testFindRootDirectoryForGeneratedTypesIfMissing(testCase)
import tests.fixtures.NwbClearGeneratedFixture
testCase.applyFixture(NwbClearGeneratedFixture(testCase.getTypesOutputFolder))
testCase.verifyError(...
@() schemes.utility.findRootDirectoryForGeneratedTypes(), ...
'NWB:Types:GeneratedTypesNotFound')
end

function testFindRootDirectoryForGeneratedTypesWithDuplicates(testCase)
% Generate types in temp working folder. Will produce an extra
% set of generated types on MATLAB's search path
import matlab.unittest.fixtures.WorkingFolderFixture
testCase.applyFixture(WorkingFolderFixture)

generateCore('savedir', '.')
testCase.verifyWarning(...
@() schemes.utility.findRootDirectoryForGeneratedTypes(), ...
'NWB:Types:MultipleGeneratedTypesFound')
end
end
end