From 431b9be45820585a090cb66731ace214895cabf5 Mon Sep 17 00:00:00 2001 From: henry-js <79054685+henry-js@users.noreply.github.com> Date: Tue, 29 Jul 2025 01:48:39 +0100 Subject: [PATCH 1/2] feat: new options - allow optional period prefix - allow to pascal case --- .../PathEnvironmentHelperTests.cs | 3 +- src/DotNetPathUtils/DotNetPathUtils.csproj | 3 +- src/DotNetPathUtils/Extensions.cs | 13 ++++++ src/DotNetPathUtils/IEnvironmentService.cs | 2 +- src/DotNetPathUtils/PathEnvironmentHelper.cs | 43 ++++++++++++++----- .../PathEnvironmentHelperLog.cs | 34 +++++++++++++++ src/DotNetPathUtils/PathUtilsOptions.cs | 13 ++++++ .../SystemEnvironmentService.cs | 6 ++- 8 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 src/DotNetPathUtils/Extensions.cs create mode 100644 src/DotNetPathUtils/PathEnvironmentHelperLog.cs create mode 100644 src/DotNetPathUtils/PathUtilsOptions.cs diff --git a/src/DotNetPathUtils.Tests/PathEnvironmentHelperTests.cs b/src/DotNetPathUtils.Tests/PathEnvironmentHelperTests.cs index c455ebf..d391e68 100644 --- a/src/DotNetPathUtils.Tests/PathEnvironmentHelperTests.cs +++ b/src/DotNetPathUtils.Tests/PathEnvironmentHelperTests.cs @@ -79,7 +79,8 @@ public async Task EnsureApplicationXdgConfigDirectoryIsInPath_Constructs_Correct // Arrange var appName = "MyCoolApp"; var xdgHome = "/home/user/.config"; - var expectedPath = Path.Combine(xdgHome, appName); + var expectedAppName = "." + appName.ToPascalCase(); + var expectedPath = Path.Combine(xdgHome, expectedAppName); _service.GetApplicationName().Returns(appName); _service.GetXdgConfigHome().Returns(xdgHome); diff --git a/src/DotNetPathUtils/DotNetPathUtils.csproj b/src/DotNetPathUtils/DotNetPathUtils.csproj index 835b182..f6b95a1 100644 --- a/src/DotNetPathUtils/DotNetPathUtils.csproj +++ b/src/DotNetPathUtils/DotNetPathUtils.csproj @@ -1,7 +1,7 @@  netstandard2.0 - latest + preview enable enable @@ -19,6 +19,7 @@ + diff --git a/src/DotNetPathUtils/Extensions.cs b/src/DotNetPathUtils/Extensions.cs new file mode 100644 index 0000000..1f91f31 --- /dev/null +++ b/src/DotNetPathUtils/Extensions.cs @@ -0,0 +1,13 @@ +internal static class StringExtensions +{ + extension(string str) + { + public string ToPascalCase() + { + if (string.IsNullOrEmpty(str)) + return str; + + return char.ToLowerInvariant(str[0]) + str.Substring(1); + } + } +} diff --git a/src/DotNetPathUtils/IEnvironmentService.cs b/src/DotNetPathUtils/IEnvironmentService.cs index 0a8cb33..12a6798 100644 --- a/src/DotNetPathUtils/IEnvironmentService.cs +++ b/src/DotNetPathUtils/IEnvironmentService.cs @@ -6,7 +6,7 @@ public interface IEnvironmentService void SetEnvironmentVariable(string variable, string? value, EnvironmentVariableTarget target); string GetFullPath(string path); void CreateDirectory(string path); - string? GetApplicationName(); + string GetApplicationName(); string GetXdgConfigHome(); void BroadcastEnvironmentChange(); bool IsWindows(); diff --git a/src/DotNetPathUtils/PathEnvironmentHelper.cs b/src/DotNetPathUtils/PathEnvironmentHelper.cs index 7f43999..63c3005 100644 --- a/src/DotNetPathUtils/PathEnvironmentHelper.cs +++ b/src/DotNetPathUtils/PathEnvironmentHelper.cs @@ -1,4 +1,5 @@ using System.Security; +using Microsoft.Extensions.Logging; namespace DotNetPathUtils; @@ -6,31 +7,52 @@ public class PathEnvironmentHelper { private readonly IEnvironmentService _service; private readonly string _pathVariableName; + private readonly ILogger? logger; - public PathEnvironmentHelper(IEnvironmentService service) - : this(service, "PATH") { } + public PathEnvironmentHelper( + IEnvironmentService service, + ILogger? logger = null + ) + : this(service, "PATH") + { + this.logger = logger; + } internal PathEnvironmentHelper(IEnvironmentService service, string pathVariableName) { - _service = service ?? throw new ArgumentNullException(nameof(service)); - _pathVariableName = - pathVariableName ?? throw new ArgumentNullException(nameof(pathVariableName)); + if (string.IsNullOrWhiteSpace(pathVariableName)) + throw new ArgumentNullException(nameof(pathVariableName)); + { + _service = service ?? throw new ArgumentNullException(nameof(service)); + _pathVariableName = + pathVariableName ?? throw new ArgumentNullException(nameof(pathVariableName)); + } } public PathUpdateResult EnsureApplicationXdgConfigDirectoryIsInPath( EnvironmentVariableTarget target = EnvironmentVariableTarget.User, - string? appName = null + string? appName = null, + PathUtilsOptions? options = null ) { - appName ??= _service.GetApplicationName(); - if (string.IsNullOrWhiteSpace(appName)) + string name = appName ?? _service.GetApplicationName(); + if (string.IsNullOrWhiteSpace(name)) return PathUpdateResult.Error; + options ??= PathUtilsOptions.Default; + + if (options.DirectoryNameCase == DirectoryNameCase.PascalCase) + { + name = name.ToPascalCase(); + } + + if (options.PrefixWithPeriod && !name!.StartsWith(".")) + name = '.' + name; string configHome = _service.GetXdgConfigHome(); if (string.IsNullOrWhiteSpace(configHome)) return PathUpdateResult.Error; - string appConfigPath = Path.Combine(configHome, appName); + string appConfigPath = Path.Combine(configHome, name); return EnsureDirectoryIsInPath(appConfigPath, target); } @@ -49,6 +71,7 @@ public PathUpdateResult EnsureDirectoryIsInPath( } catch (Exception ex) { + logger?.DirectoryCreationFailed(directoryPath, ex.Message); return PathUpdateResult.Error; } @@ -80,6 +103,7 @@ .. currentPathVariable string normalizedExisting = _service .GetFullPath(p) .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return normalizedExisting.Equals( normalizedDirectoryToAdd, StringComparison.OrdinalIgnoreCase @@ -121,7 +145,6 @@ public PathRemoveResult RemoveApplicationXdgConfigDirectoryFromPath( EnvironmentVariableTarget target = EnvironmentVariableTarget.User ) { - // This method should also be updated to use the generic RemoveDirectoryFromPath string? appName = _service.GetApplicationName(); if (string.IsNullOrWhiteSpace(appName)) return PathRemoveResult.Error; diff --git a/src/DotNetPathUtils/PathEnvironmentHelperLog.cs b/src/DotNetPathUtils/PathEnvironmentHelperLog.cs new file mode 100644 index 0000000..ddf72ed --- /dev/null +++ b/src/DotNetPathUtils/PathEnvironmentHelperLog.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Logging; + +namespace DotNetPathUtils; + +internal static partial class PathEnvironmentHelperLog +{ + [LoggerMessage(1000, LogLevel.Information, "Ensuring app config path '{Path}' is in PATH.")] + public static partial void EnsuringAppConfigPath(this ILogger logger, string path); + + [LoggerMessage(1001, LogLevel.Warning, "The path '{Path}' already exists in PATH.")] + public static partial void PathAlreadyExists(this ILogger logger, string path); + + [LoggerMessage(1002, LogLevel.Information, "Path '{Path}' was added to PATH.")] + public static partial void PathAdded(this ILogger logger, string path); + + [LoggerMessage(1003, LogLevel.Error, "Failed to create directory '{Path}': {Message}")] + public static partial void DirectoryCreationFailed( + this ILogger logger, + string path, + string message + ); + + [LoggerMessage(1004, LogLevel.Error, "Exception setting environment variable: {Message}")] + public static partial void SetEnvVarFailed(this ILogger logger, string message); + + [LoggerMessage(1005, LogLevel.Information, "Removing path '{Path}' from PATH.")] + public static partial void RemovingPath(this ILogger logger, string path); + + [LoggerMessage(1006, LogLevel.Warning, "Path '{Path}' not found in PATH.")] + public static partial void PathNotFound(this ILogger logger, string path); + + [LoggerMessage(1007, LogLevel.Information, "Path '{Path}' was removed from PATH.")] + public static partial void PathRemoved(this ILogger logger, string path); +} diff --git a/src/DotNetPathUtils/PathUtilsOptions.cs b/src/DotNetPathUtils/PathUtilsOptions.cs new file mode 100644 index 0000000..ecc71af --- /dev/null +++ b/src/DotNetPathUtils/PathUtilsOptions.cs @@ -0,0 +1,13 @@ +namespace DotNetPathUtils; + +public record PathUtilsOptions +{ + public bool PrefixWithPeriod { get; } = true; + public DirectoryNameCase DirectoryNameCase { get; } + public static readonly PathUtilsOptions Default = new(); +} + +public enum DirectoryNameCase +{ + PascalCase, +} diff --git a/src/DotNetPathUtils/SystemEnvironmentService.cs b/src/DotNetPathUtils/SystemEnvironmentService.cs index 6fe8b5b..988ccb4 100644 --- a/src/DotNetPathUtils/SystemEnvironmentService.cs +++ b/src/DotNetPathUtils/SystemEnvironmentService.cs @@ -19,7 +19,11 @@ EnvironmentVariableTarget target public void CreateDirectory(string path) => Directory.CreateDirectory(path); - public string? GetApplicationName() => Assembly.GetEntryAssembly()?.GetName().Name; + public string GetApplicationName() => + Assembly.GetEntryAssembly()?.GetName().Name + ?? throw new InvalidOperationException( + "Unable to determine application name. Ensure the entry assembly is set correctly." + ); public string GetXdgConfigHome() => BaseDirectory.ConfigHome; From f6a1e20a48169cbeb8b512e7c6223c158880c7d7 Mon Sep 17 00:00:00 2001 From: henry-js <79054685+henry-js@users.noreply.github.com> Date: Tue, 29 Jul 2025 01:56:24 +0100 Subject: [PATCH 2/2] fix: ToPascalCase -> ToCamelCase --- .../PathEnvironmentHelperTests.cs | 2 +- src/DotNetPathUtils/Extensions.cs | 11 +++++------ src/DotNetPathUtils/PathEnvironmentHelper.cs | 4 ++-- src/DotNetPathUtils/PathUtilsOptions.cs | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/DotNetPathUtils.Tests/PathEnvironmentHelperTests.cs b/src/DotNetPathUtils.Tests/PathEnvironmentHelperTests.cs index d391e68..c6691e3 100644 --- a/src/DotNetPathUtils.Tests/PathEnvironmentHelperTests.cs +++ b/src/DotNetPathUtils.Tests/PathEnvironmentHelperTests.cs @@ -79,7 +79,7 @@ public async Task EnsureApplicationXdgConfigDirectoryIsInPath_Constructs_Correct // Arrange var appName = "MyCoolApp"; var xdgHome = "/home/user/.config"; - var expectedAppName = "." + appName.ToPascalCase(); + var expectedAppName = "." + appName.ToCamelCase(); var expectedPath = Path.Combine(xdgHome, expectedAppName); _service.GetApplicationName().Returns(appName); diff --git a/src/DotNetPathUtils/Extensions.cs b/src/DotNetPathUtils/Extensions.cs index 1f91f31..c9e62e1 100644 --- a/src/DotNetPathUtils/Extensions.cs +++ b/src/DotNetPathUtils/Extensions.cs @@ -1,13 +1,12 @@ internal static class StringExtensions { - extension(string str) + public static string ToCamelCase(this string str) { - public string ToPascalCase() + if (string.IsNullOrEmpty(str)) { - if (string.IsNullOrEmpty(str)) - return str; - - return char.ToLowerInvariant(str[0]) + str.Substring(1); + return str; } + + return char.ToLowerInvariant(str[0]) + str.Substring(1); } } diff --git a/src/DotNetPathUtils/PathEnvironmentHelper.cs b/src/DotNetPathUtils/PathEnvironmentHelper.cs index 63c3005..7b77057 100644 --- a/src/DotNetPathUtils/PathEnvironmentHelper.cs +++ b/src/DotNetPathUtils/PathEnvironmentHelper.cs @@ -41,9 +41,9 @@ public PathUpdateResult EnsureApplicationXdgConfigDirectoryIsInPath( options ??= PathUtilsOptions.Default; - if (options.DirectoryNameCase == DirectoryNameCase.PascalCase) + if (options.DirectoryNameCase == DirectoryNameCase.CamelCase) { - name = name.ToPascalCase(); + name = name.ToCamelCase(); } if (options.PrefixWithPeriod && !name!.StartsWith(".")) diff --git a/src/DotNetPathUtils/PathUtilsOptions.cs b/src/DotNetPathUtils/PathUtilsOptions.cs index ecc71af..c8a9c59 100644 --- a/src/DotNetPathUtils/PathUtilsOptions.cs +++ b/src/DotNetPathUtils/PathUtilsOptions.cs @@ -9,5 +9,5 @@ public record PathUtilsOptions public enum DirectoryNameCase { - PascalCase, + CamelCase, }