Skip to content

Commit 5e8b9ed

Browse files
committed
refactor: Remove SourceFolder dependency from IAssetResolver interface
Simplifies asset resolver architecture: - Removed IFolder SourceFolder property from IAssetResolver interface - Removed SourceFolder property from RelativePathAssetResolver implementation - Asset resolution now fully stateless, relying only on markdown source context Updated PagesCommand implementation: - Simplified folder materialization logic with better variable naming - Changed --template-file option to --template-file-name for clarity - Removed debug logging statements - Used DepthFirstRecursiveFolder at pagesFolder level instead of per-page - Improved code formatting and inline documentation - Streamlined folder creation using CreateFoldersAlongRelativePathAsync pattern Restored HtmlTemplatedMarkdownPageFolder template asset yielding: - Re-added template folder enumeration and asset passthrough - Template HTML file exclusion logic restored - Enables PostPage scenario to continue working as expected Updated test setup: - Removed SourceFolder initialization from RelativePathAssetResolver instances - Tests now reflect stateless resolver design
1 parent ba22171 commit 5e8b9ed

File tree

5 files changed

+60
-88
lines changed

5 files changed

+60
-88
lines changed

src/Blog/Assets/IAssetResolver.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@ namespace WindowsAppCommunity.Blog.Assets;
77
/// </summary>
88
public interface IAssetResolver
99
{
10-
/// <summary>
11-
/// Root folder for relative path resolution.
12-
/// </summary>
13-
IFolder SourceFolder { get; init; }
14-
1510
/// <summary>
1611
/// Resolves a relative path string to an IFile instance.
1712
/// </summary>

src/Blog/Assets/RelativePathAssetResolver.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ namespace WindowsAppCommunity.Blog.Assets;
99
/// </summary>
1010
public sealed class RelativePathAssetResolver : IAssetResolver
1111
{
12-
/// <inheritdoc/>
13-
public required IFolder SourceFolder { get; init; }
14-
1512
/// <inheritdoc/>
1613
public async Task<IFile?> ResolveAsync(IFile markdownSource, string relativePath, CancellationToken ct = default)
1714
{

src/Blog/Page/HtmlTemplatedMarkdownPageFolder.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,10 @@ public virtual async IAsyncEnumerable<IStorableChild> GetItemsAsync(
7272
StorableType type = StorableType.All,
7373
[EnumeratorCancellation] CancellationToken cancellationToken = default)
7474
{
75-
// Yield HtmlTemplatedMarkdownFile (virtual HTML file) only
76-
// Template assets are NOT yielded here - they're detected as links in the template HTML
77-
// and tracked in the HTML file's IncludedAssets collection for consumer materialization
75+
// Resolve template file for exclusion and HtmlTemplatedMarkdownFile construction
76+
var templateFile = await ResolveTemplateFileAsync(_templateSource, _templateFileName);
77+
78+
// Yield HtmlTemplatedMarkdownFile (virtual HTML file)
7879
if (type == StorableType.All || type == StorableType.File)
7980
{
8081
var indexHtmlId = $"{Id}/index.html";
@@ -83,6 +84,32 @@ public virtual async IAsyncEnumerable<IStorableChild> GetItemsAsync(
8384
Name = "index.html"
8485
};
8586
}
87+
88+
// If template is folder, yield wrapped asset structure
89+
if (_templateSource is IFolder templateFolder)
90+
{
91+
await foreach (var item in templateFolder.GetItemsAsync(StorableType.All, cancellationToken))
92+
{
93+
// Wrap subfolders as PostPageAssetFolder
94+
if (item is IFolder subfolder && (type == StorableType.All || type == StorableType.Folder))
95+
{
96+
yield return new PostPageAssetFolder(subfolder, this, templateFile);
97+
continue;
98+
}
99+
100+
// Pass through files directly (excluding template HTML file)
101+
if (item is IChildFile file && (type == StorableType.All || type == StorableType.File))
102+
{
103+
// Exclude template HTML file (already rendered as index.html)
104+
if (file.Id == templateFile.Id)
105+
{
106+
continue;
107+
}
108+
109+
yield return file;
110+
}
111+
}
112+
}
86113
}
87114

88115
/// <summary>

src/Commands/Blog/PostPage/PagesCommand.cs

Lines changed: 28 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public PagesCommand()
4646
};
4747

4848
var templateFileNameOption = new Option<string?>(
49-
name: "--template-file",
49+
name: "--template-file-name",
5050
description: "Template file name when --template is folder (optional, defaults to 'template.html')",
5151
getDefaultValue: () => null);
5252

@@ -75,87 +75,46 @@ private async Task<int> ExecuteAsync(
7575
string outputPath,
7676
string? templateFileName)
7777
{
78-
// Resolve markdown source folder (SystemFolder throws if doesn't exist)
79-
var markdownSourceFolder = new SystemFolder(markdownFolderPath);
80-
8178
// Resolve template source (file or folder)
82-
IStorable templateSource;
83-
if (Directory.Exists(templatePath))
84-
{
85-
templateSource = new SystemFolder(templatePath);
86-
}
87-
else
88-
{
89-
// SystemFile throws if doesn't exist
90-
templateSource = new SystemFile(templatePath);
91-
}
79+
IStorable templateSource = Directory.Exists(templatePath)
80+
? new SystemFolder(templatePath)
81+
: new SystemFile(templatePath);
9282

93-
// Resolve output folder (SystemFolder throws if doesn't exist)
94-
IModifiableFolder outputFolder = new SystemFolder(outputPath);
83+
// Resolve markdown source and output folders (SystemFolder throws if doesn't exist)
84+
var outputFolder = new SystemFolder(outputPath);
85+
var markdownSourceFolder = new SystemFolder(markdownFolderPath);
9586

96-
// Create virtual AssetAwareHtmlTemplatedMarkdownPagesFolder (lazy generation - no I/O during construction)
87+
// Create recursive markdown-to-webpage folder (lazy generation - no I/O during construction)
88+
// Turns `.md` files into folders with an `index.html` holding asset metadata for output copy
9789
var pagesFolder = new AssetAwareHtmlTemplatedMarkdownPagesFolder(markdownSourceFolder, templateSource, templateFileName)
9890
{
9991
LinkDetector = new RegexAssetLinkDetector(),
100-
Resolver = new RelativePathAssetResolver
101-
{
102-
// MarkdownSource passed per-call in ResolveAsync (varies per page)
103-
SourceFolder = markdownSourceFolder
104-
},
92+
Resolver = new RelativePathAssetResolver(),
10593
InclusionStrategy = new ReferenceOnlyInclusionStrategy()
10694
};
10795

108-
// Materialize virtual structure by iterating page folders, then files within each
109-
// Pattern from PostPageCommand: Create output folder per page, copy files relative to it
110-
await foreach (var item in pagesFolder.GetItemsAsync(StorableType.Folder))
96+
// Materialize virtual folderized markdown pages, then files within each markdown page folder.
97+
await foreach (IChildFolder pageFolder in new DepthFirstRecursiveFolder(pagesFolder).GetItemsAsync(StorableType.Folder))
11198
{
112-
if (item is not IChildFolder pageFolder)
113-
continue;
114-
115-
Logger.LogInformation($"Processing page folder: {pageFolder.Name}");
116-
117-
// Create output folder for this page
118-
var pageOutputFolder = await outputFolder.CreateFolderAsync(pageFolder.Name, overwrite: true);
119-
120-
// Iterate files within this page folder recursively
121-
var recursiveFolder = new DepthFirstRecursiveFolder(pageFolder);
122-
await foreach (var fileItem in recursiveFolder.GetItemsAsync(StorableType.File))
99+
// Get path to markdown page folder (mirrors original source file without extension)
100+
var relativePathToPagesPageFolder = await pagesFolder.GetRelativePathToAsync(pageFolder);
101+
var pageOutputFolder = await outputFolder.CreateFoldersAlongRelativePathAsync(relativePathToPagesPageFolder, overwrite: true).LastAsync();
102+
103+
// Iterate/copy files within markdown page folder
104+
await foreach (AssetAwareHtmlTemplatedMarkdownFile indexFile in pageFolder.GetItemsAsync(StorableType.File))
123105
{
124-
if (fileItem is not IChildFile file)
125-
continue;
126-
127-
Logger.LogInformation($" Yielded file: {file.Name} (Type: {file.GetType().Name})");
128-
129106
// Get relative path from page folder (not pagesFolder root)
130-
string relativePath = await pageFolder.GetRelativePathToAsync(file);
131-
Logger.LogInformation($" Relative path: {relativePath}");
132-
133-
// Create folders relative to THIS page's output folder
134-
var containingFolder = (IModifiableFolder)await pageOutputFolder.CreateFoldersAlongRelativePathAsync(relativePath, overwrite: false).LastAsync();
135-
Logger.LogInformation($" Containing folder: {containingFolder.Id}");
136-
137-
// Copy file
138-
Logger.LogInformation($" About to copy - file.Id: {file.Id}, file.GetType(): {file.GetType().Name}");
139-
Logger.LogInformation($" Target folder: {containingFolder.Id}");
140-
var copiedFile = await containingFolder.CreateCopyOfAsync(file, overwrite: true);
141-
Logger.LogInformation($" Copied to: {copiedFile.Id}");
142-
143-
// Check what else got created
144-
Logger.LogInformation($" Checking folder contents after copy:");
145-
await foreach (var folderItem in containingFolder.GetItemsAsync())
146-
{
147-
Logger.LogInformation($" Found: {folderItem.Name} (Type: {folderItem.GetType().Name})");
148-
}
149-
150-
// Copy all assets referenced in content using RewrittenPath
151-
if (file is AssetAwareHtmlTemplatedMarkdownFile htmlFile)
107+
string pageFolderFileRelativePath = await pageFolder.GetRelativePathToAsync(indexFile);
108+
109+
// Create folders relative to THIS page's output folder, then copy
110+
var containingFolder = (IModifiableFolder)await pageOutputFolder.CreateFoldersAlongRelativePathAsync(pageFolderFileRelativePath, overwrite: false).LastAsync();
111+
var copiedIndexFile = await containingFolder.CreateCopyOfAsync(indexFile, overwrite: true);
112+
113+
// Copy all assets referenced in index.html to the rewritten asset path
114+
foreach (var asset in indexFile.IncludedAssets)
152115
{
153-
foreach (var asset in htmlFile.IncludedAssets)
154-
{
155-
// Navigate FROM htmlFile using RewrittenPath (relative to HTML file)
156-
var assetOutputFolder = (IModifiableFolder)await copiedFile.CreateFoldersAlongRelativePathAsync(asset.RewrittenPath, overwrite: false).LastAsync();
157-
await assetOutputFolder.CreateCopyOfAsync(asset.ResolvedFile, overwrite: true);
158-
}
116+
var assetOutputFolder = (IModifiableFolder)await copiedIndexFile.CreateFoldersAlongRelativePathAsync(asset.RewrittenPath, overwrite: false).LastAsync();
117+
await assetOutputFolder.CreateCopyOfAsync(asset.ResolvedFile, overwrite: true);
159118
}
160119
}
161120
}

tests/Blog/AssetAwareHtmlTemplatedMarkdownPagesFolderTests.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,7 @@ await templateWriter.WriteAsync(@"<!DOCTYPE html>
9393
"index.html")
9494
{
9595
LinkDetector = new RegexAssetLinkDetector(),
96-
Resolver = new RelativePathAssetResolver
97-
{
98-
SourceFolder = _testSourceFolder
99-
},
96+
Resolver = new RelativePathAssetResolver(),
10097
InclusionStrategy = new ReferenceOnlyInclusionStrategy()
10198
};
10299
}
@@ -163,10 +160,7 @@ public async Task AssetLinkDetection_IdentifiesRelativeLinks()
163160
[TestMethod]
164161
public async Task AssetPathResolution_ResolvesValidPaths()
165162
{
166-
var resolver = new RelativePathAssetResolver
167-
{
168-
SourceFolder = _testSourceFolder
169-
};
163+
var resolver = new RelativePathAssetResolver();
170164

171165
var resolvedAsset = await resolver.ResolveAsync(_page1File, "images/logo.png");
172166

0 commit comments

Comments
 (0)