diff --git a/src/RepoM.ActionMenu.Core/Abstractions/EnvironmentsCacheDecorator.cs b/src/RepoM.ActionMenu.Core/Abstractions/EnvironmentsCacheDecorator.cs new file mode 100644 index 00000000..311f11c9 --- /dev/null +++ b/src/RepoM.ActionMenu.Core/Abstractions/EnvironmentsCacheDecorator.cs @@ -0,0 +1,20 @@ +namespace RepoM.ActionMenu.Core.Abstractions; + +using System; +using System.Collections.Generic; + +internal sealed class EnvironmentsCacheDecorator: IEnvironment +{ + private readonly IEnvironment _decoratee; + private Dictionary? _cachedValue; + + public EnvironmentsCacheDecorator(IEnvironment decoratee) + { + _decoratee = decoratee ?? throw new ArgumentNullException(nameof(decoratee)); + } + + public Dictionary GetEnvironmentVariables() + { + return _cachedValue ??= _decoratee.GetEnvironmentVariables(); + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.Core/Abstractions/IEnvironment.cs b/src/RepoM.ActionMenu.Core/Abstractions/IEnvironment.cs new file mode 100644 index 00000000..348062ea --- /dev/null +++ b/src/RepoM.ActionMenu.Core/Abstractions/IEnvironment.cs @@ -0,0 +1,8 @@ +namespace RepoM.ActionMenu.Core.Abstractions; + +using System.Collections.Generic; + +internal interface IEnvironment +{ + Dictionary GetEnvironmentVariables(); +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.Core/Abstractions/SystemEnvironments.cs b/src/RepoM.ActionMenu.Core/Abstractions/SystemEnvironments.cs new file mode 100644 index 00000000..bf6a6ad9 --- /dev/null +++ b/src/RepoM.ActionMenu.Core/Abstractions/SystemEnvironments.cs @@ -0,0 +1,36 @@ +namespace RepoM.ActionMenu.Core.Abstractions; + +using System; +using System.Collections; +using System.Collections.Generic; + +internal sealed class SystemEnvironments : IEnvironment +{ + private SystemEnvironments() + { + } + + public static SystemEnvironments Instance { get; } = new (); + + public Dictionary GetEnvironmentVariables() + { + var env = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (DictionaryEntry item in Environment.GetEnvironmentVariables()) + { + if (item.Key is not string key || string.IsNullOrEmpty(key)) + { + continue; + } + + if (item.Value is not string value) + { + continue; + } + + env.Add(key.Trim(), value); + } + + return env; + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.Core/ActionMenu/Context/EnvSetScriptObject.cs b/src/RepoM.ActionMenu.Core/ActionMenu/Context/EnvSetScriptObject.cs index 25f6c615..8ef55f63 100644 --- a/src/RepoM.ActionMenu.Core/ActionMenu/Context/EnvSetScriptObject.cs +++ b/src/RepoM.ActionMenu.Core/ActionMenu/Context/EnvSetScriptObject.cs @@ -12,6 +12,10 @@ internal sealed class EnvSetScriptObject : IScriptObject, IDisposable { private FastStack _stack = new(10); + public EnvSetScriptObject(IDictionary envVars) : this(new EnvScriptObject(envVars)) + { + } + public EnvSetScriptObject(EnvScriptObject @base) { _ = @base ?? throw new ArgumentNullException(nameof(@base)); diff --git a/src/RepoM.ActionMenu.Core/Bootstrapper.cs b/src/RepoM.ActionMenu.Core/Bootstrapper.cs index 017ef592..791d506d 100644 --- a/src/RepoM.ActionMenu.Core/Bootstrapper.cs +++ b/src/RepoM.ActionMenu.Core/Bootstrapper.cs @@ -5,6 +5,7 @@ namespace RepoM.ActionMenu.Core; using System.Diagnostics; using System.Linq; using System.Reflection; +using RepoM.ActionMenu.Core.Abstractions; using RepoM.ActionMenu.Core.ConfigReader; using RepoM.ActionMenu.Core.Misc; using RepoM.ActionMenu.Core.Model; @@ -55,6 +56,9 @@ private static void RegisterPrivateTypes(Container container) container.RegisterSingleton(); container.RegisterDecorator(Lifestyle.Singleton); + + container.RegisterInstance(SystemEnvironments.Instance); + container.RegisterDecorator(Lifestyle.Singleton); } /// diff --git a/src/RepoM.ActionMenu.Core/Model/ActionMenuGenerationContext.cs b/src/RepoM.ActionMenu.Core/Model/ActionMenuGenerationContext.cs index 505eeb60..efc369d4 100644 --- a/src/RepoM.ActionMenu.Core/Model/ActionMenuGenerationContext.cs +++ b/src/RepoM.ActionMenu.Core/Model/ActionMenuGenerationContext.cs @@ -5,6 +5,7 @@ namespace RepoM.ActionMenu.Core.Model; using System.IO.Abstractions; using System.Linq; using System.Threading.Tasks; +using RepoM.ActionMenu.Core.Abstractions; using RepoM.ActionMenu.Core.ActionMenu.Context; using RepoM.ActionMenu.Core.Misc; using RepoM.ActionMenu.Core.Yaml.Model.ActionContext; @@ -23,6 +24,7 @@ namespace RepoM.ActionMenu.Core.Model; internal class ActionMenuGenerationContext : TemplateContext, IActionMenuGenerationContext, IContextMenuActionMenuGenerationContext { private readonly ITemplateParser _templateParser; + private readonly IEnvironment _environment; private readonly ITemplateContextRegistration[] _functionsArray; private readonly IActionMenuDeserializer _deserializer; private readonly IActionToRepositoryActionMapper[] _repositoryActionMappers; @@ -35,12 +37,14 @@ internal class ActionMenuGenerationContext : TemplateContext, IActionMenuGenerat public ActionMenuGenerationContext( ITemplateParser templateParser, IFileSystem fileSystem, + IEnvironment environment, ITemplateContextRegistration[] functionsArray, IActionToRepositoryActionMapper[] repositoryActionMappers, IActionMenuDeserializer deserializer, IContextActionProcessor[] contextActionMappers) { _templateParser = templateParser ?? throw new ArgumentNullException(nameof(templateParser)); + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); _functionsArray = functionsArray ?? throw new ArgumentNullException(nameof(functionsArray)); FileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); _repositoryActionMappers = repositoryActionMappers ?? throw new ArgumentNullException(nameof(repositoryActionMappers)); @@ -102,6 +106,7 @@ public IActionMenuGenerationContext Clone() var result = new ActionMenuGenerationContext( _templateParser, FileSystem, + _environment, _functionsArray, _repositoryActionMappers, _deserializer, @@ -115,8 +120,7 @@ internal void Initialize(IRepository repository) { Repository = repository ?? throw new ArgumentNullException(nameof(repository)); - _rootScriptObject = CreateAndInitRepoMScriptObject( - new EnvSetScriptObject(EnvScriptObject.Instance)); + _rootScriptObject = CreateAndInitRepoMScriptObject(new EnvSetScriptObject(_environment.GetEnvironmentVariables())); foreach (ITemplateContextRegistration contextRegistration in _functionsArray) { diff --git a/src/RepoM.ActionMenu.Core/Services/UserInterfaceActionMenuFactory.cs b/src/RepoM.ActionMenu.Core/Services/UserInterfaceActionMenuFactory.cs index 873ca69a..ec07bb32 100644 --- a/src/RepoM.ActionMenu.Core/Services/UserInterfaceActionMenuFactory.cs +++ b/src/RepoM.ActionMenu.Core/Services/UserInterfaceActionMenuFactory.cs @@ -7,6 +7,7 @@ namespace RepoM.ActionMenu.Core.Services; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using RepoM.ActionMenu.Core; +using RepoM.ActionMenu.Core.Abstractions; using RepoM.ActionMenu.Core.ConfigReader; using RepoM.ActionMenu.Core.Misc; using RepoM.ActionMenu.Core.Model; @@ -25,6 +26,7 @@ namespace RepoM.ActionMenu.Core.Services; internal class UserInterfaceActionMenuFactory : IUserInterfaceActionMenuFactory { private readonly IFileSystem _fileSystem; + private readonly IEnvironment _environment; private readonly ITemplateParser _templateParser; private readonly ITemplateContextRegistration[] _plugins; private readonly IActionToRepositoryActionMapper[] _mappers; @@ -34,7 +36,8 @@ internal class UserInterfaceActionMenuFactory : IUserInterfaceActionMenuFactory private readonly IContextActionProcessor[] _contextActionMappers; public UserInterfaceActionMenuFactory( - IFileSystem fileSystem, + IFileSystem fileSystem, + IEnvironment environment, ITemplateParser templateParser, IEnumerable plugins, IEnumerable mappers, @@ -43,6 +46,7 @@ public UserInterfaceActionMenuFactory( ILogger logger) { _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); _templateParser = templateParser ?? throw new ArgumentNullException(nameof(templateParser)); _plugins = plugins.ToArray(); _mappers = mappers.ToArray(); @@ -101,7 +105,7 @@ private async Task CreateActionMenuGenerationContex await Task.Yield(); _logger.LogTrace("CreateActionMenuGenerationContext ActionMenuGenerationContext ctor"); - var actionMenuGenerationContext = new ActionMenuGenerationContext(_templateParser, _fileSystem, _plugins, _mappers, _deserializer, _contextActionMappers); + var actionMenuGenerationContext = new ActionMenuGenerationContext(_templateParser, _fileSystem, _environment, _plugins, _mappers, _deserializer, _contextActionMappers); actionMenuGenerationContext.Initialize(repository); return actionMenuGenerationContext; } diff --git a/tests/RepoM.ActionMenu.Core.Tests/Model/DisposableContextScriptObjectTests.cs b/tests/RepoM.ActionMenu.Core.Tests/Model/DisposableContextScriptObjectTests.cs index 529d65d2..683d12d6 100644 --- a/tests/RepoM.ActionMenu.Core.Tests/Model/DisposableContextScriptObjectTests.cs +++ b/tests/RepoM.ActionMenu.Core.Tests/Model/DisposableContextScriptObjectTests.cs @@ -7,7 +7,6 @@ namespace RepoM.ActionMenu.Core.Tests.Model; using System.Threading.Tasks; using FakeItEasy; using FluentAssertions; -using RepoM.ActionMenu.Core.ActionMenu.Context; using RepoM.ActionMenu.Core.Misc; using RepoM.ActionMenu.Core.Model; using RepoM.ActionMenu.Core.Yaml.Model.ActionContext; @@ -17,6 +16,7 @@ namespace RepoM.ActionMenu.Core.Tests.Model; using RepoM.ActionMenu.Interface.YamlModel.Templating; using RepoM.Core.Plugin.Repository; using Xunit; +using IEnvironment = RepoM.ActionMenu.Core.Abstractions.IEnvironment; public class DisposableContextScriptObjectTests { @@ -26,21 +26,25 @@ public class DisposableContextScriptObjectTests private readonly ITemplateContextRegistration[] _functionsArray = []; private readonly IActionToRepositoryActionMapper[] _mapper = []; private readonly IActionMenuDeserializer _deserializer = A.Fake(); - private readonly ActionMenuGenerationContext _context; + private ActionMenuGenerationContext _context; private readonly IContextActionProcessor[] _mappers; private readonly DisposableContextScriptObject _sut; + private readonly IEnvironment _environment = A.Fake(); public DisposableContextScriptObjectTests() { + A.CallTo(() => _environment.GetEnvironmentVariables()).Returns(new Dictionary() + { + { "x", "y" }, + }); + _mappers = [ A.Fake(), A.Fake(), ]; - _context = new ActionMenuGenerationContext(_templateParser, _fileSystem, _functionsArray, _mapper, _deserializer, _mappers); - _context.Initialize(_repository); - - _sut = new DisposableContextScriptObject(_context, _mappers); + _context = CreateContext(); + _sut = CreateSut(); } [Fact] @@ -129,16 +133,17 @@ public async Task AddContextActionAsync_ShouldUseMapperForProcessing_WhenMapperF public void PushEnvironmentVariable_ShouldAddVariables() { // arrange - var env = new EnvScriptObject( - new Dictionary + A.CallTo(() => _environment.GetEnvironmentVariables()) + .Returns(new Dictionary { { "x", "y" }, }); - _context.Env.Push(env); - IScope sut = new DisposableContextScriptObject(_context, _mappers); + + _context = CreateContext(); + IScope sut = CreateSut(); // assume - env.Count.Should().Be(1); + _context.Env.Count.Should().Be(1); // act sut.PushEnvironmentVariable( @@ -149,24 +154,25 @@ public void PushEnvironmentVariable_ShouldAddVariables() }); // assert - env.Count.Should().Be(3); - env.GetMembers().Should().BeEquivalentTo("x", "x1", "x2"); + _context.Env.Count.Should().Be(3); + _context.Env.GetMembers().Should().BeEquivalentTo("x", "x1", "x2"); } [Fact] public void PushEnvironmentVariable_ShouldAddVariables_Distinct() { // arrange - var env = new EnvScriptObject( - new Dictionary - { - { "x", "y" }, - }); - _context.Env.Push(env); - IScope sut = new DisposableContextScriptObject(_context, _mappers); + A.CallTo(() => _environment.GetEnvironmentVariables()) + .Returns(new Dictionary + { + { "x", "y" }, + }); + + _context = CreateContext(); + IScope sut = CreateSut(); // assume - env.Count.Should().Be(1); + _context.Env.Count.Should().Be(1); // act sut.PushEnvironmentVariable( @@ -177,22 +183,24 @@ public void PushEnvironmentVariable_ShouldAddVariables_Distinct() }); // assert - env.Count.Should().Be(2); - env.GetMembers().Should().BeEquivalentTo("x", "x1"); + _context.Env.Count.Should().Be(2); + _context.Env.GetMembers().Should().BeEquivalentTo("x", "x1"); } [Fact] public void Dispose_ShouldPopAllPushedEnvironmentVariables() { // arrange - var env = new EnvScriptObject( - new Dictionary - { - { "x1", "y" }, - { "x2", "yy" }, - }); - _context.Env.Push(env); - IScope sut = new DisposableContextScriptObject(_context, _mappers); + A.CallTo(() => _environment.GetEnvironmentVariables()) + .Returns(new Dictionary + { + { "x1", "y" }, + { "x2", "yy" }, + }); + + _context = CreateContext(); + IScope sut = CreateSut(); + sut.PushEnvironmentVariable( new Dictionary { @@ -207,13 +215,25 @@ public void Dispose_ShouldPopAllPushedEnvironmentVariables() }); // assume - env.Count.Should().Be(5); + _context.Env.Count.Should().Be(5); // act sut.Dispose(); // assert - env.Count.Should().Be(2); + _context.Env.Count.Should().Be(2); + } + + private ActionMenuGenerationContext CreateContext() + { + var context = new ActionMenuGenerationContext(_templateParser, _fileSystem, _environment, _functionsArray, _mapper, _deserializer, _mappers); + context.Initialize(_repository); + return context; + } + + private DisposableContextScriptObject CreateSut() + { + return new DisposableContextScriptObject(_context, _mappers); } }