зеркало из https://github.com/microsoft/PSRule.git
Context refactor 2 (#2525)
This commit is contained in:
Родитель
1fd2d7c334
Коммит
e05ff57c29
|
@ -7,8 +7,11 @@ using System.ComponentModel;
|
|||
namespace PSRule.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Options that affect property binding of TargetName and TargetType.
|
||||
/// Options that configure property binding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://aka.ms/ps-rule/options"/>.
|
||||
/// </remarks>
|
||||
public sealed class BindingOption : IEquatable<BindingOption>, IBindingOption
|
||||
{
|
||||
private const bool DEFAULT_IGNORECASE = true;
|
||||
|
|
|
@ -5,6 +5,67 @@ using PSRule.Options;
|
|||
|
||||
namespace PSRule.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Options that configure property binding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://aka.ms/ps-rule/options"/>.
|
||||
/// </remarks>
|
||||
public interface IBindingOption : IOption
|
||||
{
|
||||
/// <summary>
|
||||
/// One or more custom fields to bind.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://aka.ms/ps-rule/options#bindingfield"/>.
|
||||
/// </remarks>
|
||||
FieldMap Field { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if custom binding uses ignores case when matching properties.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://aka.ms/ps-rule/options#bindingignorecase"/>.
|
||||
/// </remarks>
|
||||
bool? IgnoreCase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Configures the separator to use for building a qualified name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://aka.ms/ps-rule/options#bindingnameseparator"/>.
|
||||
/// </remarks>
|
||||
string NameSeparator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if binding prefers target info provided by the object over custom configuration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://aka.ms/ps-rule/options#bindingprefertargetinfo"/>.
|
||||
/// </remarks>
|
||||
bool? PreferTargetInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Property names to use to bind TargetName.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://aka.ms/ps-rule/options#bindingtargetname"/>.
|
||||
/// </remarks>
|
||||
string[] TargetName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Property names to use to bind TargetType.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://aka.ms/ps-rule/options#bindingtargettype"/>.
|
||||
/// </remarks>
|
||||
string[] TargetType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a qualified TargetName is used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://aka.ms/ps-rule/options#bindingusequalifiedname"/>.
|
||||
/// </remarks>
|
||||
bool? UseQualifiedName { get; }
|
||||
}
|
||||
|
|
|
@ -63,42 +63,6 @@ internal sealed class GetTargetPipelineBuilder : PipelineBuilderBase, IGetTarget
|
|||
/// <inheritdoc/>
|
||||
protected override PipelineInputStream PrepareReader()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Option.Input.ObjectPath))
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ReadObjectPath(sourceObject, next, Option.Input.ObjectPath, true);
|
||||
});
|
||||
}
|
||||
|
||||
if (Option.Input.Format == InputFormat.Yaml)
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ConvertFromYaml(sourceObject, next);
|
||||
});
|
||||
}
|
||||
else if (Option.Input.Format == InputFormat.Json)
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ConvertFromJson(sourceObject, next);
|
||||
});
|
||||
}
|
||||
else if (Option.Input.Format == InputFormat.Markdown)
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ConvertFromMarkdown(sourceObject, next);
|
||||
});
|
||||
}
|
||||
else if (Option.Input.Format == InputFormat.PowerShellData)
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next);
|
||||
});
|
||||
}
|
||||
return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter(), Option);
|
||||
return new PipelineInputStream(_InputPath, GetInputObjectSourceFilter(), Option);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using PSRule.Configuration;
|
||||
|
||||
namespace PSRule.Pipeline;
|
||||
|
||||
/// <summary>
|
||||
/// A helper to build a PSRule pipeline.
|
||||
/// </summary>
|
||||
public interface IPipelineBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure the pipeline with options.
|
||||
/// </summary>
|
||||
IPipelineBuilder Configure(PSRuleOption option);
|
||||
|
||||
/// <summary>
|
||||
/// Configure the pipeline to use a specific baseline.
|
||||
/// </summary>
|
||||
/// <param name="baseline">A baseline option or the name of a baseline.</param>
|
||||
void Baseline(BaselineOption baseline);
|
||||
|
||||
/// <summary>
|
||||
/// Build the pipeline.
|
||||
/// </summary>
|
||||
/// <param name="writer">Optionally specify a custom writer which will handle output processing.</param>
|
||||
IPipeline Build(IPipelineWriter writer = null);
|
||||
}
|
|
@ -5,6 +5,8 @@ using PSRule.Data;
|
|||
|
||||
namespace PSRule.Pipeline;
|
||||
|
||||
#nullable enable
|
||||
|
||||
internal interface IPipelineReader
|
||||
{
|
||||
int Count { get; }
|
||||
|
|
|
@ -131,42 +131,6 @@ internal abstract class InvokePipelineBuilderBase : PipelineBuilderBase, IInvoke
|
|||
|
||||
protected override PipelineInputStream PrepareReader()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Option.Input.ObjectPath))
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ReadObjectPath(sourceObject, next, Option.Input.ObjectPath, true);
|
||||
});
|
||||
}
|
||||
|
||||
if (Option.Input.Format == InputFormat.Yaml)
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ConvertFromYaml(sourceObject, next);
|
||||
});
|
||||
}
|
||||
else if (Option.Input.Format == InputFormat.Json)
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ConvertFromJson(sourceObject, next);
|
||||
});
|
||||
}
|
||||
else if (Option.Input.Format == InputFormat.Markdown)
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ConvertFromMarkdown(sourceObject, next);
|
||||
});
|
||||
}
|
||||
else if (Option.Input.Format == InputFormat.PowerShellData)
|
||||
{
|
||||
AddVisitTargetObjectAction((sourceObject, next) =>
|
||||
{
|
||||
return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next);
|
||||
});
|
||||
}
|
||||
return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter(), Option);
|
||||
return new PipelineInputStream(_InputPath, GetInputObjectSourceFilter(), Option);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using PSRule.Configuration;
|
||||
using PSRule.Data;
|
||||
using PSRule.Definitions;
|
||||
using PSRule.Definitions.Baselines;
|
||||
using PSRule.Options;
|
||||
using PSRule.Pipeline.Output;
|
||||
using PSRule.Resources;
|
||||
|
||||
namespace PSRule.Pipeline;
|
||||
|
||||
|
@ -164,453 +156,3 @@ public static class PipelineBuilder
|
|||
return pipeline;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A helper to build a PSRule pipeline.
|
||||
/// </summary>
|
||||
public interface IPipelineBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure the pipeline with options.
|
||||
/// </summary>
|
||||
IPipelineBuilder Configure(PSRuleOption option);
|
||||
|
||||
/// <summary>
|
||||
/// Configure the pipeline to use a specific baseline.
|
||||
/// </summary>
|
||||
/// <param name="baseline">A baseline option or the name of a baseline.</param>
|
||||
void Baseline(Configuration.BaselineOption baseline);
|
||||
|
||||
/// <summary>
|
||||
/// Build the pipeline.
|
||||
/// </summary>
|
||||
/// <param name="writer">Optionally specify a custom writer which will handle output processing.</param>
|
||||
IPipeline Build(IPipelineWriter writer = null);
|
||||
}
|
||||
|
||||
internal abstract class PipelineBuilderBase : IPipelineBuilder
|
||||
{
|
||||
private const string ENGINE_MODULE_NAME = "PSRule";
|
||||
|
||||
protected readonly PSRuleOption Option;
|
||||
protected readonly Source[] Source;
|
||||
protected readonly IHostContext HostContext;
|
||||
protected BindTargetMethod BindTargetNameHook;
|
||||
protected BindTargetMethod BindTargetTypeHook;
|
||||
protected BindTargetMethod BindFieldHook;
|
||||
protected VisitTargetObject VisitTargetObject;
|
||||
|
||||
private string[] _Include;
|
||||
private Hashtable _Tag;
|
||||
private Configuration.BaselineOption _Baseline;
|
||||
private string[] _Convention;
|
||||
private PathFilter _InputFilter;
|
||||
private PipelineWriter _Writer;
|
||||
|
||||
private readonly HostPipelineWriter _Output;
|
||||
|
||||
private const int MIN_JSON_INDENT = 0;
|
||||
private const int MAX_JSON_INDENT = 4;
|
||||
|
||||
protected PipelineBuilderBase(Source[] source, IHostContext hostContext)
|
||||
{
|
||||
Option = new PSRuleOption();
|
||||
Source = source;
|
||||
_Output = new HostPipelineWriter(hostContext, Option, ShouldProcess);
|
||||
HostContext = hostContext;
|
||||
BindTargetNameHook = PipelineHookActions.BindTargetName;
|
||||
BindTargetTypeHook = PipelineHookActions.BindTargetType;
|
||||
BindFieldHook = PipelineHookActions.BindField;
|
||||
VisitTargetObject = PipelineReceiverActions.PassThru;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the pipeline is executing in a remote PowerShell session.
|
||||
/// </summary>
|
||||
public bool InSession => HostContext != null && HostContext.InSession;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Name(string[] name)
|
||||
{
|
||||
if (name == null || name.Length == 0)
|
||||
return;
|
||||
|
||||
_Include = name;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Tag(Hashtable tag)
|
||||
{
|
||||
if (tag == null || tag.Count == 0)
|
||||
return;
|
||||
|
||||
_Tag = tag;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Convention(string[] convention)
|
||||
{
|
||||
if (convention == null || convention.Length == 0)
|
||||
return;
|
||||
|
||||
_Convention = convention;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IPipelineBuilder Configure(PSRuleOption option)
|
||||
{
|
||||
if (option == null)
|
||||
return this;
|
||||
|
||||
Option.Baseline = new Options.BaselineOption(option.Baseline);
|
||||
Option.Binding = new BindingOption(option.Binding);
|
||||
Option.Convention = new ConventionOption(option.Convention);
|
||||
Option.Execution = GetExecutionOption(option.Execution);
|
||||
Option.Input = new InputOption(option.Input);
|
||||
Option.Input.Format ??= InputOption.Default.Format;
|
||||
Option.Output = new OutputOption(option.Output);
|
||||
Option.Output.Outcome ??= OutputOption.Default.Outcome;
|
||||
Option.Output.Banner ??= OutputOption.Default.Banner;
|
||||
Option.Output.Style = GetStyle(option.Output.Style ?? OutputOption.Default.Style.Value);
|
||||
Option.Repository = GetRepository(option.Repository);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract IPipeline Build(IPipelineWriter writer = null);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Baseline(Configuration.BaselineOption baseline)
|
||||
{
|
||||
if (baseline == null)
|
||||
return;
|
||||
|
||||
_Baseline = baseline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Require correct module versions for pipeline execution.
|
||||
/// </summary>
|
||||
protected bool RequireModules()
|
||||
{
|
||||
var result = true;
|
||||
if (Option.Requires.TryGetValue(ENGINE_MODULE_NAME, out var requiredVersion))
|
||||
{
|
||||
var engineVersion = Engine.GetVersion();
|
||||
if (GuardModuleVersion(ENGINE_MODULE_NAME, engineVersion, requiredVersion))
|
||||
result = false;
|
||||
}
|
||||
for (var i = 0; Source != null && i < Source.Length; i++)
|
||||
{
|
||||
if (Source[i].Module != null && Option.Requires.TryGetValue(Source[i].Module.Name, out requiredVersion))
|
||||
{
|
||||
if (GuardModuleVersion(Source[i].Module.Name, Source[i].Module.Version, requiredVersion))
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Require sources for pipeline execution.
|
||||
/// </summary>
|
||||
protected bool RequireSources()
|
||||
{
|
||||
if (Source == null || Source.Length == 0)
|
||||
{
|
||||
PrepareWriter().WarnRulePathNotFound();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool GuardModuleVersion(string moduleName, string moduleVersion, string requiredVersion)
|
||||
{
|
||||
if (!TryModuleVersion(moduleVersion, requiredVersion))
|
||||
{
|
||||
var writer = PrepareWriter();
|
||||
writer.ErrorRequiredVersionMismatch(moduleName, moduleVersion, requiredVersion);
|
||||
writer.End(new DefaultPipelineResult(null, BreakLevel.None) { HadErrors = true });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryModuleVersion(string moduleVersion, string requiredVersion)
|
||||
{
|
||||
return SemanticVersion.TryParseVersion(moduleVersion, out var version) &&
|
||||
SemanticVersion.TryParseConstraint(requiredVersion, out var constraint) &&
|
||||
constraint.Equals(version);
|
||||
}
|
||||
|
||||
protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField)
|
||||
{
|
||||
var unresolved = new List<ResourceRef>();
|
||||
if (_Baseline is Configuration.BaselineOption.BaselineRef baselineRef)
|
||||
unresolved.Add(new BaselineRef(ResolveBaselineGroup(baselineRef.Name), ScopeType.Explicit));
|
||||
|
||||
return PipelineContext.New(
|
||||
option: Option,
|
||||
hostContext: HostContext,
|
||||
reader: PrepareReader(),
|
||||
bindTargetName: bindTargetName,
|
||||
bindTargetType: bindTargetType,
|
||||
bindField: bindField,
|
||||
optionBuilder: GetOptionBuilder(),
|
||||
unresolved: unresolved
|
||||
);
|
||||
}
|
||||
|
||||
protected string[] ResolveBaselineGroup(string[] name)
|
||||
{
|
||||
for (var i = 0; name != null && i < name.Length; i++)
|
||||
name[i] = ResolveBaselineGroup(name[i]);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
protected string ResolveBaselineGroup(string name)
|
||||
{
|
||||
if (name == null || name.Length < 2 || !name.StartsWith("@") ||
|
||||
Option == null || Option.Baseline == null || Option.Baseline.Group == null ||
|
||||
Option.Baseline.Group.Count == 0)
|
||||
return name;
|
||||
|
||||
var key = name.Substring(1);
|
||||
if (!Option.Baseline.Group.TryGetValue(key, out var baselines) || baselines.Length == 0)
|
||||
throw new PipelineConfigurationException("Baseline.Group", string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.PSR0003, key));
|
||||
|
||||
var writer = PrepareWriter();
|
||||
writer.WriteVerbose($"Using baseline group '{key}': {baselines[0]}");
|
||||
return baselines[0];
|
||||
}
|
||||
|
||||
protected virtual PipelineInputStream PrepareReader()
|
||||
{
|
||||
return new PipelineInputStream(null, null, GetInputObjectSourceFilter(), Option);
|
||||
}
|
||||
|
||||
protected virtual PipelineWriter PrepareWriter()
|
||||
{
|
||||
if (_Writer != null)
|
||||
return _Writer;
|
||||
|
||||
var output = GetOutput();
|
||||
_Writer = Option.Output.Format switch
|
||||
{
|
||||
OutputFormat.Csv => new CsvOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Json => new JsonOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.NUnit3 => new NUnit3OutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Yaml => new YamlOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Markdown => new MarkdownOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Wide => new WideOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Sarif => new SarifOutputWriter(Source, output, Option, ShouldProcess),
|
||||
_ => output,
|
||||
};
|
||||
return _Writer;
|
||||
}
|
||||
|
||||
protected virtual PipelineWriter GetOutput(bool writeHost = false)
|
||||
{
|
||||
// Redirect to file instead
|
||||
return !string.IsNullOrEmpty(Option.Output.Path)
|
||||
? new FileOutputWriter(
|
||||
inner: _Output,
|
||||
option: Option,
|
||||
encoding: Option.Output.GetEncoding(),
|
||||
path: Option.Output.Path,
|
||||
shouldProcess: HostContext.ShouldProcess,
|
||||
writeHost: writeHost
|
||||
)
|
||||
: _Output;
|
||||
}
|
||||
|
||||
protected static string[] GetCulture(string[] culture)
|
||||
{
|
||||
var result = new List<string>();
|
||||
var parent = new List<string>();
|
||||
var set = new HashSet<string>();
|
||||
for (var i = 0; culture != null && i < culture.Length; i++)
|
||||
{
|
||||
var c = CultureInfo.CreateSpecificCulture(culture[i]);
|
||||
if (!set.Contains(c.Name))
|
||||
{
|
||||
result.Add(c.Name);
|
||||
set.Add(c.Name);
|
||||
}
|
||||
for (var p = c.Parent; !string.IsNullOrEmpty(p.Name); p = p.Parent)
|
||||
{
|
||||
if (!set.Contains(p.Name))
|
||||
{
|
||||
parent.Add(p.Name);
|
||||
set.Add(p.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent.Count > 0)
|
||||
result.AddRange(parent);
|
||||
|
||||
return result.Count == 0 ? null : result.ToArray();
|
||||
}
|
||||
|
||||
protected static RepositoryOption GetRepository(RepositoryOption option)
|
||||
{
|
||||
var result = new RepositoryOption(option);
|
||||
if (string.IsNullOrEmpty(result.Url) && GitHelper.TryRepository(out var url))
|
||||
result.Url = url;
|
||||
|
||||
if (string.IsNullOrEmpty(result.BaseRef) && GitHelper.TryBaseRef(out var baseRef))
|
||||
result.BaseRef = baseRef;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coalesce execution options with defaults.
|
||||
/// </summary>
|
||||
protected static ExecutionOption GetExecutionOption(ExecutionOption option)
|
||||
{
|
||||
var result = ExecutionOption.Combine(option, ExecutionOption.Default);
|
||||
|
||||
// Handle when preference is set to none. The default should be used.
|
||||
result.AliasReference = result.AliasReference == ExecutionActionPreference.None ? ExecutionOption.Default.AliasReference.Value : result.AliasReference;
|
||||
result.DuplicateResourceId = result.DuplicateResourceId == ExecutionActionPreference.None ? ExecutionOption.Default.DuplicateResourceId.Value : result.DuplicateResourceId;
|
||||
result.InvariantCulture = result.InvariantCulture == ExecutionActionPreference.None ? ExecutionOption.Default.InvariantCulture.Value : result.InvariantCulture;
|
||||
result.RuleExcluded = result.RuleExcluded == ExecutionActionPreference.None ? ExecutionOption.Default.RuleExcluded.Value : result.RuleExcluded;
|
||||
result.RuleInconclusive = result.RuleInconclusive == ExecutionActionPreference.None ? ExecutionOption.Default.RuleInconclusive.Value : result.RuleInconclusive;
|
||||
result.RuleSuppressed = result.RuleSuppressed == ExecutionActionPreference.None ? ExecutionOption.Default.RuleSuppressed.Value : result.RuleSuppressed;
|
||||
result.SuppressionGroupExpired = result.SuppressionGroupExpired == ExecutionActionPreference.None ? ExecutionOption.Default.SuppressionGroupExpired.Value : result.SuppressionGroupExpired;
|
||||
result.UnprocessedObject = result.UnprocessedObject == ExecutionActionPreference.None ? ExecutionOption.Default.UnprocessedObject.Value : result.UnprocessedObject;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected PathFilter GetInputObjectSourceFilter()
|
||||
{
|
||||
return Option.Input.IgnoreObjectSource.GetValueOrDefault(InputOption.Default.IgnoreObjectSource.Value) ? GetInputFilter() : null;
|
||||
}
|
||||
|
||||
protected PathFilter GetInputFilter()
|
||||
{
|
||||
if (_InputFilter == null)
|
||||
{
|
||||
var basePath = Environment.GetWorkingPath();
|
||||
var ignoreGitPath = Option.Input.IgnoreGitPath ?? InputOption.Default.IgnoreGitPath.Value;
|
||||
var ignoreRepositoryCommon = Option.Input.IgnoreRepositoryCommon ?? InputOption.Default.IgnoreRepositoryCommon.Value;
|
||||
var builder = PathFilterBuilder.Create(basePath, Option.Input.PathIgnore, ignoreGitPath, ignoreRepositoryCommon);
|
||||
builder.UseGitIgnore();
|
||||
|
||||
_InputFilter = builder.Build();
|
||||
}
|
||||
return _InputFilter;
|
||||
}
|
||||
|
||||
private OptionContextBuilder GetOptionBuilder()
|
||||
{
|
||||
return new OptionContextBuilder(Option, _Include, _Tag, _Convention);
|
||||
}
|
||||
|
||||
protected void ConfigureBinding(PSRuleOption option)
|
||||
{
|
||||
if (option.Pipeline.BindTargetName != null && option.Pipeline.BindTargetName.Count > 0)
|
||||
{
|
||||
// Do not allow custom binding functions to be used with constrained language mode
|
||||
if (Option.Execution.LanguageMode == LanguageMode.ConstrainedLanguage)
|
||||
throw new PipelineConfigurationException(optionName: "BindTargetName", message: PSRuleResources.ConstrainedTargetBinding);
|
||||
|
||||
foreach (var action in option.Pipeline.BindTargetName)
|
||||
BindTargetNameHook = AddBindTargetAction(action, BindTargetNameHook);
|
||||
}
|
||||
|
||||
if (option.Pipeline.BindTargetType != null && option.Pipeline.BindTargetType.Count > 0)
|
||||
{
|
||||
// Do not allow custom binding functions to be used with constrained language mode
|
||||
if (Option.Execution.LanguageMode == LanguageMode.ConstrainedLanguage)
|
||||
throw new PipelineConfigurationException(optionName: "BindTargetType", message: PSRuleResources.ConstrainedTargetBinding);
|
||||
|
||||
foreach (var action in option.Pipeline.BindTargetType)
|
||||
BindTargetTypeHook = AddBindTargetAction(action, BindTargetTypeHook);
|
||||
}
|
||||
}
|
||||
|
||||
private static BindTargetMethod AddBindTargetAction(BindTargetFunc action, BindTargetMethod previous)
|
||||
{
|
||||
// Nest the previous write action in the new supplied action
|
||||
// Execution chain will be: action -> previous -> previous..n
|
||||
return (string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) =>
|
||||
{
|
||||
return action(propertyNames, caseSensitive, preferTargetInfo, targetObject, previous, out path);
|
||||
};
|
||||
}
|
||||
|
||||
private static BindTargetMethod AddBindTargetAction(BindTargetName action, BindTargetMethod previous)
|
||||
{
|
||||
return AddBindTargetAction((string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, BindTargetMethod next, out string path) =>
|
||||
{
|
||||
path = null;
|
||||
var targetType = action(targetObject);
|
||||
return string.IsNullOrEmpty(targetType) ? next(propertyNames, caseSensitive, preferTargetInfo, targetObject, out path) : targetType;
|
||||
}, previous);
|
||||
}
|
||||
|
||||
protected void AddVisitTargetObjectAction(VisitTargetObjectAction action)
|
||||
{
|
||||
// Nest the previous write action in the new supplied action
|
||||
// Execution chain will be: action -> previous -> previous..n
|
||||
var previous = VisitTargetObject;
|
||||
VisitTargetObject = (targetObject) => action(targetObject, previous);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes JSON indent range between minimum 0 and maximum 4.
|
||||
/// </summary>
|
||||
/// <param name="jsonIndent"></param>
|
||||
/// <returns>The number of characters to indent.</returns>
|
||||
protected static int NormalizeJsonIndentRange(int? jsonIndent)
|
||||
{
|
||||
if (jsonIndent.HasValue)
|
||||
{
|
||||
if (jsonIndent < MIN_JSON_INDENT)
|
||||
return MIN_JSON_INDENT;
|
||||
|
||||
else if (jsonIndent > MAX_JSON_INDENT)
|
||||
return MAX_JSON_INDENT;
|
||||
|
||||
return jsonIndent.Value;
|
||||
}
|
||||
return MIN_JSON_INDENT;
|
||||
}
|
||||
|
||||
protected bool TryChangedFiles(out string[] files)
|
||||
{
|
||||
files = null;
|
||||
if (!Option.Input.IgnoreUnchangedPath.GetValueOrDefault(InputOption.Default.IgnoreUnchangedPath.Value) ||
|
||||
!GitHelper.TryGetChangedFiles(Option.Repository.BaseRef, "d", null, out files))
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < files.Length; i++)
|
||||
HostContext.Verbose(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.UsingChangedFile, files[i]));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool ShouldProcess(string target, string action)
|
||||
{
|
||||
return HostContext == null || HostContext.ShouldProcess(target, action);
|
||||
}
|
||||
|
||||
protected static OutputStyle GetStyle(OutputStyle style)
|
||||
{
|
||||
if (style != OutputStyle.Detect)
|
||||
return style;
|
||||
|
||||
if (Environment.IsAzurePipelines())
|
||||
return OutputStyle.AzurePipelines;
|
||||
|
||||
if (Environment.IsGitHubActions())
|
||||
return OutputStyle.GitHubActions;
|
||||
|
||||
return Environment.IsVisualStudioCode() ?
|
||||
OutputStyle.VisualStudioCode :
|
||||
OutputStyle.Client;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,440 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using PSRule.Configuration;
|
||||
using PSRule.Data;
|
||||
using PSRule.Definitions;
|
||||
using PSRule.Definitions.Baselines;
|
||||
using PSRule.Options;
|
||||
using PSRule.Pipeline.Output;
|
||||
using PSRule.Resources;
|
||||
|
||||
namespace PSRule.Pipeline;
|
||||
|
||||
internal abstract class PipelineBuilderBase : IPipelineBuilder
|
||||
{
|
||||
private const string ENGINE_MODULE_NAME = "PSRule";
|
||||
|
||||
protected readonly PSRuleOption Option;
|
||||
protected readonly Source[] Source;
|
||||
protected readonly IHostContext HostContext;
|
||||
protected BindTargetMethod BindTargetNameHook;
|
||||
protected BindTargetMethod BindTargetTypeHook;
|
||||
protected BindTargetMethod BindFieldHook;
|
||||
protected VisitTargetObject VisitTargetObject;
|
||||
|
||||
private string[] _Include;
|
||||
private Hashtable _Tag;
|
||||
private Configuration.BaselineOption _Baseline;
|
||||
private string[] _Convention;
|
||||
private PathFilter _InputFilter;
|
||||
private PipelineWriter _Writer;
|
||||
|
||||
private readonly HostPipelineWriter _Output;
|
||||
|
||||
private const int MIN_JSON_INDENT = 0;
|
||||
private const int MAX_JSON_INDENT = 4;
|
||||
|
||||
protected PipelineBuilderBase(Source[] source, IHostContext hostContext)
|
||||
{
|
||||
Option = new PSRuleOption();
|
||||
Source = source;
|
||||
_Output = new HostPipelineWriter(hostContext, Option, ShouldProcess);
|
||||
HostContext = hostContext;
|
||||
BindTargetNameHook = PipelineHookActions.BindTargetName;
|
||||
BindTargetTypeHook = PipelineHookActions.BindTargetType;
|
||||
BindFieldHook = PipelineHookActions.BindField;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the pipeline is executing in a remote PowerShell session.
|
||||
/// </summary>
|
||||
public bool InSession => HostContext != null && HostContext.InSession;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Name(string[] name)
|
||||
{
|
||||
if (name == null || name.Length == 0)
|
||||
return;
|
||||
|
||||
_Include = name;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Tag(Hashtable tag)
|
||||
{
|
||||
if (tag == null || tag.Count == 0)
|
||||
return;
|
||||
|
||||
_Tag = tag;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Convention(string[] convention)
|
||||
{
|
||||
if (convention == null || convention.Length == 0)
|
||||
return;
|
||||
|
||||
_Convention = convention;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IPipelineBuilder Configure(PSRuleOption option)
|
||||
{
|
||||
if (option == null)
|
||||
return this;
|
||||
|
||||
Option.Baseline = new Options.BaselineOption(option.Baseline);
|
||||
Option.Binding = new BindingOption(option.Binding);
|
||||
Option.Convention = new ConventionOption(option.Convention);
|
||||
Option.Execution = GetExecutionOption(option.Execution);
|
||||
Option.Input = new InputOption(option.Input);
|
||||
Option.Input.Format ??= InputOption.Default.Format;
|
||||
Option.Output = new OutputOption(option.Output);
|
||||
Option.Output.Outcome ??= OutputOption.Default.Outcome;
|
||||
Option.Output.Banner ??= OutputOption.Default.Banner;
|
||||
Option.Output.Style = GetStyle(option.Output.Style ?? OutputOption.Default.Style.Value);
|
||||
Option.Repository = GetRepository(option.Repository);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract IPipeline Build(IPipelineWriter writer = null);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Baseline(Configuration.BaselineOption baseline)
|
||||
{
|
||||
if (baseline == null)
|
||||
return;
|
||||
|
||||
_Baseline = baseline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Require correct module versions for pipeline execution.
|
||||
/// </summary>
|
||||
protected bool RequireModules()
|
||||
{
|
||||
var result = true;
|
||||
if (Option.Requires.TryGetValue(ENGINE_MODULE_NAME, out var requiredVersion))
|
||||
{
|
||||
var engineVersion = Engine.GetVersion();
|
||||
if (GuardModuleVersion(ENGINE_MODULE_NAME, engineVersion, requiredVersion))
|
||||
result = false;
|
||||
}
|
||||
for (var i = 0; Source != null && i < Source.Length; i++)
|
||||
{
|
||||
if (Source[i].Module != null && Option.Requires.TryGetValue(Source[i].Module.Name, out requiredVersion))
|
||||
{
|
||||
if (GuardModuleVersion(Source[i].Module.Name, Source[i].Module.Version, requiredVersion))
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Require sources for pipeline execution.
|
||||
/// </summary>
|
||||
protected bool RequireSources()
|
||||
{
|
||||
if (Source == null || Source.Length == 0)
|
||||
{
|
||||
PrepareWriter().WarnRulePathNotFound();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool GuardModuleVersion(string moduleName, string moduleVersion, string requiredVersion)
|
||||
{
|
||||
if (!TryModuleVersion(moduleVersion, requiredVersion))
|
||||
{
|
||||
var writer = PrepareWriter();
|
||||
writer.ErrorRequiredVersionMismatch(moduleName, moduleVersion, requiredVersion);
|
||||
writer.End(new DefaultPipelineResult(null, BreakLevel.None) { HadErrors = true });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryModuleVersion(string moduleVersion, string requiredVersion)
|
||||
{
|
||||
return SemanticVersion.TryParseVersion(moduleVersion, out var version) &&
|
||||
SemanticVersion.TryParseConstraint(requiredVersion, out var constraint) &&
|
||||
constraint.Equals(version);
|
||||
}
|
||||
|
||||
protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField)
|
||||
{
|
||||
var unresolved = new List<ResourceRef>();
|
||||
if (_Baseline is Configuration.BaselineOption.BaselineRef baselineRef)
|
||||
unresolved.Add(new BaselineRef(ResolveBaselineGroup(baselineRef.Name), ScopeType.Explicit));
|
||||
|
||||
return PipelineContext.New(
|
||||
option: Option,
|
||||
hostContext: HostContext,
|
||||
reader: PrepareReader(),
|
||||
bindTargetName: bindTargetName,
|
||||
bindTargetType: bindTargetType,
|
||||
bindField: bindField,
|
||||
optionBuilder: GetOptionBuilder(),
|
||||
unresolved: unresolved
|
||||
);
|
||||
}
|
||||
|
||||
protected string[] ResolveBaselineGroup(string[] name)
|
||||
{
|
||||
for (var i = 0; name != null && i < name.Length; i++)
|
||||
name[i] = ResolveBaselineGroup(name[i]);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
protected string ResolveBaselineGroup(string name)
|
||||
{
|
||||
if (name == null || name.Length < 2 || !name.StartsWith("@") ||
|
||||
Option == null || Option.Baseline == null || Option.Baseline.Group == null ||
|
||||
Option.Baseline.Group.Count == 0)
|
||||
return name;
|
||||
|
||||
var key = name.Substring(1);
|
||||
if (!Option.Baseline.Group.TryGetValue(key, out var baselines) || baselines.Length == 0)
|
||||
throw new PipelineConfigurationException("Baseline.Group", string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.PSR0003, key));
|
||||
|
||||
var writer = PrepareWriter();
|
||||
writer.WriteVerbose($"Using baseline group '{key}': {baselines[0]}");
|
||||
return baselines[0];
|
||||
}
|
||||
|
||||
protected virtual PipelineInputStream PrepareReader()
|
||||
{
|
||||
return new PipelineInputStream(null, GetInputObjectSourceFilter(), Option);
|
||||
}
|
||||
|
||||
protected virtual PipelineWriter PrepareWriter()
|
||||
{
|
||||
if (_Writer != null)
|
||||
return _Writer;
|
||||
|
||||
var output = GetOutput();
|
||||
_Writer = Option.Output.Format switch
|
||||
{
|
||||
OutputFormat.Csv => new CsvOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Json => new JsonOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.NUnit3 => new NUnit3OutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Yaml => new YamlOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Markdown => new MarkdownOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Wide => new WideOutputWriter(output, Option, ShouldProcess),
|
||||
OutputFormat.Sarif => new SarifOutputWriter(Source, output, Option, ShouldProcess),
|
||||
_ => output,
|
||||
};
|
||||
return _Writer;
|
||||
}
|
||||
|
||||
protected virtual PipelineWriter GetOutput(bool writeHost = false)
|
||||
{
|
||||
// Redirect to file instead
|
||||
return !string.IsNullOrEmpty(Option.Output.Path)
|
||||
? new FileOutputWriter(
|
||||
inner: _Output,
|
||||
option: Option,
|
||||
encoding: Option.Output.GetEncoding(),
|
||||
path: Option.Output.Path,
|
||||
shouldProcess: HostContext.ShouldProcess,
|
||||
writeHost: writeHost
|
||||
)
|
||||
: _Output;
|
||||
}
|
||||
|
||||
protected static string[] GetCulture(string[] culture)
|
||||
{
|
||||
var result = new List<string>();
|
||||
var parent = new List<string>();
|
||||
var set = new HashSet<string>();
|
||||
for (var i = 0; culture != null && i < culture.Length; i++)
|
||||
{
|
||||
var c = CultureInfo.CreateSpecificCulture(culture[i]);
|
||||
if (!set.Contains(c.Name))
|
||||
{
|
||||
result.Add(c.Name);
|
||||
set.Add(c.Name);
|
||||
}
|
||||
for (var p = c.Parent; !string.IsNullOrEmpty(p.Name); p = p.Parent)
|
||||
{
|
||||
if (!set.Contains(p.Name))
|
||||
{
|
||||
parent.Add(p.Name);
|
||||
set.Add(p.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent.Count > 0)
|
||||
result.AddRange(parent);
|
||||
|
||||
return result.Count == 0 ? null : result.ToArray();
|
||||
}
|
||||
|
||||
protected static RepositoryOption GetRepository(RepositoryOption option)
|
||||
{
|
||||
var result = new RepositoryOption(option);
|
||||
if (string.IsNullOrEmpty(result.Url) && GitHelper.TryRepository(out var url))
|
||||
result.Url = url;
|
||||
|
||||
if (string.IsNullOrEmpty(result.BaseRef) && GitHelper.TryBaseRef(out var baseRef))
|
||||
result.BaseRef = baseRef;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coalesce execution options with defaults.
|
||||
/// </summary>
|
||||
protected static ExecutionOption GetExecutionOption(ExecutionOption option)
|
||||
{
|
||||
var result = ExecutionOption.Combine(option, ExecutionOption.Default);
|
||||
|
||||
// Handle when preference is set to none. The default should be used.
|
||||
result.AliasReference = result.AliasReference == ExecutionActionPreference.None ? ExecutionOption.Default.AliasReference.Value : result.AliasReference;
|
||||
result.DuplicateResourceId = result.DuplicateResourceId == ExecutionActionPreference.None ? ExecutionOption.Default.DuplicateResourceId.Value : result.DuplicateResourceId;
|
||||
result.InvariantCulture = result.InvariantCulture == ExecutionActionPreference.None ? ExecutionOption.Default.InvariantCulture.Value : result.InvariantCulture;
|
||||
result.RuleExcluded = result.RuleExcluded == ExecutionActionPreference.None ? ExecutionOption.Default.RuleExcluded.Value : result.RuleExcluded;
|
||||
result.RuleInconclusive = result.RuleInconclusive == ExecutionActionPreference.None ? ExecutionOption.Default.RuleInconclusive.Value : result.RuleInconclusive;
|
||||
result.RuleSuppressed = result.RuleSuppressed == ExecutionActionPreference.None ? ExecutionOption.Default.RuleSuppressed.Value : result.RuleSuppressed;
|
||||
result.SuppressionGroupExpired = result.SuppressionGroupExpired == ExecutionActionPreference.None ? ExecutionOption.Default.SuppressionGroupExpired.Value : result.SuppressionGroupExpired;
|
||||
result.UnprocessedObject = result.UnprocessedObject == ExecutionActionPreference.None ? ExecutionOption.Default.UnprocessedObject.Value : result.UnprocessedObject;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected PathFilter GetInputObjectSourceFilter()
|
||||
{
|
||||
return Option.Input.IgnoreObjectSource.GetValueOrDefault(InputOption.Default.IgnoreObjectSource.Value) ? GetInputFilter() : null;
|
||||
}
|
||||
|
||||
protected PathFilter GetInputFilter()
|
||||
{
|
||||
if (_InputFilter == null)
|
||||
{
|
||||
var basePath = Environment.GetWorkingPath();
|
||||
var ignoreGitPath = Option.Input.IgnoreGitPath ?? InputOption.Default.IgnoreGitPath.Value;
|
||||
var ignoreRepositoryCommon = Option.Input.IgnoreRepositoryCommon ?? InputOption.Default.IgnoreRepositoryCommon.Value;
|
||||
var builder = PathFilterBuilder.Create(basePath, Option.Input.PathIgnore, ignoreGitPath, ignoreRepositoryCommon);
|
||||
builder.UseGitIgnore();
|
||||
|
||||
_InputFilter = builder.Build();
|
||||
}
|
||||
return _InputFilter;
|
||||
}
|
||||
|
||||
private OptionContextBuilder GetOptionBuilder()
|
||||
{
|
||||
return new OptionContextBuilder(Option, _Include, _Tag, _Convention);
|
||||
}
|
||||
|
||||
protected void ConfigureBinding(PSRuleOption option)
|
||||
{
|
||||
if (option.Pipeline.BindTargetName != null && option.Pipeline.BindTargetName.Count > 0)
|
||||
{
|
||||
// Do not allow custom binding functions to be used with constrained language mode
|
||||
if (Option.Execution.LanguageMode == LanguageMode.ConstrainedLanguage)
|
||||
throw new PipelineConfigurationException(optionName: "BindTargetName", message: PSRuleResources.ConstrainedTargetBinding);
|
||||
|
||||
foreach (var action in option.Pipeline.BindTargetName)
|
||||
BindTargetNameHook = AddBindTargetAction(action, BindTargetNameHook);
|
||||
}
|
||||
|
||||
if (option.Pipeline.BindTargetType != null && option.Pipeline.BindTargetType.Count > 0)
|
||||
{
|
||||
// Do not allow custom binding functions to be used with constrained language mode
|
||||
if (Option.Execution.LanguageMode == LanguageMode.ConstrainedLanguage)
|
||||
throw new PipelineConfigurationException(optionName: "BindTargetType", message: PSRuleResources.ConstrainedTargetBinding);
|
||||
|
||||
foreach (var action in option.Pipeline.BindTargetType)
|
||||
BindTargetTypeHook = AddBindTargetAction(action, BindTargetTypeHook);
|
||||
}
|
||||
}
|
||||
|
||||
private static BindTargetMethod AddBindTargetAction(BindTargetFunc action, BindTargetMethod previous)
|
||||
{
|
||||
// Nest the previous write action in the new supplied action
|
||||
// Execution chain will be: action -> previous -> previous..n
|
||||
return (string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) =>
|
||||
{
|
||||
return action(propertyNames, caseSensitive, preferTargetInfo, targetObject, previous, out path);
|
||||
};
|
||||
}
|
||||
|
||||
private static BindTargetMethod AddBindTargetAction(BindTargetName action, BindTargetMethod previous)
|
||||
{
|
||||
return AddBindTargetAction((string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, BindTargetMethod next, out string path) =>
|
||||
{
|
||||
path = null;
|
||||
var targetType = action(targetObject);
|
||||
return string.IsNullOrEmpty(targetType) ? next(propertyNames, caseSensitive, preferTargetInfo, targetObject, out path) : targetType;
|
||||
}, previous);
|
||||
}
|
||||
|
||||
protected void AddVisitTargetObjectAction(VisitTargetObjectAction action)
|
||||
{
|
||||
// Nest the previous write action in the new supplied action
|
||||
// Execution chain will be: action -> previous -> previous..n
|
||||
var previous = VisitTargetObject;
|
||||
VisitTargetObject = (targetObject) => action(targetObject, previous);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes JSON indent range between minimum 0 and maximum 4.
|
||||
/// </summary>
|
||||
/// <param name="jsonIndent"></param>
|
||||
/// <returns>The number of characters to indent.</returns>
|
||||
protected static int NormalizeJsonIndentRange(int? jsonIndent)
|
||||
{
|
||||
if (jsonIndent.HasValue)
|
||||
{
|
||||
if (jsonIndent < MIN_JSON_INDENT)
|
||||
return MIN_JSON_INDENT;
|
||||
|
||||
else if (jsonIndent > MAX_JSON_INDENT)
|
||||
return MAX_JSON_INDENT;
|
||||
|
||||
return jsonIndent.Value;
|
||||
}
|
||||
return MIN_JSON_INDENT;
|
||||
}
|
||||
|
||||
protected bool TryChangedFiles(out string[] files)
|
||||
{
|
||||
files = null;
|
||||
if (!Option.Input.IgnoreUnchangedPath.GetValueOrDefault(InputOption.Default.IgnoreUnchangedPath.Value) ||
|
||||
!GitHelper.TryGetChangedFiles(Option.Repository.BaseRef, "d", null, out files))
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < files.Length; i++)
|
||||
HostContext.Verbose(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.UsingChangedFile, files[i]));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool ShouldProcess(string target, string action)
|
||||
{
|
||||
return HostContext == null || HostContext.ShouldProcess(target, action);
|
||||
}
|
||||
|
||||
protected static OutputStyle GetStyle(OutputStyle style)
|
||||
{
|
||||
if (style != OutputStyle.Detect)
|
||||
return style;
|
||||
|
||||
if (Environment.IsAzurePipelines())
|
||||
return OutputStyle.AzurePipelines;
|
||||
|
||||
if (Environment.IsGitHubActions())
|
||||
return OutputStyle.GitHubActions;
|
||||
|
||||
return Environment.IsVisualStudioCode() ?
|
||||
OutputStyle.VisualStudioCode :
|
||||
OutputStyle.Client;
|
||||
}
|
||||
}
|
|
@ -14,17 +14,15 @@ namespace PSRule.Pipeline;
|
|||
/// <summary>
|
||||
/// A stream of input objects that will be evaluated.
|
||||
/// </summary>
|
||||
internal sealed class PipelineInputStream
|
||||
internal sealed class PipelineInputStream : IPipelineReader
|
||||
{
|
||||
private readonly VisitTargetObject _Input;
|
||||
private readonly InputPathBuilder _InputPath;
|
||||
private readonly PathFilter _InputFilter;
|
||||
private readonly ConcurrentQueue<ITargetObject> _Queue;
|
||||
private readonly EmitterCollection _EmitterCollection;
|
||||
|
||||
public PipelineInputStream(VisitTargetObject input, InputPathBuilder inputPath, PathFilter inputFilter, PSRuleOption option)
|
||||
public PipelineInputStream(InputPathBuilder inputPath, PathFilter inputFilter, PSRuleOption option)
|
||||
{
|
||||
_Input = input;
|
||||
_InputPath = inputPath;
|
||||
_InputFilter = inputFilter;
|
||||
_Queue = new ConcurrentQueue<ITargetObject>();
|
||||
|
@ -35,12 +33,7 @@ internal sealed class PipelineInputStream
|
|||
|
||||
public bool IsEmpty => _Queue.IsEmpty;
|
||||
|
||||
/// <summary>
|
||||
/// Add a new object into the stream.
|
||||
/// </summary>
|
||||
/// <param name="sourceObject">An object to process.</param>
|
||||
/// <param name="targetType">A pre-bound type.</param>
|
||||
/// <param name="skipExpansion">Determines if expansion is skipped.</param>
|
||||
/// <inheritdoc/>
|
||||
public void Enqueue(object sourceObject, string? targetType = null, bool skipExpansion = false)
|
||||
{
|
||||
if (sourceObject == null)
|
||||
|
@ -55,11 +48,13 @@ internal sealed class PipelineInputStream
|
|||
_EmitterCollection.Visit(sourceObject);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryDequeue(out ITargetObject sourceObject)
|
||||
{
|
||||
return _Queue.TryDequeue(out sourceObject);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Open()
|
||||
{
|
||||
if (_InputPath == null || _InputPath.Count == 0)
|
||||
|
@ -97,11 +92,8 @@ internal sealed class PipelineInputStream
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a path to the list of inputs.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of files to add.</param>
|
||||
internal void Add(string path)
|
||||
/// <inheritdoc/>
|
||||
public void Add(string path)
|
||||
{
|
||||
_InputPath.Add(path);
|
||||
}
|
||||
|
|
|
@ -109,6 +109,14 @@ public static class PipelineWriterExtensions
|
|||
));
|
||||
}
|
||||
|
||||
internal static void VerboseRuleDiscovery(this IPipelineWriter writer, string path)
|
||||
{
|
||||
if (writer == null || !writer.ShouldWriteVerbose() || string.IsNullOrEmpty(path))
|
||||
return;
|
||||
|
||||
writer.WriteVerbose($"[PSRule][D] -- Discovering rules in: {path}");
|
||||
}
|
||||
|
||||
private static string Format(string message, params object[] args)
|
||||
{
|
||||
return args == null || args.Length == 0 ? message : string.Format(Thread.CurrentThread.CurrentCulture, message, args);
|
||||
|
|
|
@ -673,16 +673,16 @@ internal sealed class RunspaceContext : IDisposable, ILogger
|
|||
internal void AddService(string id, object service)
|
||||
{
|
||||
ResourceHelper.ParseIdString(LanguageScope.Name, id, out var scopeName, out var name);
|
||||
if (!StringComparer.OrdinalIgnoreCase.Equals(LanguageScope.Name, scopeName))
|
||||
if (!StringComparer.OrdinalIgnoreCase.Equals(LanguageScope.Name, scopeName) || string.IsNullOrEmpty(name))
|
||||
return;
|
||||
|
||||
LanguageScope.AddService(name, service);
|
||||
LanguageScope.AddService(name!, service);
|
||||
}
|
||||
|
||||
internal object? GetService(string id)
|
||||
{
|
||||
ResourceHelper.ParseIdString(LanguageScope.Name, id, out var scopeName, out var name);
|
||||
return !_LanguageScopes.TryScope(scopeName, out var scope) ? null : scope.GetService(name);
|
||||
return !_LanguageScopes.TryScope(scopeName, out var scope) || string.IsNullOrEmpty(name) ? null : scope.GetService(name!);
|
||||
}
|
||||
|
||||
private void RunConventionInitialize()
|
||||
|
|
|
@ -180,7 +180,7 @@ public sealed class PipelineTests
|
|||
Environment.UseCurrentCulture(CultureInfo.InvariantCulture);
|
||||
var context = PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null);
|
||||
var writer = new TestWriter(GetOption());
|
||||
var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null, null), writer, false);
|
||||
var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null), writer, false);
|
||||
try
|
||||
{
|
||||
pipeline.Begin();
|
||||
|
@ -202,7 +202,7 @@ public sealed class PipelineTests
|
|||
option.Execution.InvariantCulture = ExecutionActionPreference.Ignore;
|
||||
var context = PipelineContext.New(option, null, null, null, null, null, new OptionContextBuilder(), null);
|
||||
var writer = new TestWriter(option);
|
||||
var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null, null), writer, false);
|
||||
var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null), writer, false);
|
||||
try
|
||||
{
|
||||
pipeline.Begin();
|
||||
|
|
Загрузка…
Ссылка в новой задаче