This commit is contained in:
Bernie White 2024-03-30 23:21:41 +10:00 коммит произвёл GitHub
Родитель 7203d44297
Коммит bb67aab55b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
28 изменённых файлов: 590 добавлений и 463 удалений

2
.github/workflows/build.yaml поставляемый
Просмотреть файл

@ -29,6 +29,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4

Просмотреть файл

@ -32,6 +32,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSRule.Tool.Tests", "tests\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSRule.CommandLine", "src\PSRule.CommandLine\PSRule.CommandLine.csproj", "{9A556814-8E9D-4C76-8F6D-1AF2DA23A9E0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSRule.CommandLine.Tests", "tests\PSRule.CommandLine.Tests\PSRule.CommandLine.Tests.csproj", "{C25E2FC1-E306-4D99-925C-15E5DD51F6A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -82,6 +84,10 @@ Global
{9A556814-8E9D-4C76-8F6D-1AF2DA23A9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A556814-8E9D-4C76-8F6D-1AF2DA23A9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A556814-8E9D-4C76-8F6D-1AF2DA23A9E0}.Release|Any CPU.Build.0 = Release|Any CPU
{C25E2FC1-E306-4D99-925C-15E5DD51F6A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C25E2FC1-E306-4D99-925C-15E5DD51F6A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C25E2FC1-E306-4D99-925C-15E5DD51F6A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C25E2FC1-E306-4D99-925C-15E5DD51F6A2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -89,6 +95,7 @@ Global
GlobalSection(NestedProjects) = preSolution
{D3488CE2-779F-4474-B38A-F894A4B689F7} = {E0EA0CBA-96C5-4447-8B69-BC13EF0D7A4A}
{DA46C891-08F1-4D01-9F98-1F8BB10CAFEC} = {E0EA0CBA-96C5-4447-8B69-BC13EF0D7A4A}
{C25E2FC1-E306-4D99-925C-15E5DD51F6A2} = {E0EA0CBA-96C5-4447-8B69-BC13EF0D7A4A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {533491EB-BAE9-472E-B57F-A675ECD335B5}

Просмотреть файл

@ -162,13 +162,13 @@ internal static class GitHelper
return $"diff --diff-filter={filter} --ignore-submodules=all --name-only --no-renames {target}";
}
private static bool TryReadHead(string path, out string value)
internal static bool TryReadHead(string path, out string value)
{
value = null;
return TryGitFile(path, GIT_HEAD, out var filePath) && TryCommit(filePath, out value, out _);
}
private static bool TryReadCommit(string path, out string value)
internal static bool TryReadCommit(string path, out string value)
{
value = null;
if (!TryGitFile(path, GIT_HEAD, out var filePath))
@ -182,8 +182,7 @@ internal static class GitHelper
private static bool TryGitFile(string path, string file, out string filePath)
{
path ??= Environment.GetRootedBasePath(GIT_DEFAULT_PATH);
filePath = Path.Combine(path, file);
filePath = Path.Combine(Environment.GetRootedBasePath(path ?? GIT_DEFAULT_PATH), file);
return File.Exists(filePath);
}
@ -191,6 +190,9 @@ internal static class GitHelper
{
value = null;
isRef = false;
if (!File.Exists(path))
return false;
var lines = File.ReadAllLines(path);
if (lines == null || lines.Length == 0)
return false;

Просмотреть файл

@ -198,7 +198,7 @@ public sealed class PSRuleOption : IEquatable<PSRuleOption>, IBaselineV1Spec
}
/// <summary>
/// Merge two option instances by repacing any unset properties from <paramref name="o1"/> with <paramref name="o2"/> values.
/// Merge two option instances by replacing any unset properties from <paramref name="o1"/> with <paramref name="o2"/> values.
/// Values from <paramref name="o1"/> that are set are not overridden.
/// </summary>
/// <returns>A new <see cref="PSRuleOption"/> instance combining options from both instances.</returns>

Просмотреть файл

@ -77,7 +77,7 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\DocumentStrings.es-us.resx">
<EmbeddedResource Update="Resources\DocumentStrings.es-US.resx">
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\DocumentStrings.es.resx">

Просмотреть файл

@ -65,7 +65,7 @@ public static class CommandLineBuilder
builder.ModuleByName(module[i], version);
}
for (var i = 0; option.Include.Module != null && i < option.Include.Module.Length; i++)
for (var i = 0; option?.Include?.Module != null && i < option.Include.Module.Length; i++)
{
var version = file != null && file.Modules.TryGetValue(option.Include.Module[i], out var entry) ? entry.Version.ToString() : null;
builder.ModuleByName(option.Include.Module[i], version);

Просмотреть файл

@ -11,27 +11,6 @@ using PSRule.Rules;
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a pipeline for getting help from rules.
/// </summary>
public interface IHelpPipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Get the full help output for a rule.
/// </summary>
void Full();
/// <summary>
/// Open or show online help for a rule if it exists.
/// </summary>
void Online();
/// <summary>
/// Filter by name.
/// </summary>
void Name(string[] name);
}
internal sealed class GetRuleHelpPipelineBuilder : PipelineBuilderBase, IHelpPipelineBuilder
{
private bool _Full;

Просмотреть файл

@ -5,17 +5,6 @@ using PSRule.Configuration;
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a get pipeline.
/// </summary>
public interface IGetPipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Determines if the returned rules also include rule dependencies.
/// </summary>
void IncludeDependencies();
}
/// <summary>
/// A helper to construct a get pipeline.
/// </summary>

Просмотреть файл

@ -2,133 +2,9 @@
// Licensed under the MIT License.
using System.Management.Automation;
using PSRule.Configuration;
using PSRule.Options;
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a pipeline to return target objects.
/// </summary>
public interface IGetTargetPipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Specifies a path for reading input objects from disk.
/// </summary>
void InputPath(string[] path);
}
/// <summary>
/// A helper to construct the pipeline for Assert-PSRule.
/// </summary>
internal sealed class GetTargetPipelineBuilder : PipelineBuilderBase, IGetTargetPipelineBuilder
{
private InputPathBuilder _InputPath;
internal GetTargetPipelineBuilder(Source[] source, IHostContext hostContext)
: base(source, hostContext)
{
_InputPath = null;
}
/// <inheritdoc/>
public override IPipelineBuilder Configure(PSRuleOption option)
{
if (option == null)
return this;
base.Configure(option);
Option.Output = new OutputOption();
Option.Output.Culture = GetCulture(option.Output.Culture);
ConfigureBinding(option);
Option.Requires = new RequiresOption(option.Requires);
return this;
}
/// <inheritdoc/>
public void InputPath(string[] path)
{
if (path == null || path.Length == 0)
return;
PathFilter required = null;
if (TryChangedFiles(out var files))
{
required = PathFilter.Create(Environment.GetWorkingPath(), path);
path = files;
}
var builder = new InputPathBuilder(GetOutput(), Environment.GetWorkingPath(), "*", GetInputFilter(), required);
builder.Add(path);
_InputPath = builder;
}
/// <inheritdoc/>
public override IPipeline Build(IPipelineWriter writer = null)
{
return new GetTargetPipeline(PrepareContext(null, null, null), PrepareReader(), writer ?? PrepareWriter());
}
/// <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);
});
}
else if (Option.Input.Format == InputFormat.File)
{
AddVisitTargetObjectAction((sourceObject, next) =>
{
return PipelineReceiverActions.ConvertFromGitHead(sourceObject, next);
});
}
else if (Option.Input.Format == InputFormat.Detect && _InputPath != null)
{
AddVisitTargetObjectAction((sourceObject, next) =>
{
return PipelineReceiverActions.DetectInputFormat(sourceObject, next);
});
}
return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter());
}
}
/// <summary>
/// A pipeline that gets target objects through the pipeline.
/// </summary>

