Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions ExpressionEngine/ExpressionEngineDIExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Linq;
using System.Reflection;
using ExpressionEngine.Functions.Base;
using Microsoft.Extensions.DependencyInjection;

namespace ExpressionEngine
{
/// <summary>
/// Extension methods for service collection
/// </summary>
public static class ExpressionEngineDiExtensions
{
/// <summary>
/// Find function implementations registered with <see cref="FunctionRegistrationAttribute"/>.
///
/// The function only scans the given type's assembly
/// </summary>
/// <param name="serviceCollection"></param>
/// <typeparam name="T">Assembly to scan</typeparam>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <returns></returns>
public static IServiceCollection WithFunctionDiscovery<T>(this IServiceCollection serviceCollection)
{
var addFunctionMethodInfo = typeof(ExpressionEngineDiExtensions).GetMethod(nameof(AddFunction));

if (addFunctionMethodInfo == null)
throw new Exception();

var functions =
typeof(T)
.Assembly
.GetTypes()
.Where(t => t.GetCustomAttributes<FunctionRegistrationAttribute>().Any());

foreach (var function in functions)
{
var generic = addFunctionMethodInfo.MakeGenericMethod(function);
generic.Invoke(null, new object[] {serviceCollection});
}

return serviceCollection;
}

/// <summary>
///
/// </summary>
/// <param name="serviceCollection"></param>
/// <typeparam name="T"></typeparam>
private static void AddFunction<T>(IServiceCollection serviceCollection) where T : class, IFunction
{
serviceCollection.AddFunction<T>();
}
}
}
101 changes: 11 additions & 90 deletions ExpressionEngine/FlowRunnerDependencyExtension.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
using System;
using ExpressionEngine.Functions.Base;
using ExpressionEngine.Functions.CustomException;
using ExpressionEngine.Functions.Implementations.CollectionFunctions;
using ExpressionEngine.Functions.Implementations.ConversionFunctions;
using ExpressionEngine.Functions.Implementations.LogicalComparisonFunctions;
using ExpressionEngine.Functions.Implementations.StringFunctions;
using ExpressionEngine.Functions.Math;
using Microsoft.Extensions.DependencyInjection;

namespace ExpressionEngine
Expand All @@ -15,6 +10,7 @@ namespace ExpressionEngine
/// </summary>
public static class FlowRunnerDependencyExtension
{

/// <summary>
/// Add necessary dependencies inorder to use expression engine.
/// </summary>
Expand All @@ -24,35 +20,30 @@ public static void AddExpressionEngine(this IServiceCollection services)
services.AddScoped<IExpressionEngine, ExpressionEngine>();
services.AddScoped<ExpressionGrammar>();

AddStringFunctions(services);
AddCollectionFunction(services);
AddConversionFunction(services);
AddLogicalComparisonFunctions(services);
AddMathFunctions(services);

services.RegisterTransientFunctionAlias<LengthFunction>("length");
services.RegisterTransientFunctionAlias<GreaterFunction>("greater");
services.WithFunctionDiscovery<Functions.Functions>();
}

/// <summary>
/// Register function to be used in expression, function implementation must implement <see cref="IFunction"/>.
/// </summary>
/// <param name="services">Services which to add function metadata</param>
/// <param name="functionName">name of function used to invoke it</param>
/// <typeparam name="T">Function implementation</typeparam>
[Obsolete("Use '.AddFunction().AsTransient().WithAlias(<string>)' instead")]
public static void RegisterTransientFunctionAlias<T>(this IServiceCollection services, string functionName)
where T : class, IFunction
{
services.AddTransient<T>();
services.AddSingleton(new FunctionMetadata(typeof(T), functionName));
}

