Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context performance improvement using deferred evaluation #203

Merged
merged 14 commits into from
Oct 11, 2024
1 change: 1 addition & 0 deletions docs/mdsource/repom.generated.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Properties:
- `name`: Name of the menu item. ([Text](repository_action_types.md#text))
- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate))
- `context`: The context in which the action is available. ([Context](repository_action_types.md#context))
- `is-deferred`: Whether the folder is deferred. ([Predicate](repository_action_types.md#predicate))

### Example

Expand Down
4 changes: 3 additions & 1 deletion docs/repom.generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Properties:
- `name`: Name of the menu item. ([Text](repository_action_types.md#text))
- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate))
- `context`: The context in which the action is available. ([Context](repository_action_types.md#context))
- `is-deferred`: Whether the folder is deferred. ([Predicate](repository_action_types.md#predicate))

### Example

Expand All @@ -129,6 +130,7 @@ action-menu:
- type: folder@1
name: My folder
active: true
is-deferred: false
actions:
- type: url@1
name: 'Browse to remote {{ repository.remotes[0].key }}'
Expand All @@ -138,7 +140,7 @@ action-menu:
name: 'wiki'
url: '{{ repository.remotes[0].url }}/wiki'
```
<sup><a href='/tests/RepoM.ActionMenu.Core.Tests/ActionMenu/IntegrationTests/Docs/folder@1-scenario01.testfile.yaml#L3-L19' title='Snippet source file'>snippet source</a> | <a href='#snippet-folder@1-scenario01' title='Start of snippet'>anchor</a></sup>
<sup><a href='/tests/RepoM.ActionMenu.Core.Tests/ActionMenu/IntegrationTests/Docs/folder@1-scenario01.testfile.yaml#L3-L20' title='Snippet source file'>snippet source</a> | <a href='#snippet-folder@1-scenario01' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, string>? _cachedValue;

public EnvironmentsCacheDecorator(IEnvironment decoratee)
{
_decoratee = decoratee ?? throw new ArgumentNullException(nameof(decoratee));
}

public Dictionary<string, string> GetEnvironmentVariables()
{
return _cachedValue ??= _decoratee.GetEnvironmentVariables();
}
}
11 changes: 11 additions & 0 deletions src/RepoM.ActionMenu.Core/Abstractions/IEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace RepoM.ActionMenu.Core.Abstractions;

using System.Collections.Generic;

/// <summary>
/// Abstraction of the environment.
/// </summary>
internal interface IEnvironment
{
Dictionary<string, string> GetEnvironmentVariables();
}
19 changes: 19 additions & 0 deletions src/RepoM.ActionMenu.Core/Abstractions/OperatingSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace RepoM.ActionMenu.Core.Abstractions;

using System;
using System.IO.Abstractions;

internal class OperatingSystem
{
public OperatingSystem(IFileSystem fileSystem, IEnvironment environment)
{
FileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
Environment = environment ?? throw new ArgumentNullException(nameof(environment));
}

/// <inheritdoc cref="IFileSystem"/>
public IFileSystem FileSystem { get; }

/// <inheritdoc cref="IEnvironment"/>
public IEnvironment Environment { get; }
}
36 changes: 36 additions & 0 deletions src/RepoM.ActionMenu.Core/Abstractions/SystemEnvironments.cs
Original file line number Diff line number Diff line change
@@ -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<string, string> GetEnvironmentVariables()
{
var env = new Dictionary<string, string>(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;
}
}
38 changes: 10 additions & 28 deletions src/RepoM.ActionMenu.Core/ActionMenu/Context/EnvScriptObject.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace RepoM.ActionMenu.Core.ActionMenu.Context;

using System;
using System.Collections;
using System.Collections.Generic;
using Scriban;
using Scriban.Parsing;
Expand All @@ -11,31 +10,17 @@ internal sealed class EnvScriptObject : IScriptObject
{
private readonly IDictionary<string, string> _env;

public static EnvScriptObject Create()
public EnvScriptObject(IDictionary<string, string> envVars)
{
var env = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

foreach (DictionaryEntry item in Environment.GetEnvironmentVariables()) // difficult to test.
{
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 new EnvScriptObject(env);
_env = envVars ?? throw new ArgumentNullException(nameof(envVars));
}

public EnvScriptObject(IDictionary<string, string> envVars)
public int Count => _env.Count;

public bool IsReadOnly
{
_env = envVars;
get => true;
set => _ = value;
}

public IEnumerable<string> GetMembers()
Expand Down Expand Up @@ -80,16 +65,13 @@ public void SetReadOnly(string member, bool readOnly)
// intentionally do nothing
}

public IScriptObject Clone(bool deep)
public EnvScriptObject Clone()
{
return new EnvScriptObject(new Dictionary<string, string>(_env));
}

public int Count => _env.Count;

public bool IsReadOnly
IScriptObject IScriptObject.Clone(bool deep)
{
get => true;
set => _ = value;
return Clone();
}
}
15 changes: 12 additions & 3 deletions src/RepoM.ActionMenu.Core/ActionMenu/Context/EnvSetScriptObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ internal sealed class EnvSetScriptObject : IScriptObject, IDisposable
{
private FastStack<EnvScriptObject> _stack = new(10);

public EnvSetScriptObject(IDictionary<string, string> envVars) : this(new EnvScriptObject(envVars))
{
}

public EnvSetScriptObject(EnvScriptObject @base)
{
_ = @base ?? throw new ArgumentNullException(nameof(@base));
Expand Down Expand Up @@ -74,10 +78,10 @@ public void SetReadOnly(string member, bool readOnly)
// intentionally do nothing
}

public IScriptObject Clone(bool deep)
public EnvSetScriptObject Clone()
{
EnvScriptObject[] items = _stack.Items;
var result = new EnvSetScriptObject((EnvScriptObject)items[0].Clone(true));
var result = new EnvSetScriptObject(items[0].Clone());

if (items.Length <= 1)
{
Expand All @@ -88,7 +92,7 @@ public IScriptObject Clone(bool deep)
{
if (items[i] != null)
{
result.Push((EnvScriptObject)items[i].Clone(true));
result.Push(items[i].Clone());
}
}

Expand Down Expand Up @@ -122,4 +126,9 @@ public void Dispose()

return null;
}

IScriptObject IScriptObject.Clone(bool deep)
{
return Clone();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> Map
name,
repository,
context,
captureScope: false,
async ctx => await EnumerateRemotes(ctx.Repository).ConfigureAwait(false))
{
CanExecute = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace RepoM.ActionMenu.Core.ActionMenu.Model.ActionMenus.Folder;
/// <snippet name='folder@1-scenario01' mode='snippet' />
/// </example>
[RepositoryAction(TYPE_VALUE)]
internal sealed class RepositoryActionFolderV1 : IMenuAction, IName, IMenuActions, IContext, IDeferred
internal sealed class RepositoryActionFolderV1 : IMenuAction, IName, IMenuActions, IContext
{
public const string TYPE_VALUE = "folder@1";
internal const string EXAMPLE_1 = TYPE_VALUE + "-scenario01";
Expand All @@ -38,7 +38,9 @@ public string Type
/// <inheritdoc cref="IContext.Context"/>
public Context? Context { get; set; }

// Not documented as it is not implemented yet. Deferred with cloning the context doesn't work yet (https://github.com/coenm/RepoM/issues/85)
/// <summary>
/// Whether the folder is deferred.
/// </summary>
[Predicate(false)]
public Predicate IsDeferred { get; set; } = new ScribanPredicate();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace RepoM.ActionMenu.Core.ActionMenu.Model.ActionMenus.Folder;
[UsedImplicitly]
internal class RepositoryActionFolderV1Mapper : ActionToRepositoryActionMapperBase<RepositoryActionFolderV1>
{

protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> MapAsync(RepositoryActionFolderV1 action, IActionMenuGenerationContext context, IRepository repository)
{
var name = await context.RenderStringAsync(action.Name).ConfigureAwait(false);
Expand All @@ -22,20 +21,14 @@ protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> Map
yield break;
}

#pragma warning disable S2583 // Change this condition so that it does not always evaluate to 'False'. Some code paths are unreachable
// Deferred with cloning the context doesn't work yet (https://github.com/coenm/RepoM/issues/85) therefore, set to false.
#pragma warning disable S125
// var isDeferred = await action.IsDeferred.EvaluateAsync(context).ConfigureAwait(false);
#pragma warning restore S125
var isDeferred = false;
var isDeferred = await action.IsDeferred.EvaluateAsync(context).ConfigureAwait(false);

if (isDeferred)
{
yield return new DeferredSubActionsUserInterfaceRepositoryAction(
name,
repository,
context,
action.Actions != null,
async ctx => await ctx.AddActionMenusAsyncArray(action.Actions).ConfigureAwait(false))
{
CanExecute = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> Map
name,
repository,
context,
false,
_ =>
Task.FromResult(repository.LocalBranches
.Take(50)
Expand All @@ -42,7 +41,6 @@ protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> Map
remoteBranchesTranslated,
repository,
context,
false,
_ =>
{
UserInterfaceRepositoryActionBase[] remoteBranches = repository
Expand Down
7 changes: 7 additions & 0 deletions src/RepoM.ActionMenu.Core/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -14,6 +15,7 @@ namespace RepoM.ActionMenu.Core;
using RepoM.ActionMenu.Interface.YamlModel;
using RepoM.Core.Plugin.RepositoryOrdering.Configuration;
using SimpleInjector;
using OperatingSystem = Abstractions.OperatingSystem;

public static class Bootstrapper
{
Expand Down Expand Up @@ -55,6 +57,11 @@ private static void RegisterPrivateTypes(Container container)

container.RegisterSingleton<IFileReader, FileReader>();
container.RegisterDecorator<IFileReader, CacheFileReaderDecorator>(Lifestyle.Singleton);

container.RegisterInstance<IEnvironment>(SystemEnvironments.Instance);
container.RegisterDecorator<IEnvironment, EnvironmentsCacheDecorator>(Lifestyle.Singleton);

container.RegisterSingleton<OperatingSystem>();
}

/// <summary>
Expand Down
Loading
Loading