Просмотреть файл

@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Configuration;
using PSRule.Options;
namespace PSRule.Pipeline;
/// <summary>
/// A helper to construct the pipeline for Assert-PSRule.
/// </summary>
internal sealed class GetTargetPipelineBuilder : PipelineBuilderBase, IGetTargetPipelineBuilder
{
private InputPathBuilder _InputPath;
internal GetTargetPipelineBuilder(Source[] source, IHostContext hostContext)
: base(source, hostContext)
{
_InputPath = null;
}
/// <inheritdoc/>
public override IPipelineBuilder Configure(PSRuleOption option)
{
if (option == null)
return this;
base.Configure(option);
Option.Output = new OutputOption();
Option.Output.Culture = GetCulture(option.Output.Culture);
ConfigureBinding(option);
Option.Requires = new RequiresOption(option.Requires);
return this;
}
/// <inheritdoc/>
public void InputPath(string[] path)
{
if (path == null || path.Length == 0)
return;
PathFilter required = null;
if (TryChangedFiles(out var files))
{
required = PathFilter.Create(Environment.GetWorkingPath(), path);
path = files;
}
var builder = new InputPathBuilder(GetOutput(), Environment.GetWorkingPath(), "*", GetInputFilter(), required);
builder.Add(path);
_InputPath = builder;
}
/// <inheritdoc/>
public override IPipeline Build(IPipelineWriter writer = null)
{
return new GetTargetPipeline(PrepareContext(null, null, null), PrepareReader(), writer ?? PrepareWriter());
}
/// <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);
});
}
else if (Option.Input.Format == InputFormat.File)
{
AddVisitTargetObjectAction((sourceObject, next) =>
{
return PipelineReceiverActions.ConvertFromGitHead(sourceObject, next);
});
}
else if (Option.Input.Format == InputFormat.Detect && _InputPath != null)
{
AddVisitTargetObjectAction((sourceObject, next) =>
{
return PipelineReceiverActions.DetectInputFormat(sourceObject, next);
});
}
return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter());
}
}

