Skip to content

Commit 2de8b8a

Browse files
authored
Process execution with custom arguments and placeholders (#62)
* Add unit tests for new text and placeholder extension functionality - Introduce unit tests for `EnumerableStringExtensions.ToDictionary`, `StringExtension.SplitIntoKeyValue`, and `PlaceholderReplacer`. - Improve test coverage for string transformation, placeholder replacement, and dictionary conversion edge cases. - Include the addition of `KeyAndValue` implementation for structured key-value handling. * Add support for placeholder replacement in process execution and extend `IProcessExecutor` functionality. - Added `usePlaceholderVars` to `ProcessExecutorInfo` for enabling argument placeholder replacement. - Introduced placeholder replacement in `ProcessExecutorBase` using `EnumerableStringExtensions.ReplacePlaceholders`. - Updated `IProcessExecutor` to support overloaded `Execute` and `ExecuteAsync` methods with custom arguments. - Enhanced `IProcessExecutorBuilder` to configure `usePlaceholderVars`. - Updated related unit tests to include `usePlaceholderVars` handling. * Remove `usePlaceholderVars` property, refactor argument handling, and extend support for placeholder replacements. - Removed `usePlaceholderVars` from `ProcessExecutorInfo` and related methods in `IProcessExecutorBuilder`. - Added overloads for `Execute` and `ExecuteAsync` methods to directly accept placeholder variables. - Refactored internal process execution logic to improve flexibility. - Updated related tests to include direct placeholder variable handling. * Add unit tests and extensions for placeholder variable support in `ProcessExecutor` - Introduced `ProcessExecutorExtensions` to simplify execution with object-to-dictionary conversion. - Added comprehensive unit tests for generic and non-generic execution methods with placeholders. - Updated `IProcessExecutor` to include `ExecuteEx` and `ExecuteExAsync` overloads for dictionary inputs.
1 parent 047ea5f commit 2de8b8a

22 files changed

Lines changed: 2081 additions & 38 deletions

source/Core/CreativeCoders.Core/ObjectExtensions.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq;
5+
using System.Reflection;
36
using System.Threading.Tasks;
47
using JetBrains.Annotations;
58

@@ -66,7 +69,7 @@ public static async ValueTask TryDisposeAsync(this object instance)
6669
throw new MissingMemberException(instance.GetType().Name, propertyName);
6770
}
6871

69-
return (T?) propInfo.GetValue(instance);
72+
return (T?)propInfo.GetValue(instance);
7073
}
7174

7275
public static void SetPropertyValue<T>(this object instance, string propertyName, T? value)
@@ -80,4 +83,19 @@ public static void SetPropertyValue<T>(this object instance, string propertyName
8083

8184
propInfo.SetValue(instance, value);
8285
}
86+
87+
public static Dictionary<string, object?> ToDictionary(this object obj)
88+
{
89+
Ensure.NotNull(obj);
90+
91+
return obj
92+
.GetType()
93+
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
94+
.Select(propertyInfo => ReadProperty(obj, propertyInfo))
95+
.ToDictionary(x => x.PropertyName, x => x.PropertyValue);
96+
}
97+
98+
private static (string PropertyName, object? PropertyValue) ReadProperty(object obj,
99+
PropertyInfo propertyInfo)
100+
=> (propertyInfo.Name, propertyInfo.GetValue(obj));
83101
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using JetBrains.Annotations;
4+
5+
namespace CreativeCoders.Core.Placeholders;
6+
7+
#nullable enable
8+
9+
public class PlaceholderReplacer(
10+
string placeholderPrefix,
11+
string placeholderSuffix,
12+
IDictionary<string, object?> placeholders)
13+
{
14+
private readonly string _placeholderPrefix = Ensure.IsNotNullOrWhitespace(placeholderPrefix);
15+
16+
private readonly string _placeholderSuffix = Ensure.IsNotNullOrWhitespace(placeholderSuffix);
17+
18+
private readonly IDictionary<string, object?> _placeholders = Ensure.NotNull(placeholders);
19+
20+
public string Replace(string text, bool allowNull = false)
21+
{
22+
if (_placeholders.Count == 0)
23+
{
24+
return text;
25+
}
26+
27+
return _placeholders
28+
.Aggregate(text,
29+
(current, placeholder) =>
30+
current.Replace($"{_placeholderPrefix}{placeholder.Key}{_placeholderSuffix}",
31+
placeholder.Value.ToStringSafe(allowNull ? "null" : string.Empty)));
32+
}
33+
34+
public IEnumerable<string> Replace(IEnumerable<string> lines, bool allowNull = false)
35+
{
36+
return _placeholders.Count == 0
37+
? lines
38+
: lines.Select(x => Replace(x, allowNull));
39+
}
40+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq;
5+
using CreativeCoders.Core.Placeholders;
6+
7+
namespace CreativeCoders.Core.Text;
8+
9+
#nullable enable
10+
11+
public static class EnumerableStringExtensions
12+
{
13+
public static Dictionary<string, string> ToDictionary(this IEnumerable<string> items, string separator,
14+
bool ignoreInvalidEntries = true)
15+
{
16+
Ensure.NotNull(items);
17+
Ensure.NotNull(separator);
18+
19+
return items
20+
.Select(x => x.SplitIntoKeyValue(separator))
21+
.Where(x =>
22+
{
23+
if (x == null && !ignoreInvalidEntries)
24+
{
25+
throw new ArgumentException("Invalid key/value entry found");
26+
}
27+
28+
return x != null;
29+
})
30+
.ToDictionary(x => x.Key, x => x.Value);
31+
}
32+
33+
[ExcludeFromCodeCoverage]
34+
public static IEnumerable<string> ReplacePlaceholders(this IEnumerable<string> items,
35+
string placeholderPrefix, string placeholderSuffix,
36+
IDictionary<string, object?> placeholders)
37+
{
38+
Ensure.NotNull(items);
39+
40+
var replacer = new PlaceholderReplacer(placeholderPrefix, placeholderSuffix, placeholders);
41+
42+
return replacer.Replace(items);
43+
}
44+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#nullable enable
2+
namespace CreativeCoders.Core.Text;
3+
4+
public class KeyAndValue(string key, string value)
5+
{
6+
public string Key { get; } = Ensure.NotNull(key);
7+
8+
public string Value { get; } = Ensure.NotNull(value);
9+
}

source/Core/CreativeCoders.Core/Text/StringExtension.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Linq;
45
using System.Runtime.CompilerServices;
@@ -190,4 +191,26 @@ private static string SeparatedToPascalCase(this string? text, char separator)
190191
return parts.Aggregate(string.Empty,
191192
(current, part) => current + char.ToUpperInvariant(part[0]) + part[1..].ToLowerInvariant());
192193
}
194+
195+
public static KeyAndValue? SplitIntoKeyValue(this string? text, string separator)
196+
{
197+
Ensure.NotNull(separator);
198+
199+
if (string.IsNullOrEmpty(text))
200+
{
201+
return null;
202+
}
203+
204+
var separatorIndex = text.IndexOf(separator, StringComparison.Ordinal);
205+
206+
if (separatorIndex == -1)
207+
{
208+
return null;
209+
}
210+
211+
var key = text[..separatorIndex];
212+
var value = text[(separatorIndex + 1)..];
213+
214+
return new KeyAndValue(key, value);
215+
}
193216
}