/// <summary>
/// Register function to be used in expression, function implementation must implement <see cref="IFunction"/>.
/// </summary>
/// <param name="services">Services which to add function metadata</param>
/// <param name="functionName">name of function used to invoke it</param>
/// <typeparam name="T">Function implementation</typeparam>
[Obsolete("Use '.AddFunction().AsScoped().WithAlias(<string>)' instead")]
public static void RegisterScopedFunctionAlias<T>(this IServiceCollection services, string functionName)
where T : class, IFunction
{
Expand All @@ -67,6 +58,7 @@ public static void RegisterScopedFunctionAlias<T>(this IServiceCollection servic
/// <param name="functionName">name of function used to invoke it</param>
/// <param name="implementationFactory"></param>
/// <typeparam name="T">Function implementation</typeparam>
[Obsolete("Use '.AddFunction<T>(<factory>).AsScoped().WithAlias(<string>)' instead")]
public static void RegisterScopedFunctionAlias<T>(this IServiceCollection services, string functionName,
Func<IServiceProvider, T> implementationFactory)
where T : class, IFunction
Expand All @@ -81,87 +73,16 @@ public static void RegisterScopedFunctionAlias<T>(this IServiceCollection servic
/// <param name="services"></param>
/// <param name="fromFunctionName">The name of the function, without function parenthesis</param>
/// <param name="toExpression">The full expression which is inserted</param>
public static void AddFunctionDefinition(this IServiceCollection services, string fromFunctionName, string toExpression)
public static void AddFunctionDefinition(this IServiceCollection services, string fromFunctionName,
string toExpression)
{
if (fromFunctionName.EndsWith("()"))
{
throw new ArgumentError($"{nameof(fromFunctionName)} cannot end in ()");
}

services.AddSingleton<IFunctionDefinition>(new FunctionDefinition{From = fromFunctionName + "()", To = toExpression});
}

private static void AddStringFunctions(IServiceCollection services)
{
services.RegisterTransientFunctionAlias<ConcatFunction>("concat");
services.RegisterTransientFunctionAlias<EndsWithFunction>("endsWith");
services.RegisterTransientFunctionAlias<FormatNumberFunction>("formatNumber");
services.RegisterTransientFunctionAlias<GuidFunction>("guid");
services.RegisterTransientFunctionAlias<IndexOfFunction>("indexOf");
services.RegisterTransientFunctionAlias<LastIndexOfFunction>("lastIndexOf");
services.RegisterTransientFunctionAlias<LengthFunction>("length");
services.RegisterTransientFunctionAlias<ReplaceFunction>("replace");
services.RegisterTransientFunctionAlias<SplitFunction>("split");
services.RegisterTransientFunctionAlias<StartsWithFunction>("startsWith");
services.RegisterTransientFunctionAlias<SubstringFunction>("substring");
services.RegisterTransientFunctionAlias<ToLowerFunction>("toLower");
services.RegisterTransientFunctionAlias<ToUpperFunction>("toUpper");
services.RegisterTransientFunctionAlias<TrimFunction>("trim");
}

private static void AddCollectionFunction(IServiceCollection services)
{
services.RegisterTransientFunctionAlias<ContainsFunction>("contains");
services.RegisterTransientFunctionAlias<EmptyFunction>("empty");
services.RegisterTransientFunctionAlias<FirstFunction>("first");
services.RegisterTransientFunctionAlias<InterSectionFunction>("intersection");
services.RegisterTransientFunctionAlias<JoinFunction>("join");
services.RegisterTransientFunctionAlias<LastFunction>("last");
services.RegisterTransientFunctionAlias<LengthFunction>("length");
services.RegisterTransientFunctionAlias<SkipFunction>("skip");
services.RegisterTransientFunctionAlias<TakeFunction>("take");
services.RegisterTransientFunctionAlias<UnionFunction>("union");
}

private static void AddConversionFunction(IServiceCollection services)
{
services.RegisterTransientFunctionAlias<ArrayFunction>("array");
services.RegisterTransientFunctionAlias<Base64Function>("base64");
services.RegisterTransientFunctionAlias<Base64ToBinaryFunction>("base64ToBinary");
services.RegisterTransientFunctionAlias<Base64ToStringFunction>("base64ToString");
services.RegisterTransientFunctionAlias<BinaryFunction>("binary");
services.RegisterTransientFunctionAlias<BoolFunction>("bool");
services.RegisterTransientFunctionAlias<CreateArrayFunction>("createArray");
services.RegisterTransientFunctionAlias<DataUriFunction>("dataUri");
services.RegisterTransientFunctionAlias<DataUriToBinaryFunction>("dataUriToBinary");
services.RegisterTransientFunctionAlias<FloatFunction>("float");
services.RegisterTransientFunctionAlias<IntFunction>("int");
}

private static void AddLogicalComparisonFunctions(IServiceCollection services)
{
services.RegisterTransientFunctionAlias<AndFunction>("and");
services.RegisterTransientFunctionAlias<EqualFunction>("equal");
services.RegisterTransientFunctionAlias<GreaterFunction>("greater");
services.RegisterTransientFunctionAlias<GreaterOrEqualsFunction>("greaterOrEquals");
services.RegisterTransientFunctionAlias<IfFunction>("if");
services.RegisterTransientFunctionAlias<LessFunction>("less");
services.RegisterTransientFunctionAlias<LessOrEqualsFunction>("lessOrEquals");
services.RegisterTransientFunctionAlias<NotFunction>("not");
services.RegisterTransientFunctionAlias<OrFunction>("or");
}

private static void AddMathFunctions(IServiceCollection services)
{
services.RegisterTransientFunctionAlias<AddFunction>("add");
services.RegisterTransientFunctionAlias<DivFunction>("div");
services.RegisterTransientFunctionAlias<MaxFunction>("max");
services.RegisterTransientFunctionAlias<MinFunction>("min");
services.RegisterTransientFunctionAlias<ModFunction>("mod");
services.RegisterTransientFunctionAlias<MulFunction>("mul");
services.RegisterTransientFunctionAlias<RandFunction>("rand");
services.RegisterTransientFunctionAlias<RangeFunction>("range");
services.RegisterTransientFunctionAlias<SubFunction>("sub");
services.AddSingleton<IFunctionDefinition>(new FunctionDefinition
{From = fromFunctionName + "()", To = toExpression});
}
}
}
141 changes: 141 additions & 0 deletions ExpressionEngine/FunctionBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System;
using System.Reflection;
using ExpressionEngine.Functions.Base;
using Microsoft.Extensions.DependencyInjection;

namespace ExpressionEngine
{
/// <summary>
///
/// </summary>
public static class FunctionBuilderExt
{
/// <summary>
/// Initiate Function builder for registering a Function, preloaded with <see cref="FunctionRegistrationAttribute"/>
/// </summary>
/// <param name="serviceCollection">ServiceCollection to which the function is registered</param>
/// <typeparam name="TFunction">Type of the Function</typeparam>
/// <returns></returns>
public static FunctionBuilder<TFunction> BuildFunction<TFunction>(this IServiceCollection serviceCollection)
where TFunction : class, IFunction
{
var t = new FunctionBuilder<TFunction>(serviceCollection);

var functionRegistrationAttributes = typeof(TFunction).GetCustomAttributes<FunctionRegistrationAttribute>();

foreach (var functionRegistrationAttribute in functionRegistrationAttributes)
{
t.SetScope(functionRegistrationAttribute.Scope);
t.WithAlias(functionRegistrationAttribute.FunctionName);
}

return t;
}

/// <summary>
/// Add function using <see cref="FunctionRegistrationAttribute"/>
/// </summary>
/// <param name="serviceCollection">ServiceCollection to which the function is registered</param>
/// <typeparam name="TFunction">Type of the Function</typeparam>
/// <returns></returns>
public static IServiceCollection AddFunction<TFunction>(this IServiceCollection serviceCollection)
where TFunction : class, IFunction
{
return serviceCollection.BuildFunction<TFunction>().Add();
}
}

/// <summary>
/// FunctionBuilder is responsible for adding a Function properly
/// </summary>
/// <typeparam name="T">Type of the Function</typeparam>
public class FunctionBuilder<T> where T : class, IFunction
{
private readonly IServiceCollection _serviceCollection;
private Scope _scope = Scope.Transient;

/// <summary>
/// Set scope without using As{Scope}
/// </summary>
/// <param name="scope"></param>
internal void SetScope(Scope scope)
{
_scope = scope;
}

/// <summary>
/// Create function builder
/// </summary>
/// <param name="serviceCollection">ServiceCollection to which the function is registered</param>
public FunctionBuilder(IServiceCollection serviceCollection)
{
_serviceCollection = serviceCollection;
}

/// <summary>
/// Add Function as transient
/// </summary>
/// <returns>builder</returns>
public FunctionBuilder<T> AsTransient()
{
_scope = Scope.Transient;
return this;
}

/// <summary>
/// Add Function as scoped
/// </summary>
/// <returns>builder</returns>
public FunctionBuilder<T> AsScoped()
{
_scope = Scope.Scoped;
return this;
}

/// <summary>
/// Add Function as singleton
/// </summary>
/// <returns>builder</returns>
public FunctionBuilder<T> AsSingleton()
{
_scope = Scope.Singleton;
return this;
}

/// <summary>
/// Associate alias with Function, an alias can be used to invoke a function.
/// </summary>
/// <param name="alias">function alias</param>
/// <returns>builder</returns>
public FunctionBuilder<T> WithAlias(string alias)
{
_serviceCollection.AddSingleton(new FunctionMetadata(typeof(T), alias));
return this;
}

/// <summary>
/// Add build Function to ServiceCollection
/// </summary>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public IServiceCollection Add()
{
switch (_scope)
{
case Scope.Scoped:
_serviceCollection.AddScoped<T>();
break;
case Scope.Transient:
_serviceCollection.AddTransient<T>();
break;
case Scope.Singleton:
_serviceCollection.AddSingleton<T>();
break;
default:
throw new ArgumentOutOfRangeException();
}

return _serviceCollection;
}
}
}
29 changes: 29 additions & 0 deletions ExpressionEngine/FunctionRegistrationAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;

namespace ExpressionEngine
{
/// <summary>
///
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class FunctionRegistrationAttribute : Attribute
{
/// <summary>
/// Function Name
/// </summary>
public string FunctionName { get; }
public Scope Scope { get; }

/// <summary>
/// Enable function to be discovered by <code>.WithFunctionDiscovery$lt{T}$gt</code> <see cref="ExpressionEngineDiExtensions"/>.
/// Or to be added easily with .AddFunction;
/// </summary>
/// <param name="functionName">Name of function</param>
/// <param name="scope">DI Scope - defaults to Transient</param>
public FunctionRegistrationAttribute(string functionName, Scope scope = Scope.Transient)
{
FunctionName = functionName;
Scope = scope;
}
}
}
Loading