forked from GitTools/GitVersion
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStringFormatWithExtension.cs
More file actions
157 lines (130 loc) · 7.46 KB
/
Copy pathStringFormatWithExtension.cs
File metadata and controls
157 lines (130 loc) · 7.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
using System.Text.RegularExpressions;
using GitVersion.Core;
namespace GitVersion.Formatting;
internal static class StringFormatWithExtension
{
private static readonly IExpressionCompiler ExpressionCompiler = new ExpressionCompiler();
private static readonly IInputSanitizer InputSanitizer = new InputSanitizer();
private static readonly IMemberResolver MemberResolver = new MemberResolver();
/// <summary>
/// Provides extension methods for formatting strings using a source object and environment context.
/// </summary>
extension(string template)
{
/// <summary>
/// Formats the <paramref name="template"/>, replacing each expression wrapped in curly braces
/// with the corresponding property from the <paramref name="source"/> or <paramref name="environment"/>.
/// </summary>
/// <param name="source">The source object to apply to the <paramref name="template"/></param>
/// <param name="environment"></param>
/// <exception cref="ArgumentNullException">The <paramref name="template"/> is null.</exception>
/// <exception cref="ArgumentException">An environment variable was null and no fallback was provided.</exception>
/// <remarks>
/// An expression containing "." is treated as a property or field access on the <paramref name="source"/>.
/// An expression starting with "env:" is replaced with the value of the corresponding variable from the <paramref name="environment"/>.
/// Each expression may specify a single hardcoded fallback value using the {Prop ?? "fallback"} syntax, which applies if the expression evaluates to null.
/// </remarks>
/// <example>
/// // replace an expression with a property value
/// "Hello {Name}".FormatWith(new { Name = "Fred" }, env);
/// "Hello {Name ?? \"Fred\"}".FormatWith(new { Name = GetNameOrNull() }, env);
/// // replace an expression with an environment variable
/// "{env:BUILD_NUMBER}".FormatWith(new { }, env);
/// "{env:BUILD_NUMBER ?? \"0\"}".FormatWith(new { }, env);
/// </example>
public string FormatWith(object source, IEnvironment environment)
{
ArgumentNullException.ThrowIfNull(source);
return template.FormatWith((member, format, fallback) => EvaluateMemberFromObject(source, member, format, fallback), environment);
}
/// <summary>
/// Formats the <paramref name="template"/>, replacing each expression wrapped in curly braces
/// with the corresponding property from the <paramref name="source"/> or <paramref name="environment"/>.
/// </summary>
/// <param name="source">The source object to apply to the <paramref name="template"/></param>
/// <param name="environment"></param>
/// <exception cref="ArgumentNullException">The <paramref name="template"/> is null.</exception>
/// <exception cref="ArgumentException">An environment variable was null and no fallback was provided.</exception>
/// <remarks>
/// An expression containing "." is treated as a property or field access on the <paramref name="source"/>.
/// An expression starting with "env:" is replaced with the value of the corresponding variable from the <paramref name="environment"/>.
/// Each expression may specify a single hardcoded fallback value using the {Prop ?? "fallback"} syntax, which applies if the expression evaluates to null.
/// </remarks>
/// <example>
/// // replace an expression with a property value
/// "Hello {Name}".FormatWith(new { Name = "Fred" }, env);
/// "Hello {Name ?? \"Fred\"}".FormatWith(new { Name = GetNameOrNull() }, env);
/// // replace an expression with an environment variable
/// "{env:BUILD_NUMBER}".FormatWith(new { }, env);
/// "{env:BUILD_NUMBER ?? \"0\"}".FormatWith(new { }, env);
/// </example>
public string FormatWith(IDictionary<string, object> source, IEnvironment environment)
{
ArgumentNullException.ThrowIfNull(source);
return template.FormatWith((member, format, fallback) => EvaluateMemberFromDictionary(source, member, format, fallback), environment);
}
private string FormatWith(EvaluateMemberDelegate memberEvaluator, IEnvironment environment)
{
ArgumentNullException.ThrowIfNull(template);
var result = new StringBuilder();
var lastIndex = 0;
foreach (var match in RegexPatterns.ExpandTokensRegex.Matches(template).Cast<Match>())
{
var replacement = EvaluateMatch(match, memberEvaluator, environment);
result.Append(template, lastIndex, match.Index - lastIndex);
result.Append(replacement);
lastIndex = match.Index + match.Length;
}
result.Append(template, lastIndex, template.Length - lastIndex);
return result.ToString();
}
}
private static string EvaluateMatch(Match match, EvaluateMemberDelegate memberEvaluator, IEnvironment environment)
{
var fallback = match.Groups["fallback"].Success ? match.Groups["fallback"].Value : null;
if (match.Groups["envvar"].Success)
return EvaluateEnvVar(match.Groups["envvar"].Value, fallback, environment);
if (match.Groups["member"].Success)
{
var format = match.Groups["format"].Success ? match.Groups["format"].Value : null;
return memberEvaluator(match.Groups["member"].Value, format, fallback);
}
throw new ArgumentException($"Invalid token format: '{match.Value}'");
}
private static string EvaluateEnvVar(string name, string? fallback, IEnvironment env)
{
var safeName = InputSanitizer.SanitizeEnvVarName(name);
return env.GetEnvironmentVariable(safeName)
?? fallback
?? throw new ArgumentException($"Environment variable {safeName} not found and no fallback provided");
}
private static string EvaluateMemberFromObject(object source, string member, string? format, string? fallback)
{
var safeMember = InputSanitizer.SanitizeMemberName(member);
var memberPath = MemberResolver.ResolveMemberPath(source!.GetType(), safeMember);
var getter = ExpressionCompiler.CompileGetter(source.GetType(), memberPath);
var value = getter(source);
if (value is null)
return fallback ?? string.Empty;
if (format is not null && ValueFormatter.Default.TryFormat(
value,
InputSanitizer.SanitizeFormat(format),
out var formatted))
{
return formatted;
}
return value.ToString() ?? fallback ?? string.Empty;
}
private static string EvaluateMemberFromDictionary(IDictionary<string, object> source, string member, string? format, string? fallback)
{
var safeMember = InputSanitizer.SanitizeMemberName(member);
if (!source.TryGetValue(safeMember, out var value))
return fallback ?? string.Empty;
if (value is null)
return fallback ?? string.Empty;
if (format is not null && ValueFormatter.Default.TryFormat(value, InputSanitizer.SanitizeFormat(format), out var formatted))
return formatted;
return value.ToString() ?? fallback ?? string.Empty;
}
private delegate string EvaluateMemberDelegate(string member, string? format, string? fallback);
}