Skip to content

Commit

Permalink
Add registration of custom emitters microsoft#2681
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite committed Dec 21, 2024
1 parent 9c0f86a commit 3d357c9
Show file tree
Hide file tree
Showing 56 changed files with 372 additions and 188 deletions.
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
2 changes: 1 addition & 1 deletion src/PSRule/Common/YamlConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
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;
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
File renamed without changes.
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
8 changes: 3 additions & 5 deletions src/PSRule/Pipeline/GetBaselinePipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,17 @@ internal sealed class GetBaselinePipeline : RulePipeline
internal GetBaselinePipeline(
PipelineContext pipeline,
Source[] source,
PipelineInputStream reader,
IPipelineWriter writer,
IResourceFilter filter
)
: base(pipeline, source, reader, writer)
: base(pipeline, source)
{
_Filter = filter;
}

public override void End()
{
Writer.WriteObject(HostHelper.GetBaseline(Source, Context).Where(Match), true);
Writer.End(Result);
Pipeline.Writer.WriteObject(HostHelper.GetBaseline(Source, Context).Where(Match), true);
Pipeline.Writer.End(Result);
}

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

0 comments on commit 3d357c9

Please sign in to comment.