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

Add registration of custom emitters #2681 #2682

Merged
merged 2 commits into from
Dec 21, 2024
Merged
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
3 changes: 3 additions & 0 deletions docs/CHANGELOG-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers

What's changed since pre-release v3.0.0-B0351:

- General improvements:
- Added support for registering custom emitters by @BernieWhite.
[#2681](https://github.com/microsoft/PSRule/issues/2681)
- Engineering:
- Migrate samples into PSRule repository by @BernieWhite.
[#2614](https://github.com/microsoft/PSRule/issues/2614)
Expand Down
6 changes: 5 additions & 1 deletion docs/concepts/emitters.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ Name | Default file extensions | Configurable

## Custom emitters

Custom emitters are a planned feature in PSRule v3.
Custom emitters can be created by implementing the `PSRule.Emitters.IEmitter` interface available in `Microsoft.PSRule.Types`.
This custom type implementation will be loaded by PSRule and used to process the input object.

To use a custom emitter, it must be registered with PSRule as a service.
This can be done by a convention within the `-Initialize` script block.

## Configuring formats

Expand Down
15 changes: 4 additions & 11 deletions src/PSRule.Types/Emitters/IEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,16 @@ public interface IEmitter : IDisposable
/// <summary>
/// Visit an object and emit any input objects for processing.
/// </summary>
/// <param name="context">A context object for the emitter.</param>
/// <param name="context">The current context for the emitter.</param>
/// <param name="o">The object to visit.</param>
/// <returns>Returns <c>true</c> when the emitter processed the object and <c>false</c> when it did not.</returns>
bool Visit(IEmitterContext context, object o);

/// <summary>
/// Determines if the emitter accepts the specified object type.
/// </summary>
/// <param name="context"></param>
/// <param name="type"></param>
/// <returns></returns>
/// <param name="context">The current context for the emitter.</param>
/// <param name="type">The type of object.</param>
/// <returns>Returns <c>true</c> if the emitter supports processing the object type and <c>false</c> if it does not.</returns>
bool Accepts(IEmitterContext context, Type type);

///// <summary>
///// Configure the emitter using an options instance.
///// </summary>
///// <param name="option"></param>
///// <returns></returns>
//bool Configure(PSRuleOption option);
}
36 changes: 36 additions & 0 deletions src/PSRule.Types/Runtime/IRuntimeServiceCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace PSRule.Runtime;

/// <summary>
/// A context for registering scoped runtime services within a factory.
/// </summary>
public interface IRuntimeServiceCollection : IDisposable
{
/// <summary>
/// The name of the scope.
/// </summary>
string ScopeName { get; }

/// <summary>
/// Access configuration values at runtime.
/// </summary>
IConfiguration Configuration { get; }

/// <summary>
/// Add a service.
/// </summary>
/// <typeparam name="TInterface">The specified interface type.</typeparam>
/// <typeparam name="TService">The concrete type to add.</typeparam>
void AddService<TInterface, TService>()
where TInterface : class
where TService : class, TInterface;

/// <summary>
/// Add a service.
/// </summary>
/// <param name="instanceName">A unique name of the service instance.</param>
/// <param name="instance">An instance of the service.</param>
void AddService(string instanceName, object instance);
}
2 changes: 1 addition & 1 deletion src/PSRule/Common/JsonConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
using PSRule.Definitions;
using PSRule.Definitions.Baselines;
using PSRule.Definitions.Expressions;
using PSRule.Emitters;
using PSRule.Pipeline;
using PSRule.Pipeline.Emitters;
using PSRule.Resources;
using PSRule.Runtime;

Expand Down
3 changes: 2 additions & 1 deletion src/PSRule/Common/YamlConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@
using PSRule.Data;
using PSRule.Definitions;
using PSRule.Definitions.Expressions;
using PSRule.Emitters;
using PSRule.Host;
using PSRule.Pipeline;
using PSRule.Pipeline.Emitters;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using IEmitter = YamlDotNet.Core.IEmitter;

namespace PSRule;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
using System.Collections;
using System.Management.Automation;
using PSRule.Data;
using PSRule.Emitters;
using PSRule.Options;
using PSRule.Pipeline;
using PSRule.Runtime;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

/// <summary>
///
Expand Down Expand Up @@ -45,7 +45,7 @@ public void Emit(ITargetObject value)

protected abstract void Enqueue(ITargetObject value);

private static IEnumerable<ITargetObject> ReadObjectPath(ITargetObject targetObject, string objectPath, bool caseSensitive)
private static ITargetObject[] ReadObjectPath(ITargetObject targetObject, string objectPath, bool caseSensitive)
{
if (!ObjectHelper.GetPath(
bindingContext: null,
Expand All @@ -59,10 +59,10 @@ private static IEnumerable<ITargetObject> ReadObjectPath(ITargetObject targetObj
if (typeof(IEnumerable).IsAssignableFrom(nestedType))
{
var result = new List<TargetObject>();
foreach (var item in (nestedObject as IEnumerable))
foreach (var item in nestedObject as IEnumerable)
result.Add(new TargetObject(PSObject.AsPSObject(item)));

return result.ToArray();
return [.. result];
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@

using Microsoft.Extensions.DependencyInjection;
using PSRule.Definitions;
using PSRule.Emitters;
using PSRule.Options;
using PSRule.Runtime;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

#nullable enable

Expand All @@ -29,6 +28,7 @@ public EmitterBuilder(ILanguageScopeSet? languageScopeSet = default, IFormatOpti
_Services = new ServiceCollection();
AddInternalServices();
AddInternalEmitters();
AddEmittersFromLanguageScope();
}

/// <summary>
Expand All @@ -45,6 +45,20 @@ public void AddEmitter<T>(string scope) where T : class, IEmitter
_Services.AddScoped(typeof(T));
}

/// <summary>
/// Add an <see cref="IEmitter"/> implementation class.
/// </summary>
/// <param name="scope">The scope of the emitter.</param>
/// <param name="type">An emitter type that implements <see cref="IEmitter"/>.</param>
/// <exception cref="ArgumentNullException">The <paramref name="scope"/> parameter must not be a null or empty string.</exception>
public void AddEmitter(string scope, Type type)
{
if (string.IsNullOrEmpty(scope)) throw new ArgumentNullException(nameof(scope));

_EmitterTypes.Add(new KeyValuePair<string, Type>(scope, type));
_Services.AddScoped(type);
}

/// <summary>
/// Add an existing emitter instance that is already configured.
/// </summary>
Expand Down Expand Up @@ -118,6 +132,22 @@ private void AddInternalEmitters()
AddEmitter<PowerShellDataEmitter>(ResourceHelper.STANDALONE_SCOPE_NAME);
}

/// <summary>
/// Add custom emitters from the language scope.
/// </summary>
private void AddEmittersFromLanguageScope()
{
if (_LanguageScopeSet == null) return;

foreach (var scope in _LanguageScopeSet.Get())
{
foreach (var emitterType in scope.GetEmitters())
{
AddEmitter(scope.Name, emitterType);
}
}
}

/// <summary>
/// Create a configuration for the emitter based on it's scope.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using PSRule.Emitters;

namespace PSRule.Pipeline.Emitters;
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace PSRule.Emitters;

/// <summary>
/// A chain of emitters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
using System.Management.Automation;
using Microsoft.Extensions.DependencyInjection;
using PSRule.Data;
using PSRule.Emitters;
using PSRule.Pipeline;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

#nullable enable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
using System.Collections.Concurrent;
using PSRule.Configuration;
using PSRule.Data;
using PSRule.Emitters;
using PSRule.Options;
using PSRule.Pipeline;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

#nullable enable

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using PSRule.Emitters;
using PSRule.Options;
using PSRule.Runtime;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

#nullable enable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Diagnostics;
using PSRule.Data;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

[DebuggerDisplay("{Path}")]
internal sealed class InternalFileInfo : IFileInfo, IDisposable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Text;
using PSRule.Data;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

internal sealed class InternalFileStream : IFileStream
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
using System.Management.Automation;
using Newtonsoft.Json;
using PSRule.Data;
using PSRule.Emitters;
using PSRule.Pipeline;
using PSRule.Runtime;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

/// <summary>
/// An <seealso cref="IEmitter"/> for processing JSON.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Newtonsoft.Json;
using PSRule.Data;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

internal sealed class JsonEmitterParser : JsonTextReader
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
using System.Collections.Immutable;
using System.Management.Automation;
using PSRule.Data;
using PSRule.Emitters;
using PSRule.Help;
using PSRule.Pipeline;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

/// <summary>
/// An <seealso cref="IEmitter"/> for processing Markdown.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
using System.Collections.Immutable;
using System.Management.Automation;
using PSRule.Data;
using PSRule.Emitters;
using PSRule.Pipeline;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

/// <summary>
/// An <seealso cref="IEmitter"/> for processing PowerShell Data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
using System.Collections.Immutable;
using PSRule.Data;
using PSRule.Definitions;
using PSRule.Emitters;
using PSRule.Pipeline;
using PSRule.Runtime;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NodeDeserializers;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

/// <summary>
/// An <seealso cref="PSRule.Emitters.IEmitter"/> for processing YAML.
/// An <seealso cref="IEmitter"/> for processing YAML.
/// </summary>
internal sealed class YamlEmitter : FileEmitter
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using PSRule.Data;
using YamlDotNet.Core;

namespace PSRule.Pipeline.Emitters;
namespace PSRule.Emitters;

/// <summary>
/// A custom parser that implements source mapping.
Expand Down
3 changes: 1 addition & 2 deletions src/PSRule/Pipeline/AssertPipelineBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,8 @@ public sealed override IPipeline Build(IPipelineWriter writer = null)
return !RequireModules() || !RequireSources()
? null
: (IPipeline)new InvokeRulePipeline(
context: PrepareContext(PipelineHookActions.Default),
context: PrepareContext(PipelineHookActions.Default, writer: HandleJobSummary(writer ?? PrepareWriter())),
source: Source,
writer: HandleJobSummary(writer ?? PrepareWriter()),
outcome: RuleOutcome.Processed);
}

Expand Down
4 changes: 1 addition & 3 deletions src/PSRule/Pipeline/ExportBaselinePipelineBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,8 @@ public override IPipeline Build(IPipelineWriter writer = null)
{
var filter = new BaselineFilter(_Name);
return new GetBaselinePipeline(
pipeline: PrepareContext(PipelineHookActions.Empty),
pipeline: PrepareContext(PipelineHookActions.Empty, writer ?? PrepareWriter()),
source: Source,
reader: PrepareReader(),
writer: writer ?? PrepareWriter(),
filter: filter
);
}
Expand Down
Loading
Loading