Просмотреть файл

@ -6,117 +6,6 @@ using PSRule.Definitions;
namespace PSRule.Pipeline;
/// <summary>
/// A host context for handling input and output emitted from the pipeline.
/// </summary>
public interface IHostContext
{
/// <summary>
/// Determines if the pipeline is executing in a remote PowerShell session.
/// </summary>
bool InSession { get; }
/// <summary>
/// Determines if the pipeline encountered any errors.
/// </summary>
bool HadErrors { get; }
/// <summary>
/// Get the value of a PowerShell preference variable.
/// </summary>
ActionPreference GetPreferenceVariable(string variableName);
/// <summary>
/// Get the value of a named variable.
/// </summary>
T GetVariable<T>(string variableName);
/// <summary>
/// Set the value of a named variable.
/// </summary>
void SetVariable<T>(string variableName, T value);
/// <summary>
/// Handle an error reported by the pipeline.
/// </summary>
void Error(ErrorRecord errorRecord);
/// <summary>
/// Handle a warning reported by the pipeline.
/// </summary>
void Warning(string text);
/// <summary>
/// Handle an informational record reported by the pipeline.
/// </summary>
void Information(InformationRecord informationRecord);
/// <summary>
/// Handle a verbose message reported by the pipeline.
/// </summary>
void Verbose(string text);
/// <summary>
/// Handle a debug message reported by the pipeline.
/// </summary>
void Debug(string text);
/// <summary>
/// Handle an object emitted from the pipeline.
/// </summary>
void Object(object sendToPipeline, bool enumerateCollection);
/// <summary>
/// Determines if a destructive action such as overwriting a file should be processed.
/// </summary>
bool ShouldProcess(string target, string action);
/// <summary>
/// Get the current working path.
/// </summary>
string GetWorkingPath();
}
internal static class HostContextExtensions
{
private const string ErrorPreference = "ErrorActionPreference";
private const string WarningPreference = "WarningPreference";
private const string InformationPreference = "InformationPreference";
private const string VerbosePreference = "VerbosePreference";
private const string DebugPreference = "DebugPreference";
private const string AutoLoadingPreference = "PSModuleAutoLoadingPreference";
public static ActionPreference GetErrorPreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(ErrorPreference);
}
public static ActionPreference GetWarningPreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(WarningPreference);
}
public static ActionPreference GetInformationPreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(InformationPreference);
}
public static ActionPreference GetVerbosePreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(VerbosePreference);
}
public static ActionPreference GetDebugPreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(DebugPreference);
}
public static PSModuleAutoLoadingPreference GetAutoLoadingPreference(this IHostContext hostContext)
{
return hostContext.GetVariable<PSModuleAutoLoadingPreference>(AutoLoadingPreference);
}
}
/// <summary>
/// A base class for custom host context instances.
/// </summary>
@ -206,98 +95,3 @@ public abstract class HostContext : IHostContext
return Directory.GetCurrentDirectory();
}
}
/// <summary>
/// The host context used for PowerShell-based pipelines.
/// </summary>
public sealed class PSHostContext : IHostContext
{
internal readonly PSCmdlet CmdletContext;
internal readonly EngineIntrinsics ExecutionContext;
/// <inheritdoc/>
public bool InSession { get; }
/// <inheritdoc/>
public bool HadErrors { get; private set; }
/// <summary>
/// Create an instance of a PowerShell-based host context.
/// </summary>
public PSHostContext(PSCmdlet commandRuntime, EngineIntrinsics executionContext)
{
InSession = false;
CmdletContext = commandRuntime;
ExecutionContext = executionContext;
InSession = executionContext != null && executionContext.SessionState.PSVariable.GetValue("PSSenderInfo") != null;
}
/// <inheritdoc/>
public ActionPreference GetPreferenceVariable(string variableName)
{
return ExecutionContext == null
? ActionPreference.SilentlyContinue
: (ActionPreference)ExecutionContext.SessionState.PSVariable.GetValue(variableName);
}
/// <inheritdoc/>
public T GetVariable<T>(string variableName)
{
return ExecutionContext == null ? default : (T)ExecutionContext.SessionState.PSVariable.GetValue(variableName);
}
/// <inheritdoc/>
public void SetVariable<T>(string variableName, T value)
{
CmdletContext.SessionState.PSVariable.Set(variableName, value);
}
/// <inheritdoc/>
public bool ShouldProcess(string target, string action)
{
return CmdletContext == null || CmdletContext.ShouldProcess(target, action);
}
/// <inheritdoc/>
public void Error(ErrorRecord errorRecord)
{
CmdletContext.WriteError(errorRecord);
HadErrors = true;
}
/// <inheritdoc/>
public void Warning(string text)
{
CmdletContext.WriteWarning(text);
}
/// <inheritdoc/>
public void Information(InformationRecord informationRecord)
{
CmdletContext.WriteInformation(informationRecord);
}
/// <inheritdoc/>
public void Verbose(string text)
{
CmdletContext.WriteVerbose(text);
}
/// <inheritdoc/>
public void Debug(string text)
{
CmdletContext.WriteDebug(text);
}
/// <inheritdoc/>
public void Object(object sendToPipeline, bool enumerateCollection)
{
CmdletContext.WriteObject(sendToPipeline, enumerateCollection);
}
/// <inheritdoc/>
public string GetWorkingPath()
{
return ExecutionContext.SessionState.Path.CurrentFileSystemLocation.Path;
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
namespace PSRule.Pipeline;
internal static class HostContextExtensions
{
private const string ErrorPreference = "ErrorActionPreference";
private const string WarningPreference = "WarningPreference";
private const string InformationPreference = "InformationPreference";
private const string VerbosePreference = "VerbosePreference";
private const string DebugPreference = "DebugPreference";
private const string AutoLoadingPreference = "PSModuleAutoLoadingPreference";
public static ActionPreference GetErrorPreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(ErrorPreference);
}
public static ActionPreference GetWarningPreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(WarningPreference);
}
public static ActionPreference GetInformationPreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(InformationPreference);
}
public static ActionPreference GetVerbosePreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(VerbosePreference);
}
public static ActionPreference GetDebugPreference(this IHostContext hostContext)
{
return hostContext.GetPreferenceVariable(DebugPreference);
}
public static PSModuleAutoLoadingPreference GetAutoLoadingPreference(this IHostContext hostContext)
{
return hostContext.GetVariable<PSModuleAutoLoadingPreference>(AutoLoadingPreference);
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a get pipeline.
/// </summary>
public interface IGetPipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Determines if the returned rules also include rule dependencies.
/// </summary>
void IncludeDependencies();
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a pipeline to return target objects.
/// </summary>
public interface IGetTargetPipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Specifies a path for reading input objects from disk.
/// </summary>
void InputPath(string[] path);
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a pipeline for getting help from rules.
/// </summary>
public interface IHelpPipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Get the full help output for a rule.
/// </summary>
void Full();
/// <summary>
/// Open or show online help for a rule if it exists.
/// </summary>
void Online();
/// <summary>
/// Filter by name.
/// </summary>
void Name(string[] name);
}

