in src/tooling/docs-assembler/AssembleSources.cs [160:297]
public static FrozenDictionary<Uri, TocTopLevelMapping> GetConfiguredSources(AssembleContext context)
{
var dictionary = new Dictionary<Uri, TocTopLevelMapping>();
var reader = new YamlStreamReader(context.NavigationPath, context.Collector);
var entries = new List<KeyValuePair<Uri, TocTopLevelMapping>>();
foreach (var entry in reader.Read())
{
switch (entry.Key)
{
case "toc":
ReadTocBlocks(entries, reader, entry.Entry, null, 0, null, null);
break;
}
}
foreach (var (source, block) in entries)
dictionary[source] = block;
return dictionary.ToFrozenDictionary();
static void ReadTocBlocks(
List<KeyValuePair<Uri, TocTopLevelMapping>> entries,
YamlStreamReader reader,
KeyValuePair<YamlNode, YamlNode> entry,
string? parent,
int depth,
Uri? topLevelSource,
Uri? parentSource
)
{
if (entry.Key is not YamlScalarNode { Value: not null } scalarKey)
{
reader.EmitWarning($"key '{entry.Key}' is not string");
return;
}
if (entry.Value is not YamlSequenceNode sequence)
{
reader.EmitWarning($"'{scalarKey.Value}' is not an array");
return;
}
var i = 0;
foreach (var tocEntry in sequence.Children.OfType<YamlMappingNode>())
{
ReadBlock(entries, reader, tocEntry, parent, depth, i, topLevelSource, parentSource);
i++;
}
}
static void ReadBlock(
List<KeyValuePair<Uri, TocTopLevelMapping>> entries,
YamlStreamReader reader,
YamlMappingNode tocEntry,
string? parent,
int depth,
int order,
Uri? topLevelSource,
Uri? parentSource
)
{
string? repository = null;
string? source = null;
string? pathPrefix = null;
foreach (var entry in tocEntry.Children)
{
var key = ((YamlScalarNode)entry.Key).Value;
switch (key)
{
case "toc":
source = reader.ReadString(entry);
if (source.AsSpan().IndexOf("://") == -1)
{
parent = source;
pathPrefix = source;
source = ContentSourceMoniker.CreateString(NarrativeRepository.RepositoryName, source);
}
break;
case "repo":
repository = reader.ReadString(entry);
break;
case "path_prefix":
pathPrefix = reader.ReadString(entry);
break;
}
}
if (repository is not null)
{
if (source is not null)
reader.EmitError($"toc config defines 'repo' can not be combined with 'toc': {source}", tocEntry);
pathPrefix = string.Join("/", [parent, repository]);
source = ContentSourceMoniker.CreateString(repository, parent);
}
if (source is null)
return;
source = source.EndsWith("://") ? source : source.TrimEnd('/') + "/";
if (!Uri.TryCreate(source, UriKind.Absolute, out var sourceUri))
{
reader.EmitError($"Source toc entry is not a valid uri: {source}", tocEntry);
return;
}
var sourcePrefix = $"{sourceUri.Host}/{sourceUri.AbsolutePath.TrimStart('/')}";
if (string.IsNullOrEmpty(pathPrefix))
reader.EmitError($"Path prefix is not defined for: {source}, falling back to {sourcePrefix} which may be incorrect", tocEntry);
pathPrefix ??= sourcePrefix;
topLevelSource ??= sourceUri;
parentSource ??= sourceUri;
var tocTopLevelMapping = new TocTopLevelMapping
{
Source = sourceUri,
SourcePathPrefix = pathPrefix,
TopLevelSource = topLevelSource,
ParentSource = parentSource
};
entries.Add(new KeyValuePair<Uri, TocTopLevelMapping>(sourceUri, tocTopLevelMapping));
foreach (var entry in tocEntry.Children)
{
var key = ((YamlScalarNode)entry.Key).Value;
switch (key)
{
case "children":
if (source is null && pathPrefix is null)
{
reader.EmitWarning("toc entry has no toc or path_prefix defined");
continue;
}
ReadTocBlocks(entries, reader, entry, parent, depth + 1, topLevelSource, tocTopLevelMapping.Source);
break;
}
}
}
}