33
44namespace VirtualClient . Contracts
55{
6+ using System ;
7+ using System . Collections . Generic ;
68 using System . IO . Abstractions ;
9+ using System . Linq ;
10+ using System . Text . RegularExpressions ;
711 using VirtualClient . Common . Extensions ;
812
913 /// <summary>
@@ -12,6 +16,8 @@ namespace VirtualClient.Contracts
1216 /// </summary>
1317 public class FileContext
1418 {
19+ private static readonly Regex TemplatePlaceholderExpression = new Regex ( @"\{(.*?)\}" , RegexOptions . Compiled | RegexOptions . IgnoreCase ) ;
20+
1521 /// <summary>
1622 /// Initializes a new instance of the <see cref="FileContext"/> class.
1723 /// </summary>
@@ -87,5 +93,71 @@ public FileContext(IFileInfo file, string contentType, string contentEncoding, s
8793 /// The name of the tool/toolset that produced the file (e.g. FioExecutor, FIO).
8894 /// </summary>
8995 public string ToolName { get ; }
96+
97+ /// <summary>
98+ /// Resolves placeholders in the path template provided.
99+ /// </summary>
100+ /// <param name="pathTemplate">A path template containing placeholders to resolve (e.g. {experimentId}-summary.txt).</param>
101+ /// <param name="replacements">Provides the replacement values for the placeholders in the path template.</param>
102+ /// <returns>
103+ /// A path having matching placeholders replaced with actual values
104+ /// (e.g. {experimentId}-summary.txt -> afda108a-4be9-4fe2-a9ef-7b787150896a-summary.txt).
105+ /// </returns>
106+ public static string ResolvePathTemplate ( string pathTemplate , IDictionary < string , IConvertible > replacements )
107+ {
108+ string resolvedTemplate = pathTemplate ;
109+ MatchCollection matches = FileContext . TemplatePlaceholderExpression . Matches ( pathTemplate ) ;
110+
111+ if ( matches ? . Any ( ) == true )
112+ {
113+ string resolvedValue ;
114+ foreach ( Match match in matches )
115+ {
116+ string [ ] effectivePlaceholders = null ;
117+ string templatePlaceholder = match . Groups [ 1 ] . Value ;
118+ if ( templatePlaceholder . IndexOf ( '|' ) < 0 )
119+ {
120+ effectivePlaceholders = new string [ ] { templatePlaceholder } ;
121+ }
122+ else
123+ {
124+ effectivePlaceholders = templatePlaceholder . Split ( "|" , StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries ) ;
125+ }
126+
127+ bool placeholderMatched = false ;
128+ foreach ( string placeholder in effectivePlaceholders )
129+ {
130+ // Order of placeholder resolution:
131+ // 1) Metadata known by the VC runtime is applied first because it is definitive.
132+ // 2) Component metadata supplied to the factory.
133+ // 3) Component parameters supplied to the factory.
134+ if ( replacements ? . Any ( ) == true && FileContext . TryResolvePlaceholder ( replacements , placeholder , out resolvedValue ) )
135+ {
136+ placeholderMatched = true ;
137+ resolvedTemplate = resolvedTemplate . Replace ( match . Value , resolvedValue ) ;
138+ break ;
139+ }
140+ }
141+
142+ if ( ! placeholderMatched )
143+ {
144+ resolvedTemplate = resolvedTemplate . Replace ( match . Value , string . Empty ) ;
145+ }
146+ }
147+ }
148+
149+ return resolvedTemplate ;
150+ }
151+
152+ private static bool TryResolvePlaceholder ( IDictionary < string , IConvertible > metadata , string propertyName , out string resolvedValue )
153+ {
154+ resolvedValue = null ;
155+ if ( ! string . IsNullOrWhiteSpace ( propertyName ) && metadata . TryGetValue ( propertyName , out IConvertible propertyValue ) && propertyValue != null )
156+ {
157+ resolvedValue = propertyValue . ToString ( ) ;
158+ }
159+
160+ return resolvedValue != null ;
161+ }
90162 }
91163}
0 commit comments