1+ using System . Runtime . CompilerServices ;
2+ using OwlCore . Storage ;
3+ using WindowsAppCommunity . Blog . Page ;
4+
5+ namespace WindowsAppCommunity . Blog . Pages ;
6+
7+ /// <summary>
8+ /// Source-derived route index for folderized markdown pages.
9+ /// </summary>
10+ public sealed class MarkdownPageRouteIndex
11+ {
12+ private readonly Dictionary < string , MarkdownPageRoute > _routesByFileId ;
13+
14+ private MarkdownPageRouteIndex ( Dictionary < string , MarkdownPageRoute > routesByFileId )
15+ {
16+ _routesByFileId = routesByFileId ;
17+ }
18+
19+ /// <summary>
20+ /// Gets all indexed markdown page routes.
21+ /// </summary>
22+ public IReadOnlyCollection < MarkdownPageRoute > Routes => _routesByFileId . Values ;
23+
24+ /// <summary>
25+ /// Creates an index from the markdown source folder tree.
26+ /// </summary>
27+ public static async Task < MarkdownPageRouteIndex > CreateAsync ( IFolder markdownSourceFolder , CancellationToken cancellationToken = default )
28+ {
29+ var routesByFileId = new Dictionary < string , MarkdownPageRoute > ( ) ;
30+ await AddFolderRoutesAsync ( markdownSourceFolder , string . Empty , routesByFileId , cancellationToken ) ;
31+
32+ return new MarkdownPageRouteIndex ( routesByFileId ) ;
33+ }
34+
35+ /// <summary>
36+ /// Attempts to get the generated route for a source markdown file.
37+ /// </summary>
38+ public bool TryGetRoute ( IFile sourceMarkdownFile , out MarkdownPageRoute ? route )
39+ {
40+ return _routesByFileId . TryGetValue ( sourceMarkdownFile . Id , out route ) ;
41+ }
42+
43+ /// <summary>
44+ /// Attempts to get a generated page route relative from the referencing markdown page route.
45+ /// </summary>
46+ public bool TryGetRelativeRoute ( IFile referencingMarkdownFile , IFile referencedMarkdownFile , out string ? relativeRoute )
47+ {
48+ relativeRoute = null ;
49+
50+ if ( ! TryGetRoute ( referencingMarkdownFile , out var referencingRoute ) || referencingRoute is null )
51+ return false ;
52+
53+ if ( ! TryGetRoute ( referencedMarkdownFile , out var referencedRoute ) || referencedRoute is null )
54+ return false ;
55+
56+ relativeRoute = GetRelativeFolderRoute ( referencingRoute . PageFolderPath , referencedRoute . PageFolderPath ) ;
57+ return true ;
58+ }
59+
60+ private static async Task AddFolderRoutesAsync (
61+ IFolder folder ,
62+ string currentFolderPath ,
63+ Dictionary < string , MarkdownPageRoute > routesByFileId ,
64+ CancellationToken cancellationToken )
65+ {
66+ await foreach ( var item in folder . GetItemsAsync ( StorableType . All , cancellationToken ) . WithCancellation ( cancellationToken ) )
67+ {
68+ if ( item is IFile file && Path . GetExtension ( file . Name ) . Equals ( ".md" , StringComparison . OrdinalIgnoreCase ) )
69+ {
70+ var pageFolderName = HtmlTemplatedMarkdownPageFolder . GetPageFolderName ( file . Name ) ;
71+ var pageFolderPath = CombineRoutePath ( currentFolderPath , pageFolderName ) ;
72+ routesByFileId [ file . Id ] = new MarkdownPageRoute ( file , pageFolderPath ) ;
73+ }
74+
75+ if ( item is IFolder subfolder )
76+ {
77+ var nestedFolderPath = CombineRoutePath ( currentFolderPath , subfolder . Name ) ;
78+ await AddFolderRoutesAsync ( subfolder , nestedFolderPath , routesByFileId , cancellationToken ) ;
79+ }
80+ }
81+ }
82+
83+ private static string CombineRoutePath ( string parentPath , string childName )
84+ {
85+ return string . IsNullOrWhiteSpace ( parentPath ) ? childName : $ "{ parentPath . TrimEnd ( '/' ) } /{ childName } ";
86+ }
87+
88+ private static string GetRelativeFolderRoute ( string fromPageFolderPath , string toPageFolderPath )
89+ {
90+ var fromSegments = SplitRoutePath ( fromPageFolderPath ) . ToArray ( ) ;
91+ var toSegments = SplitRoutePath ( toPageFolderPath ) . ToArray ( ) ;
92+
93+ var commonLength = 0 ;
94+ while ( commonLength < fromSegments . Length &&
95+ commonLength < toSegments . Length &&
96+ string . Equals ( fromSegments [ commonLength ] , toSegments [ commonLength ] , StringComparison . OrdinalIgnoreCase ) )
97+ {
98+ commonLength ++ ;
99+ }
100+
101+ var relativeSegments = Enumerable
102+ . Repeat ( ".." , fromSegments . Length - commonLength )
103+ . Concat ( toSegments . Skip ( commonLength ) )
104+ . ToArray ( ) ;
105+
106+ if ( relativeSegments . Length == 0 )
107+ return "./" ;
108+
109+ return $ "{ string . Join ( '/' , relativeSegments ) } /";
110+ }
111+
112+ private static IEnumerable < string > SplitRoutePath ( string routePath )
113+ {
114+ return routePath
115+ . Replace ( '\\ ' , '/' )
116+ . Split ( '/' , StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries ) ;
117+ }
118+ }
0 commit comments