discover method
Discovers the workspace rooted at rootPath.
Implementation
WorkspaceDiscoveryResult discover(String? rootPath) {
T measureGlob<T>(T Function() action) {
return action();
}
T measureParse<T>(T Function() action) {
return action();
}
final requestedRoot = rootPath ?? Directory.current.path;
late final String root;
try {
root = Directory(requestedRoot).resolveSymbolicLinksSync();
} catch (_) {
root = Directory(requestedRoot).absolute.path;
}
final workspacePrefix = root.replaceAll('\\', '/').toLowerCase();
final configPath = '$root/zuke.yaml';
final inputContents = <String, String>{};
final configFile = File(configPath);
late final ZukeConfig config;
if (!configFile.existsSync()) {
throw InvalidWorkspaceConfigError(
'zuke.yaml not found at ${configFile.path}',
);
}
final configContent = configFile.readAsStringSync();
inputContents[configFile.path] = configContent;
config = ZukeConfig.fromYaml(configContent, root: root);
final errors = <String>[];
final seenPhysicalPaths = <String, List<String>>{};
// Parse features
final parser = GherkinParser();
final features = <ParsedFeature>[];
for (final pattern in config.featurePatterns) {
_checkPatternSafety(pattern, errors);
final files = measureGlob(
() => _matchGlob(pattern, root, workspacePrefix: workspacePrefix),
);
if (files.isEmpty) {
errors.add('Feature pattern "$pattern" matched no files');
}
for (final f in files) {
_trackOverlap(f, pattern, seenPhysicalPaths, errors);
try {
final (content, result) = measureParse(() {
final fileContent = File(f).readAsStringSync();
final parseRes = parser.parseFile(fileContent, f);
return (fileContent, parseRes);
});
inputContents[File(f).path] = content;
features.addAll(result.features);
for (final e in result.errors) {
errors.add('${e.source.file}:${e.source.line}: ${e.message}');
}
} catch (e) {
errors.add('$f: parse failed: $e');
}
}
}
// Load YAML data
final epics = <String, Map<String, dynamic>>{};
for (final pattern in config.epicPatterns) {
_checkPatternSafety(pattern, errors);
_loadYamlMap(
pattern,
root,
epics,
errors,
seenPhysicalPaths,
measureGlob: measureGlob,
measureParse: measureParse,
workspacePrefix: workspacePrefix,
inputContents: inputContents,
);
}
final controls = <String, Map<String, dynamic>>{};
for (final pattern in config.controlPatterns) {
_checkPatternSafety(pattern, errors);
_loadYamlListItems(
pattern,
root,
'controls',
controls,
errors,
seenPhysicalPaths,
measureGlob: measureGlob,
measureParse: measureParse,
workspacePrefix: workspacePrefix,
inputContents: inputContents,
);
}
final registries = <String, Map<String, dynamic>>{};
for (final pattern in config.registryPatterns) {
_checkPatternSafety(pattern, errors);
_loadRegistries(
pattern,
root,
registries,
errors,
seenPhysicalPaths,
measureGlob: measureGlob,
measureParse: measureParse,
workspacePrefix: workspacePrefix,
inputContents: inputContents,
);
}
final policies = <String, Map<String, dynamic>>{};
if (config.projectPolicy != null) {
_checkPatternSafety(config.projectPolicy!, errors);
final files = measureGlob(
() => _matchGlob(
config.projectPolicy!,
root,
workspacePrefix: workspacePrefix,
),
);
for (final f in files) {
_trackOverlap(f, config.projectPolicy!, seenPhysicalPaths, errors);
measureParse(() {
_loadPolicyFile(f, policies, errors, inputContents: inputContents);
});
}
if (files.isEmpty) {
errors.add('Project policy "${config.projectPolicy}" matched no files');
}
}
for (final pattern in config.profilePatterns) {
_checkPatternSafety(pattern, errors);
final files = measureGlob(
() => _matchGlob(pattern, root, workspacePrefix: workspacePrefix),
);
for (final f in files) {
_trackOverlap(f, pattern, seenPhysicalPaths, errors);
measureParse(() {
_loadPolicyFile(f, policies, errors, inputContents: inputContents);
});
}
}
_validateEvidenceRequirements(config, features, errors);
final inputPatterns = <String>{
...config.featurePatterns,
...config.epicPatterns,
...config.controlPatterns,
...config.registryPatterns,
if (config.projectPolicy != null) config.projectPolicy!,
...config.profilePatterns,
}.toList()..sort();
final patternInputPaths = inputContents.keys
.where((path) => path != configFile.path)
.toSet();
return WorkspaceDiscoveryResult(
config: config,
data: MetadataExtractorResult(
features: features,
epics: epics,
controls: controls,
registries: registries,
policies: policies,
errors: errors,
),
inputContents: Map.unmodifiable(inputContents),
inputPatterns: List.unmodifiable(inputPatterns),
patternInputPaths: Set.unmodifiable(patternInputPaths),
);
}