source/ProcessUtils/CreativeCoders.ProcessUtils/Execution/IProcessExecutor.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,65 @@ public interface IProcessExecutor<T>
77
{
88
T? Execute();
99

10+
T? Execute(string[] args);
11+
12+
T? Execute(IDictionary<string, object?> placeholderVars);
13+
1014
Task<T?> ExecuteAsync();
1115

16+
Task<T?> ExecuteAsync(string[] args);
17+
18+
Task<T?> ExecuteAsync(IDictionary<string, object?> placeholderVars);
19+
1220
ProcessExecutionResult<T?> ExecuteEx();
1321

22+
ProcessExecutionResult<T?> ExecuteEx(string[] args);
23+
24+
ProcessExecutionResult<T?> ExecuteEx(IDictionary<string, object?> placeholderVars);
25+
1426
Task<ProcessExecutionResult<T?>> ExecuteExAsync();
27+
28+
Task<ProcessExecutionResult<T?>> ExecuteExAsync(string[] args);
29+
30+
Task<ProcessExecutionResult<T?>> ExecuteExAsync(IDictionary<string, object?> placeholderVars);
1531
}
1632

1733
[PublicAPI]
1834
public interface IProcessExecutor
1935
{
2036
void Execute();
2137

38+
void Execute(string[] args);
39+
40+
void Execute(IDictionary<string, object?> placeholderVars);
41+
2242
Task ExecuteAsync();
2343

44+
Task ExecuteAsync(string[] args);
45+
46+
Task ExecuteAsync(IDictionary<string, object?> placeholderVars);
47+
2448
IProcess ExecuteEx();
2549

50+
IProcess ExecuteEx(string[] args);
51+
52+
IProcess ExecuteEx(IDictionary<string, object?> placeholderVars);
53+
2654
Task<IProcess> ExecuteExAsync();
2755

56+
Task<IProcess> ExecuteExAsync(string[] args);
57+
58+
Task<IProcess> ExecuteExAsync(IDictionary<string, object?> placeholderVars);
59+
2860
int ExecuteAndReturnExitCode();
2961

62+
int ExecuteAndReturnExitCode(string[] args);
63+
64+
int ExecuteAndReturnExitCode(IDictionary<string, object?> placeholderVars);
65+
3066
Task<int> ExecuteAndReturnExitCodeAsync();
67+
68+
Task<int> ExecuteAndReturnExitCodeAsync(string[] args);
69+
70+
Task<int> ExecuteAndReturnExitCodeAsync(IDictionary<string, object?> placeholderVars);
3171
}

0 commit comments

Comments
 (0)