Просмотреть файл

@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
namespace PSRule.Pipeline;
/// <summary>
/// A host context for handling input and output emitted from the pipeline.
/// </summary>
public interface IHostContext
{
/// <summary>
/// Determines if the pipeline is executing in a remote PowerShell session.
/// </summary>
bool InSession { get; }
/// <summary>
/// Determines if the pipeline encountered any errors.
/// </summary>
bool HadErrors { get; }
/// <summary>
/// Get the value of a PowerShell preference variables.
/// These variables are commonly used to control logging output.
/// Preference variables include: <c>ErrorActionPreference</c>, <c>WarningPreference</c>, <c>InformationPreference</c>, <c>VerbosePreference</c>, <c>DebugPreference</c>
/// </summary>
ActionPreference GetPreferenceVariable(string variableName);
/// <summary>
/// Get the value of a named variable.
/// </summary>
T GetVariable<T>(string variableName);
/// <summary>
/// Set the value of a named variable.
/// </summary>
void SetVariable<T>(string variableName, T value);
/// <summary>
/// Handle an error reported by the pipeline.
/// </summary>
void Error(ErrorRecord errorRecord);
/// <summary>
/// Handle a warning reported by the pipeline.
/// </summary>
void Warning(string text);
/// <summary>
/// Handle an informational record reported by the pipeline.
/// </summary>
void Information(InformationRecord informationRecord);
/// <summary>
/// Handle a verbose message reported by the pipeline.
/// </summary>
void Verbose(string text);
/// <summary>
/// Handle a debug message reported by the pipeline.
/// </summary>
void Debug(string text);
/// <summary>
/// Handle an object emitted from the pipeline.
/// </summary>
void Object(object sendToPipeline, bool enumerateCollection);
/// <summary>
/// Determines if a destructive action such as overwriting a file should be processed.
/// </summary>
bool ShouldProcess(string target, string action);
/// <summary>
/// Get the current working path.
/// </summary>
string GetWorkingPath();
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a list of sources for a command-line tool pipeline.
/// </summary>
public interface ISourceCommandLineBuilder
{
/// <summary>
/// Add loose files as a source.
/// </summary>
/// <param name="path">An array of file or directory paths containing one or more rule files.</param>
/// <param name="excludeDefaultRulePath">Determine if the default rule path is excluded. When set to <c>true</c> the default rule path is excluded.</param>
void Directory(string[] path, bool excludeDefaultRulePath = false);
/// <summary>
/// Add loose files as a source.
/// </summary>
/// <param name="path">A file or directory path containing one or more rule files.</param>
/// <param name="excludeDefaultRulePath">Determine if the default rule path is excluded. When set to <c>true</c> the default rule path is excluded.</param>
void Directory(string path, bool excludeDefaultRulePath = false);
/// <summary>
/// Add a module source.
/// </summary>
/// <param name="name">The name of the module.</param>
/// <param name="version">A specific version of the module.</param>
void ModuleByName(string name, string version = null);
/// <summary>
/// Build a list of sources for executing within PSRule.
/// </summary>
/// <returns>A list of sources.</returns>
Source[] Build();
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a list of sources for a PowerShell pipeline.
/// </summary>
public interface ISourcePipelineBuilder
{
/// <summary>
/// Determines if PowerShell should automatically load the module.
/// </summary>
bool ShouldLoadModule { get; }
/// <summary>
/// Log a verbose message for scanning sources.
/// </summary>
void VerboseScanSource(string path);
/// <summary>
/// Log a verbose message for source modules.
/// </summary>
void VerboseFoundModules(int count);
/// <summary>
/// Log a verbose message for scanning for modules.
/// </summary>
void VerboseScanModule(string moduleName);
/// <summary>
/// Add loose files as a source.
/// </summary>
/// <param name="path">An array of file or directory paths containing one or more rule files.</param>
/// <param name="excludeDefaultRulePath">Determine if the default rule path is excluded. When set to <c>true</c> the default rule path is excluded.</param>
void Directory(string[] path, bool excludeDefaultRulePath = false);
/// <summary>
/// Add loose files as a source.
/// </summary>
/// <param name="path">A file or directory path containing one or more rule files.</param>
/// <param name="excludeDefaultRulePath">Determine if the default rule path is excluded. When set to <c>true</c> the default rule path is excluded.</param>
void Directory(string path, bool excludeDefaultRulePath = false);
/// <summary>
/// Add a module source.
/// </summary>
/// <param name="module">The module info.</param>
void Module(PSModuleInfo[] module);
/// <summary>
/// Build a list of sources for executing within PSRule.
/// </summary>
/// <returns>A list of sources.</returns>
Source[] Build();
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
namespace PSRule.Pipeline;
/// <summary>
/// The host context used for PowerShell-based pipelines.
/// </summary>
public sealed class PSHostContext : IHostContext
{
internal readonly PSCmdlet CmdletContext;
internal readonly EngineIntrinsics ExecutionContext;
/// <inheritdoc/>
public bool InSession { get; }
/// <inheritdoc/>
public bool HadErrors { get; private set; }
/// <summary>
/// Create an instance of a PowerShell-based host context.
/// </summary>
public PSHostContext(PSCmdlet commandRuntime, EngineIntrinsics executionContext)
{
InSession = false;
CmdletContext = commandRuntime;
ExecutionContext = executionContext;
InSession = executionContext != null && executionContext.SessionState.PSVariable.GetValue("PSSenderInfo") != null;
}
/// <inheritdoc/>
public ActionPreference GetPreferenceVariable(string variableName)
{
return ExecutionContext == null
? ActionPreference.SilentlyContinue
: (ActionPreference)ExecutionContext.SessionState.PSVariable.GetValue(variableName);
}
/// <inheritdoc/>
public T GetVariable<T>(string variableName)
{
return ExecutionContext == null ? default : (T)ExecutionContext.SessionState.PSVariable.GetValue(variableName);
}
/// <inheritdoc/>
public void SetVariable<T>(string variableName, T value)
{
CmdletContext.SessionState.PSVariable.Set(variableName, value);
}
/// <inheritdoc/>
public bool ShouldProcess(string target, string action)
{
return CmdletContext == null || CmdletContext.ShouldProcess(target, action);
}
/// <inheritdoc/>
public void Error(ErrorRecord errorRecord)
{
CmdletContext.WriteError(errorRecord);
HadErrors = true;
}
/// <inheritdoc/>
public void Warning(string text)
{
CmdletContext.WriteWarning(text);
}
/// <inheritdoc/>
public void Information(InformationRecord informationRecord)
{
CmdletContext.WriteInformation(informationRecord);
}
/// <inheritdoc/>
public void Verbose(string text)
{
CmdletContext.WriteVerbose(text);
}
/// <inheritdoc/>
public void Debug(string text)
{
CmdletContext.WriteDebug(text);
}
/// <inheritdoc/>
public void Object(object sendToPipeline, bool enumerateCollection)
{
CmdletContext.WriteObject(sendToPipeline, enumerateCollection);
}
/// <inheritdoc/>
public string GetWorkingPath()
{
return ExecutionContext.SessionState.Path.CurrentFileSystemLocation.Path;
}
}

Просмотреть файл

@ -11,95 +11,10 @@ using PSRule.Resources;
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a list of sources for a PowerShell pipeline.
/// </summary>
public interface ISourcePipelineBuilder
{
/// <summary>
/// Determines if PowerShell should automatically load the module.
/// </summary>
bool ShouldLoadModule { get; }
/// <summary>
/// Log a verbose message for scanning sources.
/// </summary>
void VerboseScanSource(string path);
/// <summary>
/// Log a verbose message for source modules.
/// </summary>
void VerboseFoundModules(int count);
/// <summary>
/// Log a verbose message for scanning for modules.
/// </summary>
void VerboseScanModule(string moduleName);
/// <summary>
/// Add loose files as a source.
/// </summary>
/// <param name="path">An array of file or directory paths containing one or more rule files.</param>
/// <param name="excludeDefaultRulePath">Determine if the default rule path is excluded. When set to <c>true</c> the default rule path is excluded.</param>
void Directory(string[] path, bool excludeDefaultRulePath = false);
/// <summary>
/// Add loose files as a source.
/// </summary>
/// <param name="path">A file or directory path containing one or more rule files.</param>
/// <param name="excludeDefaultRulePath">Determine if the default rule path is excluded. When set to <c>true</c> the default rule path is excluded.</param>
void Directory(string path, bool excludeDefaultRulePath = false);
/// <summary>
/// Add a module source.
/// </summary>
/// <param name="module">The module info.</param>
void Module(PSModuleInfo[] module);
/// <summary>
/// Build a list of sources for executing within PSRule.
/// </summary>
/// <returns>A list of sources.</returns>
Source[] Build();
}
/// <summary>
/// A helper to build a list of sources for a command-line tool pipeline.
/// </summary>
public interface ISourceCommandlineBuilder
{
/// <summary>
/// Add loose files as a source.
/// </summary>
/// <param name="path">An array of file or directory paths containing one or more rule files.</param>
/// <param name="excludeDefaultRulePath">Determine if the default rule path is excluded. When set to <c>true</c> the default rule path is excluded.</param>
void Directory(string[] path, bool excludeDefaultRulePath = false);
/// <summary>
/// Add loose files as a source.
/// </summary>
/// <param name="path">A file or directory path containing one or more rule files.</param>
/// <param name="excludeDefaultRulePath">Determine if the default rule path is excluded. When set to <c>true</c> the default rule path is excluded.</param>
void Directory(string path, bool excludeDefaultRulePath = false);
/// <summary>
/// Add a module source.
/// </summary>
/// <param name="name">The name of the module.</param>
/// <param name="version">A specific version of the module.</param>
void ModuleByName(string name, string version = null);
/// <summary>
/// Build a list of sources for executing within PSRule.
/// </summary>
/// <returns>A list of sources.</returns>
Source[] Build();
}
/// <summary>
/// A helper to build a list of rule sources for discovery.
/// </summary>
public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceCommandlineBuilder
public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceCommandLineBuilder
{
private const string SOURCE_FILE_EXTENSION_YAML = ".yaml";
private const string SOURCE_FILE_EXTENSION_YML = ".yml";
@ -213,8 +128,8 @@ public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceComma
/// <inheritdoc/>
public void ModuleByName(string name, string version = null)
{
var basePath = FindModule(name, version) ?? throw new PipelineBuilderException(PSRuleResources.ModuleNotFound);
var info = LoadManifest(basePath, name) ?? throw new PipelineBuilderException(PSRuleResources.ModuleNotFound);
var basePath = FindModule(name, version) ?? throw ModuleNotFound(name, version);
var info = LoadManifest(basePath, name) ?? throw ModuleNotFound(name, version);
VerboseScanModule(info.Name);
var files = GetFiles(
@ -356,7 +271,7 @@ public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceComma
if (value == null) return false;
if (value is string s)
requiredAssemblies = new string[] { s };
requiredAssemblies = [s];
if (value is Array array)
requiredAssemblies = array.OfType<string>().ToArray();
@ -397,9 +312,10 @@ public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceComma
return false;
foreach (var tag in module.Tags)
{
if (StringComparer.OrdinalIgnoreCase.Equals(RULE_MODULE_TAG, tag))
return true;
}
return false;
}
@ -463,7 +379,7 @@ public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceComma
return null;
helpPath ??= Path.GetDirectoryName(path);
return new SourceFile[] { new(path, null, sourceType, helpPath) };
return [new(path, null, sourceType, helpPath)];
}
private static SourceFile[] IncludePath(string path, string helpPath, string moduleName, bool excludeDefaultRulePath, RestrictScriptSource restrictScriptSource, string workspacePath)
@ -558,4 +474,9 @@ public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceComma
{
return _HostContext == null || _HostContext.ShouldProcess(target, action);
}
private static PipelineBuilderException ModuleNotFound(string name, string version)
{
return new PipelineBuilderException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ModuleNotFound, name, version));
}
}

2
src/PSRule/Resources/PSRuleResources.Designer.cs сгенерированный
Просмотреть файл

@ -430,7 +430,7 @@ namespace PSRule.Resources {
}
/// <summary>
/// Looks up a localized string similar to No valid module can be found with that name..
/// Looks up a localized string similar to No valid module can be found with: Name={0}, Version={1}.
/// </summary>
internal static string ModuleNotFound {
get {

Просмотреть файл

@ -368,7 +368,7 @@
<value>Output written to the following file: '{0}'</value>
</data>
<data name="ModuleNotFound" xml:space="preserve">
<value>No valid module can be found with that name.</value>
<value>No valid module can be found with: Name={0}, Version={1}</value>
</data>
<data name="RuleMatchTrace" xml:space="preserve">
<value>[PSRule][R][Trace] -- {0}</value>

Просмотреть файл

@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
global using Xunit;

Просмотреть файл

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>PSRule.CommandLine</RootNamespace>
<ProjectGuid>{46127fdf-9241-4b8a-9868-012b89e3318e}</ProjectGuid>
<LangVersion>12.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.IO;
namespace PSRule;
/// <summary>
/// Unit tests for <see cref="GitHelper"/>.
/// </summary>
public sealed class GitHelperTests
{
[Fact]
public void TryReadHead_WhenValidPath_ShouldReturnGitHead()
{
var expectedHead = GetGitOutput();
Assert.True(GitHelper.TryReadHead("../../../../../.git/", out var actualHead));
Assert.Equal(expectedHead, NormalizeBranch(actualHead));
}
#region Helper methods
private static string NormalizeBranch(string actualHead)
{
return actualHead.Replace("refs/heads/", "");
}
private static string GetGitOutput()
{
return File.ReadAllText("../../../../../.git/HEAD").Replace("ref: ", string.Empty).Replace("refs/heads/", string.Empty).Trim();
}
#endregion Helper methods
}

Просмотреть файл

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ProjectGuid>{DA46C891-08F1-4D01-9F98-1F8BB10CAFEC}</ProjectGuid>
<ProjectGuid>{c1d2bc26-305a-4985-8dc5-177449ff2cfd}</ProjectGuid>
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<RootNamespace>PSRule.Tool</RootNamespace>