From cdc08bb115265ae76d930add9297ddfd70857330 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sat, 8 Jun 2024 17:41:57 +1000 Subject: [PATCH] Refactoring (#1852) --- src/PSRule.CommandLine/ClientContext.cs | 1 - .../Emitters/InternalFileStream.cs | 9 +- src/PSRule/Badges/Badge.cs | 68 ++ src/PSRule/Badges/BadgeBuilder.cs | 130 ---- src/PSRule/Badges/BadgeType.cs | 25 + src/PSRule/Badges/IBadge.cs | 20 + src/PSRule/Badges/IBadgeBuilder.cs | 35 ++ src/PSRule/Common/ResourceExtensions.cs | 3 +- src/PSRule/Configuration/BindingOption.cs | 10 - .../Configuration/BindingOptionExtensions.cs | 15 + src/PSRule/Definitions/IResource.cs | 60 ++ src/PSRule/Definitions/IResourceVisitor.cs | 9 + src/PSRule/Definitions/InternalResource.cs | 58 ++ src/PSRule/Definitions/Resource.cs | 594 ------------------ src/PSRule/Definitions/ResourceAnnotation.cs | 12 + src/PSRule/Definitions/ResourceAnnotations.cs | 12 + src/PSRule/Definitions/ResourceBuilder.cs | 53 ++ src/PSRule/Definitions/ResourceExtent.cs | 20 + src/PSRule/Definitions/ResourceFlags.cs | 21 + src/PSRule/Definitions/ResourceHelper.cs | 85 +++ src/PSRule/Definitions/ResourceKind.cs | 45 ++ src/PSRule/Definitions/ResourceLabels.cs | 51 ++ src/PSRule/Definitions/ResourceMetadata.cs | 70 +++ src/PSRule/Definitions/ResourceObject.cs | 22 + src/PSRule/Definitions/ResourceRef.cs | 16 + src/PSRule/Definitions/ResourceTags.cs | 129 ++++ src/PSRule/Definitions/Rules/RuleFilter.cs | 6 - .../Definitions/ValidateResourceAnnotation.cs | 12 + src/PSRule/Help/MarkdownReader.cs | 7 - src/PSRule/Help/MarkdownReaderMode.cs | 11 + src/PSRule/Help/MarkdownStream.cs | 43 -- src/PSRule/Help/MarkdownToken.cs | 64 -- .../Help/MarkdownTokenFlagExtensions.cs | 22 + src/PSRule/Help/MarkdownTokenType.cs | 29 + src/PSRule/Help/MarkdownTokens.cs | 25 + src/PSRule/Help/SourceExtent.cs | 49 ++ src/PSRule/Help/TokenStream.cs | 187 ------ src/PSRule/Help/TokenStreamExtensions.cs | 191 ++++++ src/PSRule/Host/AssertVariable.cs | 24 + src/PSRule/Host/ConfigurationVariable.cs | 21 + src/PSRule/Host/{Host.cs => HostState.cs} | 102 --- src/PSRule/Host/LocalizedDataVariable.cs | 25 + src/PSRule/Host/PSRuleVariable.cs | 25 + src/PSRule/Host/RuleVariable.cs | 25 + src/PSRule/Host/TargetObjectVariable.cs | 23 + src/PSRule/Pipeline/Dependencies/LockEntry.cs | 19 + src/PSRule/Pipeline/Dependencies/LockFile.cs | 13 - src/PSRule/Pipeline/Emitters/EmitterChain.cs | 13 +- .../Pipeline/Emitters/InternalFileInfo.cs | 69 +- .../Pipeline/Emitters/InternalFileStream.cs | 71 ++- src/PSRule/Pipeline/Emitters/JsonEmitter.cs | 7 +- src/PSRule/Pipeline/Emitters/YamlEmitter.cs | 2 +- ...ertFormatter.cs => AssertFormatterBase.cs} | 109 ---- .../Pipeline/Formatters/IAssertFormatter.cs | 17 + .../Pipeline/Formatters/TerminalSupport.cs | 104 +++ src/PSRule/Rules/IRuleHelpInfoV2.cs | 33 + src/PSRule/Rules/Link.cs | 26 + src/PSRule/Rules/RuleHelpInfo.cs | 102 --- src/PSRule/Rules/RuleHelpInfoExtensions.cs | 58 ++ src/PSRule/Runtime/ILanguageScope.cs | 56 ++ src/PSRule/Runtime/ILogger.cs | 21 +- src/PSRule/Runtime/IOperand.cs | 30 + src/PSRule/Runtime/LanguageScope.cs | 147 ----- src/PSRule/Runtime/LanguageScopeSet.cs | 103 +++ src/PSRule/Runtime/LogLevel.cs | 23 + src/PSRule/Runtime/NameToken.cs | 21 - src/PSRule/Runtime/NameTokenType.cs | 25 + .../Runtime/ObjectPath/FilterOperator.cs | 22 + src/PSRule/Runtime/ObjectPath/IPathToken.cs | 15 + src/PSRule/Runtime/ObjectPath/ITokenReader.cs | 15 + src/PSRule/Runtime/ObjectPath/ITokenWriter.cs | 11 + src/PSRule/Runtime/ObjectPath/PathToken.cs | 36 ++ .../Runtime/ObjectPath/PathTokenOption.cs | 11 + .../Runtime/ObjectPath/PathTokenType.cs | 64 ++ src/PSRule/Runtime/ObjectPath/TokenReader.cs | 48 ++ src/PSRule/Runtime/ObjectPath/Tokens.cs | 194 ------ src/PSRule/Runtime/Operand.cs | 72 --- src/PSRule/Runtime/OperandKind.cs | 50 ++ ...SRuleMemberInfo.cs => PSRuleTargetInfo.cs} | 0 src/PSRule/Runtime/RuleConditionHelper.cs | 62 ++ src/PSRule/Runtime/RuleConditionResult.cs | 58 -- src/PSRule/Runtime/RunspaceContext.cs | 36 -- src/PSRule/Runtime/RunspaceScope.cs | 40 ++ tests/PSRule.Tests/RuleFilterTests.cs | 61 +- tests/PSRule.Tests/TestResourceName.cs | 48 ++ tests/PSRule.Tests/Usings.cs | 2 +- 86 files changed, 2352 insertions(+), 2026 deletions(-) create mode 100644 src/PSRule/Badges/Badge.cs create mode 100644 src/PSRule/Badges/BadgeType.cs create mode 100644 src/PSRule/Badges/IBadge.cs create mode 100644 src/PSRule/Badges/IBadgeBuilder.cs create mode 100644 src/PSRule/Configuration/BindingOptionExtensions.cs create mode 100644 src/PSRule/Definitions/IResource.cs create mode 100644 src/PSRule/Definitions/IResourceVisitor.cs create mode 100644 src/PSRule/Definitions/InternalResource.cs create mode 100644 src/PSRule/Definitions/ResourceAnnotation.cs create mode 100644 src/PSRule/Definitions/ResourceAnnotations.cs create mode 100644 src/PSRule/Definitions/ResourceBuilder.cs create mode 100644 src/PSRule/Definitions/ResourceExtent.cs create mode 100644 src/PSRule/Definitions/ResourceFlags.cs create mode 100644 src/PSRule/Definitions/ResourceHelper.cs create mode 100644 src/PSRule/Definitions/ResourceKind.cs create mode 100644 src/PSRule/Definitions/ResourceLabels.cs create mode 100644 src/PSRule/Definitions/ResourceMetadata.cs create mode 100644 src/PSRule/Definitions/ResourceObject.cs create mode 100644 src/PSRule/Definitions/ResourceRef.cs create mode 100644 src/PSRule/Definitions/ResourceTags.cs create mode 100644 src/PSRule/Definitions/ValidateResourceAnnotation.cs create mode 100644 src/PSRule/Help/MarkdownReaderMode.cs create mode 100644 src/PSRule/Help/MarkdownTokenFlagExtensions.cs create mode 100644 src/PSRule/Help/MarkdownTokenType.cs create mode 100644 src/PSRule/Help/MarkdownTokens.cs create mode 100644 src/PSRule/Help/SourceExtent.cs create mode 100644 src/PSRule/Help/TokenStreamExtensions.cs create mode 100644 src/PSRule/Host/AssertVariable.cs create mode 100644 src/PSRule/Host/ConfigurationVariable.cs rename src/PSRule/Host/{Host.cs => HostState.cs} (55%) create mode 100644 src/PSRule/Host/LocalizedDataVariable.cs create mode 100644 src/PSRule/Host/PSRuleVariable.cs create mode 100644 src/PSRule/Host/RuleVariable.cs create mode 100644 src/PSRule/Host/TargetObjectVariable.cs create mode 100644 src/PSRule/Pipeline/Dependencies/LockEntry.cs rename src/PSRule/Pipeline/Formatters/{AssertFormatter.cs => AssertFormatterBase.cs} (85%) create mode 100644 src/PSRule/Pipeline/Formatters/IAssertFormatter.cs create mode 100644 src/PSRule/Pipeline/Formatters/TerminalSupport.cs create mode 100644 src/PSRule/Rules/IRuleHelpInfoV2.cs create mode 100644 src/PSRule/Rules/Link.cs create mode 100644 src/PSRule/Rules/RuleHelpInfoExtensions.cs create mode 100644 src/PSRule/Runtime/ILanguageScope.cs create mode 100644 src/PSRule/Runtime/IOperand.cs create mode 100644 src/PSRule/Runtime/LanguageScopeSet.cs create mode 100644 src/PSRule/Runtime/LogLevel.cs create mode 100644 src/PSRule/Runtime/NameTokenType.cs create mode 100644 src/PSRule/Runtime/ObjectPath/FilterOperator.cs create mode 100644 src/PSRule/Runtime/ObjectPath/IPathToken.cs create mode 100644 src/PSRule/Runtime/ObjectPath/ITokenReader.cs create mode 100644 src/PSRule/Runtime/ObjectPath/ITokenWriter.cs create mode 100644 src/PSRule/Runtime/ObjectPath/PathToken.cs create mode 100644 src/PSRule/Runtime/ObjectPath/PathTokenOption.cs create mode 100644 src/PSRule/Runtime/ObjectPath/PathTokenType.cs create mode 100644 src/PSRule/Runtime/ObjectPath/TokenReader.cs delete mode 100644 src/PSRule/Runtime/ObjectPath/Tokens.cs create mode 100644 src/PSRule/Runtime/OperandKind.cs rename src/PSRule/Runtime/{PSRuleMemberInfo.cs => PSRuleTargetInfo.cs} (100%) create mode 100644 src/PSRule/Runtime/RuleConditionHelper.cs create mode 100644 src/PSRule/Runtime/RunspaceScope.cs create mode 100644 tests/PSRule.Tests/TestResourceName.cs diff --git a/src/PSRule.CommandLine/ClientContext.cs b/src/PSRule.CommandLine/ClientContext.cs index 81f1268cc8..456a4a4411 100644 --- a/src/PSRule.CommandLine/ClientContext.cs +++ b/src/PSRule.CommandLine/ClientContext.cs @@ -3,7 +3,6 @@ using System.CommandLine.Invocation; using PSRule.Configuration; -using PSRule.Options; namespace PSRule.CommandLine; diff --git a/src/PSRule.Types/Emitters/InternalFileStream.cs b/src/PSRule.Types/Emitters/InternalFileStream.cs index e8e0d9689d..1665c31131 100644 --- a/src/PSRule.Types/Emitters/InternalFileStream.cs +++ b/src/PSRule.Types/Emitters/InternalFileStream.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Emitters +namespace PSRule.Emitters; + +internal class InternalFileStream { - internal class InternalFileStream + public InternalFileStream() { - public InternalFileStream() - { - } } } \ No newline at end of file diff --git a/src/PSRule/Badges/Badge.cs b/src/PSRule/Badges/Badge.cs new file mode 100644 index 0000000000..9fd1b1cbfc --- /dev/null +++ b/src/PSRule/Badges/Badge.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Badges; + +/// +/// An instance of a badge created by the Badge API. +/// +internal sealed class Badge : IBadge +{ + private readonly string _LeftText; + private readonly string _RightText; + private readonly double _LeftWidth; + private readonly double _RightWidth; + private readonly int _MidPadding; + private readonly int _BorderPadding; + private readonly string _Fill; + + internal Badge(string left, string right, string fill) + { + _LeftWidth = BadgeResources.Measure(left); + _RightWidth = BadgeResources.Measure(right); + + _LeftText = left; + _RightText = right; + _MidPadding = 3; + _BorderPadding = 7; + _Fill = fill; + } + + /// + public override string ToString() + { + return ToSvg(); + } + + /// + public string ToSvg() + { + var w = (int)Math.Round(_LeftWidth + _RightWidth + 2 * _BorderPadding + 2 * _MidPadding); + var x = (int)Math.Round(_LeftWidth + _BorderPadding + _MidPadding); + + var builder = new SvgBuilder( + width: w, + height: 20, + textScale: 10, + midPoint: x, + rounding: 2, + borderPadding: _BorderPadding, + midPadding: _MidPadding); + builder.Begin(string.Concat(_LeftText, ": ", _RightText)); + builder.Backfill(_Fill); + builder.TextBlock(_LeftText, _RightText, 110); + builder.End(); + return builder.ToString(); + } + + /// + public void ToFile(string path) + { + path = Environment.GetRootedPath(path); + var parentPath = Directory.GetParent(path); + if (!parentPath.Exists) + Directory.CreateDirectory(path: parentPath.FullName); + + File.WriteAllText(path, contents: ToSvg()); + } +} diff --git a/src/PSRule/Badges/BadgeBuilder.cs b/src/PSRule/Badges/BadgeBuilder.cs index 1b3b3b59d7..68f2217af6 100644 --- a/src/PSRule/Badges/BadgeBuilder.cs +++ b/src/PSRule/Badges/BadgeBuilder.cs @@ -7,136 +7,6 @@ namespace PSRule.Badges; -/// -/// The type of badge. -/// -public enum BadgeType -{ - /// - /// A badge that reports an unknown state. - /// - Unknown = 0, - - /// - /// A badge reporting a successful state. - /// - Success = 1, - - /// - /// A bagde reporting a failed state. - /// - Failure = 2 -} - -/// -/// An instance of a badge created by the badge API. -/// -public interface IBadge -{ - /// - /// Get the badge as SVG text content. - /// - string ToSvg(); - - /// - /// Write the SVG badge content directly to disk. - /// - void ToFile(string path); -} - -/// -/// A builder for the badge API. -/// -public interface IBadgeBuilder -{ - /// - /// Create a badge for the worst case of an analyzed object. - /// - /// A single result. The worst case for all records of an object is used for the badge. - /// An instance of a badge. - IBadge Create(InvokeResult result); - - /// - /// Create a badge for the worst case of all analyzed objects. - /// - /// A enumeration of results. The worst case from all results is used for the badge. - /// An instance of a badge. - IBadge Create(IEnumerable result); - - /// - /// Create a custom badge. - /// - /// The left badge text. - /// Determines if the result is Unknown, Success, or Failure. - /// The right badge text. - /// An instance of a badge. - IBadge Create(string title, BadgeType type, string label); -} - -/// -/// An instance of a badge created by the Badge API. -/// -internal sealed class Badge : IBadge -{ - private readonly string _LeftText; - private readonly string _RightText; - private readonly double _LeftWidth; - private readonly double _RightWidth; - private readonly int _MidPadding; - private readonly int _BorderPadding; - private readonly string _Fill; - - internal Badge(string left, string right, string fill) - { - _LeftWidth = BadgeResources.Measure(left); - _RightWidth = BadgeResources.Measure(right); - - _LeftText = left; - _RightText = right; - _MidPadding = 3; - _BorderPadding = 7; - _Fill = fill; - } - - /// - public override string ToString() - { - return ToSvg(); - } - - /// - public string ToSvg() - { - var w = (int)Math.Round(_LeftWidth + _RightWidth + 2 * _BorderPadding + 2 * _MidPadding); - var x = (int)Math.Round(_LeftWidth + _BorderPadding + _MidPadding); - - var builder = new SvgBuilder( - width: w, - height: 20, - textScale: 10, - midPoint: x, - rounding: 2, - borderPadding: _BorderPadding, - midPadding: _MidPadding); - builder.Begin(string.Concat(_LeftText, ": ", _RightText)); - builder.Backfill(_Fill); - builder.TextBlock(_LeftText, _RightText, 110); - builder.End(); - return builder.ToString(); - } - - /// - public void ToFile(string path) - { - path = Environment.GetRootedPath(path); - var parentPath = Directory.GetParent(path); - if (!parentPath.Exists) - Directory.CreateDirectory(path: parentPath.FullName); - - File.WriteAllText(path, contents: ToSvg()); - } -} - /// /// A badge builder that implements the Badge API within PSRule. /// diff --git a/src/PSRule/Badges/BadgeType.cs b/src/PSRule/Badges/BadgeType.cs new file mode 100644 index 0000000000..48a23618e3 --- /dev/null +++ b/src/PSRule/Badges/BadgeType.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Badges; + +/// +/// The type of badge. +/// +public enum BadgeType +{ + /// + /// A badge that reports an unknown state. + /// + Unknown = 0, + + /// + /// A badge reporting a successful state. + /// + Success = 1, + + /// + /// A badge reporting a failed state. + /// + Failure = 2 +} diff --git a/src/PSRule/Badges/IBadge.cs b/src/PSRule/Badges/IBadge.cs new file mode 100644 index 0000000000..12552c7f16 --- /dev/null +++ b/src/PSRule/Badges/IBadge.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Badges; + +/// +/// An instance of a badge created by the badge API. +/// +public interface IBadge +{ + /// + /// Get the badge as SVG text content. + /// + string ToSvg(); + + /// + /// Write the SVG badge content directly to disk. + /// + void ToFile(string path); +} diff --git a/src/PSRule/Badges/IBadgeBuilder.cs b/src/PSRule/Badges/IBadgeBuilder.cs new file mode 100644 index 0000000000..3c166c3acd --- /dev/null +++ b/src/PSRule/Badges/IBadgeBuilder.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Pipeline; + +namespace PSRule.Badges; + +/// +/// A builder for the badge API. +/// +public interface IBadgeBuilder +{ + /// + /// Create a badge for the worst case of an analyzed object. + /// + /// A single result. The worst case for all records of an object is used for the badge. + /// An instance of a badge. + IBadge Create(InvokeResult result); + + /// + /// Create a badge for the worst case of all analyzed objects. + /// + /// A enumeration of results. The worst case from all results is used for the badge. + /// An instance of a badge. + IBadge Create(IEnumerable result); + + /// + /// Create a custom badge. + /// + /// The left badge text. + /// Determines if the result is Unknown, Success, or Failure. + /// The right badge text. + /// An instance of a badge. + IBadge Create(string title, BadgeType type, string label); +} diff --git a/src/PSRule/Common/ResourceExtensions.cs b/src/PSRule/Common/ResourceExtensions.cs index 1ff542ddf1..709a614d2b 100644 --- a/src/PSRule/Common/ResourceExtensions.cs +++ b/src/PSRule/Common/ResourceExtensions.cs @@ -25,7 +25,8 @@ internal static bool Match(this IResourceFilter filter, Baseline resource) internal static bool IsLocalScope(this IResource resource) { - return string.IsNullOrEmpty(resource.Source.Module); + return resource != null && + (string.IsNullOrEmpty(resource.Id.Scope) || resource.Id.Scope == "."); } internal static IEnumerable GetIds(this IResource resource) diff --git a/src/PSRule/Configuration/BindingOption.cs b/src/PSRule/Configuration/BindingOption.cs index 8f29b03950..b721179b62 100644 --- a/src/PSRule/Configuration/BindingOption.cs +++ b/src/PSRule/Configuration/BindingOption.cs @@ -2,19 +2,9 @@ // Licensed under the MIT License. using System.ComponentModel; -using System.Diagnostics; namespace PSRule.Configuration; -internal static class BindingOptionExtensions -{ - [DebuggerStepThrough] - public static StringComparer GetComparer(this BindingOption option) - { - return option.IgnoreCase.GetValueOrDefault(BindingOption.Default.IgnoreCase.Value) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - } -} - /// /// Options that affect property binding of TargetName and TargetType. /// diff --git a/src/PSRule/Configuration/BindingOptionExtensions.cs b/src/PSRule/Configuration/BindingOptionExtensions.cs new file mode 100644 index 0000000000..23d37f6c6c --- /dev/null +++ b/src/PSRule/Configuration/BindingOptionExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; + +namespace PSRule.Configuration; + +internal static class BindingOptionExtensions +{ + [DebuggerStepThrough] + public static StringComparer GetComparer(this BindingOption option) + { + return option.IgnoreCase.GetValueOrDefault(BindingOption.Default.IgnoreCase.Value) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + } +} diff --git a/src/PSRule/Definitions/IResource.cs b/src/PSRule/Definitions/IResource.cs new file mode 100644 index 0000000000..c058e1165f --- /dev/null +++ b/src/PSRule/Definitions/IResource.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// A resource language block. +/// +public interface IResource : ILanguageBlock +{ + /// + /// The type of resource. + /// + ResourceKind Kind { get; } + + /// + /// The API version of the resource. + /// + string ApiVersion { get; } + + /// + /// The name of the resource. + /// + string Name { get; } + + /// + /// An optional reference identifer for the resource. + /// + ResourceId? Ref { get; } + + /// + /// Any additional aliases for the resource. + /// + ResourceId[] Alias { get; } + + /// + /// Any resource tags. + /// + ResourceTags Tags { get; } + + /// + /// Any taxonomy references. + /// + ResourceLabels Labels { get; } + + /// + /// Flags for the resource. + /// + ResourceFlags Flags { get; } + + /// + /// The source location of the resource. + /// + ISourceExtent Extent { get; } + + /// + /// Additional information about the resource. + /// + IResourceHelpInfo Info { get; } +} diff --git a/src/PSRule/Definitions/IResourceVisitor.cs b/src/PSRule/Definitions/IResourceVisitor.cs new file mode 100644 index 0000000000..5003f17b14 --- /dev/null +++ b/src/PSRule/Definitions/IResourceVisitor.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +internal interface IResourceVisitor +{ + bool Visit(IResource resource); +} diff --git a/src/PSRule/Definitions/InternalResource.cs b/src/PSRule/Definitions/InternalResource.cs new file mode 100644 index 0000000000..2e84cca7d4 --- /dev/null +++ b/src/PSRule/Definitions/InternalResource.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Pipeline; +using YamlDotNet.Serialization; + +namespace PSRule.Definitions; + +/// +/// A base class for built-in resource types. +/// +/// The type of the related for the resource. +public abstract class InternalResource : Resource, IResource, IAnnotated where TSpec : Spec, new() +{ + private readonly Dictionary _Annotations; + + private protected InternalResource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec) + : base(kind, apiVersion, source, metadata, info, extent, spec) + { + _Annotations = new Dictionary(); + Obsolete = ResourceHelper.IsObsolete(metadata); + Flags |= ResourceHelper.IsObsolete(metadata) ? ResourceFlags.Obsolete : ResourceFlags.None; + } + + [YamlIgnore()] + internal readonly bool Obsolete; + + [YamlIgnore()] + internal ResourceFlags Flags { get; } + + ResourceKind IResource.Kind => Kind; + + string IResource.ApiVersion => ApiVersion; + + string IResource.Name => Name; + + // Not supported with base resources. + ResourceId? IResource.Ref => null; + + // Not supported with base resources. + ResourceId[] IResource.Alias => null; + + ResourceTags IResource.Tags => Metadata.Tags; + + ResourceLabels IResource.Labels => Metadata.Labels; + + ResourceFlags IResource.Flags => Flags; + + TAnnotation IAnnotated.GetAnnotation() + { + return _Annotations.TryGetValue(typeof(TAnnotation), out var annotation) ? (TAnnotation)annotation : null; + } + + void IAnnotated.SetAnnotation(TAnnotation annotation) + { + _Annotations[typeof(TAnnotation)] = annotation; + } +} diff --git a/src/PSRule/Definitions/Resource.cs b/src/PSRule/Definitions/Resource.cs index 2139f60e68..3bf556648a 100644 --- a/src/PSRule/Definitions/Resource.cs +++ b/src/PSRule/Definitions/Resource.cs @@ -1,477 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; using System.Diagnostics; -using System.Text; -using PSRule.Converters.Yaml; -using PSRule.Definitions.Rules; using PSRule.Pipeline; -using PSRule.Runtime; -using YamlDotNet.Core; -using YamlDotNet.Core.Events; using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; -using YamlDotNet.Serialization.NodeDeserializers; namespace PSRule.Definitions; -/// -/// The type of resource. -/// -public enum ResourceKind -{ - /// - /// Unknown or empty. - /// - None = 0, - - /// - /// A rule resource. - /// - Rule = 1, - - /// - /// A baseline resource. - /// - Baseline = 2, - - /// - /// A module configuration resource. - /// - ModuleConfig = 3, - - /// - /// A selector resource. - /// - Selector = 4, - - /// - /// A convention. - /// - Convention = 5, - - /// - /// A suppression group. - /// - SuppressionGroup = 6 -} - -/// -/// Additional flags that indicate the status of the resource. -/// -[Flags] -public enum ResourceFlags -{ - /// - /// No flags are set. - /// - None = 0, - - /// - /// The resource is obsolete. - /// - Obsolete = 1 -} - -/// -/// A resource langange block. -/// -public interface IResource : ILanguageBlock -{ - /// - /// The type of resource. - /// - ResourceKind Kind { get; } - - /// - /// The API version of the resource. - /// - string ApiVersion { get; } - - /// - /// The name of the resource. - /// - string Name { get; } - - /// - /// An optional reference identifer for the resource. - /// - ResourceId? Ref { get; } - - /// - /// Any additional aliases for the resource. - /// - ResourceId[] Alias { get; } - - /// - /// Any resource tags. - /// - ResourceTags Tags { get; } - - /// - /// Any taxonomy references. - /// - ResourceLabels Labels { get; } - - /// - /// Flags for the resource. - /// - ResourceFlags Flags { get; } - - /// - /// The source location of the resource. - /// - ISourceExtent Extent { get; } - - /// - /// Additional information about the resource. - /// - IResourceHelpInfo Info { get; } -} - -internal interface IResourceVisitor -{ - bool Visit(IResource resource); -} - -internal abstract class ResourceRef -{ - public readonly string Id; - public readonly ResourceKind Kind; - - protected ResourceRef(string id, ResourceKind kind) - { - Kind = kind; - Id = id; - } -} - -/// -/// A base resource annotation. -/// -internal abstract class ResourceAnnotation -{ - -} - -/// -/// Annotation used to flag validation issues. -/// -internal sealed class ValidateResourceAnnotation : ResourceAnnotation -{ - -} - -/// -/// A resource object. -/// -public sealed class ResourceObject -{ - internal ResourceObject(IResource block) - { - Block = block; - } - - internal IResource Block { get; } - - internal bool Visit(IResourceVisitor visitor) - { - return Block != null && visitor != null && visitor.Visit(Block); - } -} - -internal sealed class ResourceBuilder -{ - private readonly List _Output; - private readonly IDeserializer _Deserializer; - - internal ResourceBuilder() - { - _Output = new List(); - _Deserializer = new DeserializerBuilder() - .IgnoreUnmatchedProperties() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .WithTypeConverter(new FieldMapYamlTypeConverter()) - .WithTypeConverter(new StringArrayMapConverter()) - .WithTypeConverter(new StringArrayConverter()) - .WithNodeDeserializer( - inner => new ResourceNodeDeserializer(new LanguageExpressionDeserializer(inner)), - s => s.InsteadOf()) - .Build(); - } - - internal void FromFile(SourceFile file) - { - using var reader = new StreamReader(file.Path); - var parser = new YamlDotNet.Core.Parser(reader); - parser.TryConsume(out _); - while (parser.Current is DocumentStart) - { - var item = _Deserializer.Deserialize(parser: parser); - if (item == null || item.Block == null) - continue; - - _Output.Add(item.Block); - } - } - - internal IEnumerable Build() - { - return _Output.Count == 0 ? Array.Empty() : _Output.ToArray(); - } -} - -/// -/// Additional resource annotations. -/// -public sealed class ResourceAnnotations : Dictionary -{ - -} - -/// -/// Additional resource taxonomy references. -/// -public sealed class ResourceLabels : Dictionary -{ - /// - /// Create an empty set of resource labels. - /// - public ResourceLabels() : base(StringComparer.OrdinalIgnoreCase) { } - - /// - /// Convert from a hashtable to resource labels. - /// - internal static ResourceLabels FromHashtable(Hashtable hashtable) - { - if (hashtable == null || hashtable.Count == 0) - return null; - - var annotations = new ResourceLabels(); - foreach (DictionaryEntry kv in hashtable) - { - var key = kv.Key.ToString(); - if (hashtable.TryGetStringArray(key, out var value)) - annotations[key] = value; - } - return annotations; - } - - internal bool Contains(string key, string[] value) - { - if (!TryGetValue(key, out var actual)) - return false; - - if (value == null || value.Length == 0 || (value.Length == 1 && value[0] == "*")) - return true; - - for (var i = 0; i < value.Length; i++) - { - if (Array.IndexOf(actual, value[i]) != -1) - return true; - } - return false; - } -} - -/// -/// Additional resource tags. -/// -public sealed class ResourceTags : Dictionary -{ - private Hashtable _Hashtable; - - /// - /// Create an empty set of resource tags. - /// - public ResourceTags() : base(StringComparer.OrdinalIgnoreCase) { } - - /// - /// Convert from a hashtable to resource tags. - /// - internal static ResourceTags FromHashtable(Hashtable hashtable) - { - if (hashtable == null || hashtable.Count == 0) - return null; - - var tags = new ResourceTags(); - foreach (DictionaryEntry kv in hashtable) - tags[kv.Key.ToString()] = kv.Value.ToString(); - - return tags; - } - - /// - /// Convert from a dictionary of string pairs to resource tags. - /// - internal static ResourceTags FromDictionary(Dictionary dictionary) - { - if (dictionary == null) - return null; - - var tags = new ResourceTags(); - foreach (var kv in dictionary) - tags[kv.Key] = kv.Value; - - return tags; - } - - /// - /// Convert resource tags to a hashtable. - /// - public Hashtable ToHashtable() - { - _Hashtable ??= new ReadOnlyHashtable(this, StringComparer.OrdinalIgnoreCase); - return _Hashtable; - } - - /// - /// Check if a specific resource tag exists. - /// - internal bool Contains(object key, object value) - { - if (key == null || value == null || key is not string k || !ContainsKey(k)) - return false; - - if (TryArray(value, out var values)) - { - for (var i = 0; i < values.Length; i++) - { - if (Comparer.Equals(values[i], this[k])) - return true; - } - return false; - } - var v = value.ToString(); - return v == "*" || Comparer.Equals(v, this[k]); - } - - private static bool TryArray(object o, out string[] values) - { - values = null; - if (o is string[] sArray) - { - values = sArray; - return true; - } - if (o is IEnumerable oValues) - { - var result = new List(); - foreach (var obj in oValues) - result.Add(obj.ToString()); - - values = result.ToArray(); - return true; - } - return false; - } - - /// - /// Convert the resourecs tags to a display string for PowerShell views. - /// - /// - public string ToViewString() - { - var sb = new StringBuilder(); - var i = 0; - - foreach (var kv in this) - { - if (i > 0) - sb.Append(System.Environment.NewLine); - - sb.Append(kv.Key); - sb.Append('='); - sb.Append('\''); - sb.Append(kv.Value); - sb.Append('\''); - i++; - } - return sb.ToString(); - } -} - -/// -/// Additional resource metadata. -/// -public sealed class ResourceMetadata -{ - /// - /// Create an empty set of metadata. - /// - public ResourceMetadata() - { - Annotations = new ResourceAnnotations(); - Tags = new ResourceTags(); - Labels = new ResourceLabels(); - } - - /// - /// The name of the resource. - /// - public string Name { get; set; } - - /// - /// A non-localized display name for the resource. - /// - public string DisplayName { get; set; } - - /// - /// A non-localized description of the resource. - /// - public string Description { get; set; } - - /// - /// A opaque reference for the resource. - /// - public string Ref { get; set; } - - /// - /// Additional aliases for the resource. - /// - public string[] Alias { get; set; } - - /// - /// Any resource annotations. - /// - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] - public ResourceAnnotations Annotations { get; set; } - - /// - /// Any resource tags. - /// - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] - public ResourceTags Tags { get; set; } - - /// - /// Any taxonomy references. - /// - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] - public ResourceLabels Labels { get; set; } - - /// - /// A URL to documentation for the resource. - /// - public string Link { get; set; } -} - -/// -/// The source location of the resource. -/// -public sealed class ResourceExtent -{ - /// - /// The file where the resource is located. - /// - public string File { get; set; } - - /// - /// The name of the module if the resource is contained within a module. - /// - public string Module { get; set; } -} - /// /// A base class for resources. /// @@ -550,132 +85,3 @@ protected internal Resource(ResourceKind kind, string apiVersion, SourceFile sou /// public ISourceExtent Extent { get; } } - -/// -/// A base class for built-in resource types. -/// -/// The type of the related for the resource. -public abstract class InternalResource : Resource, IResource, IAnnotated where TSpec : Spec, new() -{ - private readonly Dictionary _Annotations; - - private protected InternalResource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec) - : base(kind, apiVersion, source, metadata, info, extent, spec) - { - _Annotations = new Dictionary(); - Obsolete = ResourceHelper.IsObsolete(metadata); - Flags |= ResourceHelper.IsObsolete(metadata) ? ResourceFlags.Obsolete : ResourceFlags.None; - } - - [YamlIgnore()] - internal readonly bool Obsolete; - - [YamlIgnore()] - internal ResourceFlags Flags { get; } - - ResourceKind IResource.Kind => Kind; - - string IResource.ApiVersion => ApiVersion; - - string IResource.Name => Name; - - // Not supported with base resources. - ResourceId? IResource.Ref => null; - - // Not supported with base resources. - ResourceId[] IResource.Alias => null; - - ResourceTags IResource.Tags => Metadata.Tags; - - ResourceLabels IResource.Labels => Metadata.Labels; - - ResourceFlags IResource.Flags => Flags; - - TAnnotation IAnnotated.GetAnnotation() - { - return _Annotations.TryGetValue(typeof(TAnnotation), out var annotation) ? (TAnnotation)annotation : null; - } - - void IAnnotated.SetAnnotation(TAnnotation annotation) - { - _Annotations[typeof(TAnnotation)] = annotation; - } -} - -internal static class ResourceHelper -{ - private const string ANNOTATION_OBSOLETE = "obsolete"; - - private const char SCOPE_SEPARATOR = '\\'; - - internal static string GetIdString(string scope, string name) - { - return name.IndexOf(SCOPE_SEPARATOR) >= 0 - ? name - : string.Concat( - LanguageScope.Normalize(scope), - SCOPE_SEPARATOR, - name - ); - } - - internal static void ParseIdString(string defaultScope, string id, out string scope, out string name) - { - ParseIdString(id, out scope, out name); - scope ??= LanguageScope.Normalize(defaultScope); - } - - internal static void ParseIdString(string id, out string scope, out string name) - { - scope = null; - name = null; - if (string.IsNullOrEmpty(id)) - return; - - var scopeSeparator = id.IndexOf(SCOPE_SEPARATOR); - scope = scopeSeparator >= 0 ? id.Substring(0, scopeSeparator) : null; - name = id.Substring(scopeSeparator + 1); - } - - /// - /// Checks each resource name and converts each into a full qualified . - /// - /// The default scope to use if the resource name if not fully qualified. - /// An array of names. Qualified names (RuleIds) supplied are left intact. - /// The of the . - /// An array of RuleIds. - internal static ResourceId[] GetRuleId(string defaultScope, string[] name, ResourceIdKind kind) - { - if (name == null || name.Length == 0) - return null; - - var result = new ResourceId[name.Length]; - for (var i = 0; i < name.Length; i++) - result[i] = GetRuleId(defaultScope, name[i], kind); - - return (result.Length == 0) ? null : result; - } - - internal static ResourceId GetRuleId(string defaultScope, string name, ResourceIdKind kind) - { - return name.IndexOf(SCOPE_SEPARATOR) > 0 ? ResourceId.Parse(name, kind) : new ResourceId(defaultScope, name, kind); - } - - internal static ResourceId? GetIdNullable(string scope, string name, ResourceIdKind kind) - { - return string.IsNullOrEmpty(name) ? null : new ResourceId(scope, name, kind); - } - - internal static bool IsObsolete(ResourceMetadata metadata) - { - return metadata != null && - metadata.Annotations != null && - metadata.Annotations.TryGetBool(ANNOTATION_OBSOLETE, out var obsolete) - && obsolete.GetValueOrDefault(false); - } - - internal static SeverityLevel GetLevel(SeverityLevel? level) - { - return !level.HasValue || level.Value == SeverityLevel.None ? RuleV1.DEFAULT_LEVEL : level.Value; - } -} diff --git a/src/PSRule/Definitions/ResourceAnnotation.cs b/src/PSRule/Definitions/ResourceAnnotation.cs new file mode 100644 index 0000000000..ab524561e5 --- /dev/null +++ b/src/PSRule/Definitions/ResourceAnnotation.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// A base resource annotation. +/// +internal abstract class ResourceAnnotation +{ + +} diff --git a/src/PSRule/Definitions/ResourceAnnotations.cs b/src/PSRule/Definitions/ResourceAnnotations.cs new file mode 100644 index 0000000000..73431f32ca --- /dev/null +++ b/src/PSRule/Definitions/ResourceAnnotations.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// Additional resource annotations. +/// +public sealed class ResourceAnnotations : Dictionary +{ + +} diff --git a/src/PSRule/Definitions/ResourceBuilder.cs b/src/PSRule/Definitions/ResourceBuilder.cs new file mode 100644 index 0000000000..e438574916 --- /dev/null +++ b/src/PSRule/Definitions/ResourceBuilder.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Converters.Yaml; +using PSRule.Pipeline; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; +using YamlDotNet.Serialization.NodeDeserializers; + +namespace PSRule.Definitions; + +internal sealed class ResourceBuilder +{ + private readonly List _Output; + private readonly IDeserializer _Deserializer; + + internal ResourceBuilder() + { + _Output = new List(); + _Deserializer = new DeserializerBuilder() + .IgnoreUnmatchedProperties() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeConverter(new FieldMapYamlTypeConverter()) + .WithTypeConverter(new StringArrayMapConverter()) + .WithTypeConverter(new StringArrayConverter()) + .WithNodeDeserializer( + inner => new ResourceNodeDeserializer(new LanguageExpressionDeserializer(inner)), + s => s.InsteadOf()) + .Build(); + } + + internal void FromFile(SourceFile file) + { + using var reader = new StreamReader(file.Path); + var parser = new YamlDotNet.Core.Parser(reader); + parser.TryConsume(out _); + while (parser.Current is DocumentStart) + { + var item = _Deserializer.Deserialize(parser: parser); + if (item == null || item.Block == null) + continue; + + _Output.Add(item.Block); + } + } + + internal IEnumerable Build() + { + return _Output.Count == 0 ? Array.Empty() : _Output.ToArray(); + } +} diff --git a/src/PSRule/Definitions/ResourceExtent.cs b/src/PSRule/Definitions/ResourceExtent.cs new file mode 100644 index 0000000000..e12b5cd1c1 --- /dev/null +++ b/src/PSRule/Definitions/ResourceExtent.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// The source location of the resource. +/// +public sealed class ResourceExtent +{ + /// + /// The file where the resource is located. + /// + public string File { get; set; } + + /// + /// The name of the module if the resource is contained within a module. + /// + public string Module { get; set; } +} diff --git a/src/PSRule/Definitions/ResourceFlags.cs b/src/PSRule/Definitions/ResourceFlags.cs new file mode 100644 index 0000000000..d7245458cf --- /dev/null +++ b/src/PSRule/Definitions/ResourceFlags.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// Additional flags that indicate the status of the resource. +/// +[Flags] +public enum ResourceFlags +{ + /// + /// No flags are set. + /// + None = 0, + + /// + /// The resource is obsolete. + /// + Obsolete = 1 +} diff --git a/src/PSRule/Definitions/ResourceHelper.cs b/src/PSRule/Definitions/ResourceHelper.cs new file mode 100644 index 0000000000..1fccf29e13 --- /dev/null +++ b/src/PSRule/Definitions/ResourceHelper.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Definitions.Rules; +using PSRule.Runtime; + +namespace PSRule.Definitions; + +internal static class ResourceHelper +{ + private const string ANNOTATION_OBSOLETE = "obsolete"; + + private const char SCOPE_SEPARATOR = '\\'; + + internal static string GetIdString(string scope, string name) + { + return name.IndexOf(SCOPE_SEPARATOR) >= 0 + ? name + : string.Concat( + LanguageScope.Normalize(scope), + SCOPE_SEPARATOR, + name + ); + } + + internal static void ParseIdString(string defaultScope, string id, out string scope, out string name) + { + ParseIdString(id, out scope, out name); + scope ??= LanguageScope.Normalize(defaultScope); + } + + internal static void ParseIdString(string id, out string scope, out string name) + { + scope = null; + name = null; + if (string.IsNullOrEmpty(id)) + return; + + var scopeSeparator = id.IndexOf(SCOPE_SEPARATOR); + scope = scopeSeparator >= 0 ? id.Substring(0, scopeSeparator) : null; + name = id.Substring(scopeSeparator + 1); + } + + /// + /// Checks each resource name and converts each into a full qualified . + /// + /// The default scope to use if the resource name if not fully qualified. + /// An array of names. Qualified names (RuleIds) supplied are left intact. + /// The of the . + /// An array of RuleIds. + internal static ResourceId[] GetRuleId(string defaultScope, string[] name, ResourceIdKind kind) + { + if (name == null || name.Length == 0) + return null; + + var result = new ResourceId[name.Length]; + for (var i = 0; i < name.Length; i++) + result[i] = GetRuleId(defaultScope, name[i], kind); + + return (result.Length == 0) ? null : result; + } + + internal static ResourceId GetRuleId(string defaultScope, string name, ResourceIdKind kind) + { + return name.IndexOf(SCOPE_SEPARATOR) > 0 ? ResourceId.Parse(name, kind) : new ResourceId(defaultScope, name, kind); + } + + internal static ResourceId? GetIdNullable(string scope, string name, ResourceIdKind kind) + { + return string.IsNullOrEmpty(name) ? null : new ResourceId(scope, name, kind); + } + + internal static bool IsObsolete(ResourceMetadata metadata) + { + return metadata != null && + metadata.Annotations != null && + metadata.Annotations.TryGetBool(ANNOTATION_OBSOLETE, out var obsolete) + && obsolete.GetValueOrDefault(false); + } + + internal static SeverityLevel GetLevel(SeverityLevel? level) + { + return !level.HasValue || level.Value == SeverityLevel.None ? RuleV1.DEFAULT_LEVEL : level.Value; + } +} diff --git a/src/PSRule/Definitions/ResourceKind.cs b/src/PSRule/Definitions/ResourceKind.cs new file mode 100644 index 0000000000..495147fb76 --- /dev/null +++ b/src/PSRule/Definitions/ResourceKind.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// The type of resource. +/// +public enum ResourceKind +{ + /// + /// Unknown or empty. + /// + None = 0, + + /// + /// A rule resource. + /// + Rule = 1, + + /// + /// A baseline resource. + /// + Baseline = 2, + + /// + /// A module configuration resource. + /// + ModuleConfig = 3, + + /// + /// A selector resource. + /// + Selector = 4, + + /// + /// A convention. + /// + Convention = 5, + + /// + /// A suppression group. + /// + SuppressionGroup = 6 +} diff --git a/src/PSRule/Definitions/ResourceLabels.cs b/src/PSRule/Definitions/ResourceLabels.cs new file mode 100644 index 0000000000..a92f0b8fb2 --- /dev/null +++ b/src/PSRule/Definitions/ResourceLabels.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; + +namespace PSRule.Definitions; + +/// +/// Additional resource taxonomy references. +/// +public sealed class ResourceLabels : Dictionary +{ + /// + /// Create an empty set of resource labels. + /// + public ResourceLabels() : base(StringComparer.OrdinalIgnoreCase) { } + + /// + /// Convert from a hashtable to resource labels. + /// + internal static ResourceLabels FromHashtable(Hashtable hashtable) + { + if (hashtable == null || hashtable.Count == 0) + return null; + + var annotations = new ResourceLabels(); + foreach (DictionaryEntry kv in hashtable) + { + var key = kv.Key.ToString(); + if (hashtable.TryGetStringArray(key, out var value)) + annotations[key] = value; + } + return annotations; + } + + internal bool Contains(string key, string[] value) + { + if (!TryGetValue(key, out var actual)) + return false; + + if (value == null || value.Length == 0 || (value.Length == 1 && value[0] == "*")) + return true; + + for (var i = 0; i < value.Length; i++) + { + if (Array.IndexOf(actual, value[i]) != -1) + return true; + } + return false; + } +} diff --git a/src/PSRule/Definitions/ResourceMetadata.cs b/src/PSRule/Definitions/ResourceMetadata.cs new file mode 100644 index 0000000000..5eb4b735c3 --- /dev/null +++ b/src/PSRule/Definitions/ResourceMetadata.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using YamlDotNet.Serialization; + +namespace PSRule.Definitions; + +/// +/// Additional resource metadata. +/// +public sealed class ResourceMetadata +{ + /// + /// Create an empty set of metadata. + /// + public ResourceMetadata() + { + Annotations = new ResourceAnnotations(); + Tags = new ResourceTags(); + Labels = new ResourceLabels(); + } + + /// + /// The name of the resource. + /// + public string Name { get; set; } + + /// + /// A non-localized display name for the resource. + /// + public string DisplayName { get; set; } + + /// + /// A non-localized description of the resource. + /// + public string Description { get; set; } + + /// + /// A opaque reference for the resource. + /// + public string Ref { get; set; } + + /// + /// Additional aliases for the resource. + /// + public string[] Alias { get; set; } + + /// + /// Any resource annotations. + /// + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] + public ResourceAnnotations Annotations { get; set; } + + /// + /// Any resource tags. + /// + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] + public ResourceTags Tags { get; set; } + + /// + /// Any taxonomy references. + /// + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] + public ResourceLabels Labels { get; set; } + + /// + /// A URL to documentation for the resource. + /// + public string Link { get; set; } +} diff --git a/src/PSRule/Definitions/ResourceObject.cs b/src/PSRule/Definitions/ResourceObject.cs new file mode 100644 index 0000000000..76fa53312a --- /dev/null +++ b/src/PSRule/Definitions/ResourceObject.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// A resource object. +/// +public sealed class ResourceObject +{ + internal ResourceObject(IResource block) + { + Block = block; + } + + internal IResource Block { get; } + + internal bool Visit(IResourceVisitor visitor) + { + return Block != null && visitor != null && visitor.Visit(Block); + } +} diff --git a/src/PSRule/Definitions/ResourceRef.cs b/src/PSRule/Definitions/ResourceRef.cs new file mode 100644 index 0000000000..78ced225a3 --- /dev/null +++ b/src/PSRule/Definitions/ResourceRef.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +internal abstract class ResourceRef +{ + public readonly string Id; + public readonly ResourceKind Kind; + + protected ResourceRef(string id, ResourceKind kind) + { + Kind = kind; + Id = id; + } +} diff --git a/src/PSRule/Definitions/ResourceTags.cs b/src/PSRule/Definitions/ResourceTags.cs new file mode 100644 index 0000000000..f5c3787d07 --- /dev/null +++ b/src/PSRule/Definitions/ResourceTags.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using System.Diagnostics; +using System.Text; + +namespace PSRule.Definitions; + +/// +/// Additional resource tags. +/// +public sealed class ResourceTags : Dictionary +{ + private Hashtable _Hashtable; + + /// + /// Create an empty set of resource tags. + /// + [DebuggerStepThrough] + public ResourceTags() : base(StringComparer.OrdinalIgnoreCase) { } + + /// + /// Convert from a hashtable to resource tags. + /// + [DebuggerStepThrough] + internal static ResourceTags FromHashtable(Hashtable hashtable) + { + if (hashtable == null || hashtable.Count == 0) + return null; + + var tags = new ResourceTags(); + foreach (DictionaryEntry kv in hashtable) + tags[kv.Key.ToString()] = kv.Value.ToString(); + + return tags; + } + + /// + /// Convert from a dictionary of string pairs to resource tags. + /// + [DebuggerStepThrough] + internal static ResourceTags FromDictionary(Dictionary dictionary) + { + if (dictionary == null) + return null; + + var tags = new ResourceTags(); + foreach (var kv in dictionary) + tags[kv.Key] = kv.Value; + + return tags; + } + + /// + /// Convert resource tags to a hashtable. + /// + [DebuggerStepThrough] + public Hashtable ToHashtable() + { + _Hashtable ??= new ReadOnlyHashtable(this, StringComparer.OrdinalIgnoreCase); + return _Hashtable; + } + + /// + /// Check if a specific resource tag exists. + /// + internal bool Contains(object key, object value) + { + if (key == null || value == null || key is not string k || !ContainsKey(k)) + return false; + + if (TryArray(value, out var values)) + { + for (var i = 0; i < values.Length; i++) + { + if (Comparer.Equals(values[i], this[k])) + return true; + } + return false; + } + var v = value.ToString(); + return v == "*" || Comparer.Equals(v, this[k]); + } + + private static bool TryArray(object o, out string[] values) + { + values = null; + if (o is string[] sArray) + { + values = sArray; + return true; + } + if (o is IEnumerable oValues) + { + var result = new List(); + foreach (var obj in oValues) + result.Add(obj.ToString()); + + values = result.ToArray(); + return true; + } + return false; + } + + /// + /// Convert the resourecs tags to a display string for PowerShell views. + /// + /// + public string ToViewString() + { + var sb = new StringBuilder(); + var i = 0; + + foreach (var kv in this) + { + if (i > 0) + sb.Append(System.Environment.NewLine); + + sb.Append(kv.Key); + sb.Append('='); + sb.Append('\''); + sb.Append(kv.Value); + sb.Append('\''); + i++; + } + return sb.ToString(); + } +} diff --git a/src/PSRule/Definitions/Rules/RuleFilter.cs b/src/PSRule/Definitions/Rules/RuleFilter.cs index f3c7bba231..cd208f6fe8 100644 --- a/src/PSRule/Definitions/Rules/RuleFilter.cs +++ b/src/PSRule/Definitions/Rules/RuleFilter.cs @@ -48,12 +48,6 @@ public RuleFilter(string[] include, Hashtable tag, string[] exclude, bool? inclu ResourceKind IResourceFilter.Kind => ResourceKind.Rule; - internal bool Match(string name, ResourceTags tag, ResourceLabels labels) - { - return !IsExcluded(new ResourceId[] { ResourceId.Parse(name) }) && - IsIncluded(new ResourceId[] { ResourceId.Parse(name) }, tag, labels); - } - /// /// Matches if the RuleId is contained or any tag is matched /// diff --git a/src/PSRule/Definitions/ValidateResourceAnnotation.cs b/src/PSRule/Definitions/ValidateResourceAnnotation.cs new file mode 100644 index 0000000000..413c52c6bf --- /dev/null +++ b/src/PSRule/Definitions/ValidateResourceAnnotation.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Definitions; + +/// +/// Annotation used to flag validation issues. +/// +internal sealed class ValidateResourceAnnotation : ResourceAnnotation +{ + +} diff --git a/src/PSRule/Help/MarkdownReader.cs b/src/PSRule/Help/MarkdownReader.cs index 7f44bc0e1d..b37d362931 100644 --- a/src/PSRule/Help/MarkdownReader.cs +++ b/src/PSRule/Help/MarkdownReader.cs @@ -3,13 +3,6 @@ namespace PSRule.Help; -internal enum MarkdownReaderMode -{ - None, - - List -} - /// /// Stateful markdown reader. /// diff --git a/src/PSRule/Help/MarkdownReaderMode.cs b/src/PSRule/Help/MarkdownReaderMode.cs new file mode 100644 index 0000000000..5d83f80f7a --- /dev/null +++ b/src/PSRule/Help/MarkdownReaderMode.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Help; + +internal enum MarkdownReaderMode +{ + None, + + List +} diff --git a/src/PSRule/Help/MarkdownStream.cs b/src/PSRule/Help/MarkdownStream.cs index d9ae6d2863..2d58a2dfed 100644 --- a/src/PSRule/Help/MarkdownStream.cs +++ b/src/PSRule/Help/MarkdownStream.cs @@ -7,49 +7,6 @@ namespace PSRule.Help; internal delegate bool CharacterMatchDelegate(char c); -[DebuggerDisplay("StartPos = (L: {Start}, C: {Column}), EndPos = (L: {End}, C: {Column.End}), Text = {Text}")] -internal sealed class SourceExtent -{ - private readonly string _Source; - - // Lazily cache extracted text - private string _Text; - - internal SourceExtent(string source, string path, int start, int end, int line, int column) - { - _Text = null; - _Source = source; - Path = path; - Start = start; - End = end; - Line = line; - Column = column; - } - - public readonly string Path; - - public readonly int Start; - - public readonly int End; - - public readonly int Line; - - public readonly int Column; - - public string Text - { - get - { - if (_Text == null) - { - _Text = _Source.Substring(Start, (End - Start)); - } - - return _Text; - } - } -} - [DebuggerDisplay("Position = {Position}, Current = {Current}")] internal sealed class MarkdownStream { diff --git a/src/PSRule/Help/MarkdownToken.cs b/src/PSRule/Help/MarkdownToken.cs index 96bcb55ec1..df130c8c65 100644 --- a/src/PSRule/Help/MarkdownToken.cs +++ b/src/PSRule/Help/MarkdownToken.cs @@ -5,70 +5,6 @@ namespace PSRule.Help; -internal enum MarkdownTokenType -{ - None = 0, - - Text, - - Header, - - FencedBlock, - - LineBreak, - - ParagraphStart, - - ParagraphEnd, - - LinkReference, - - Link, - - LinkReferenceDefinition, - - YamlKeyValue -} - -[Flags()] -internal enum MarkdownTokens -{ - None = 0, - - Italic = 1, - - Bold = 2, - - Code = 4, - - LineEnding = 8, - - LineBreak = 16, - - Preserve = 32, - - // Accelerators - PreserveLineEnding = 40 -} - -internal static class MarkdownTokenFlagExtensions -{ - public static bool IsEnding(this MarkdownTokens flags) - { - return flags.HasFlag(MarkdownTokens.LineEnding) || flags.HasFlag(MarkdownTokens.LineBreak); - } - - public static bool IsLineBreak(this MarkdownTokens flags) - { - return flags.HasFlag(MarkdownTokens.LineBreak); - } - - public static bool ShouldPreserve(this MarkdownTokens flags) - { - return flags.HasFlag(MarkdownTokens.Preserve); - } -} - [DebuggerDisplay("Type = {Type}, Text = {Text}")] internal sealed class MarkdownToken { diff --git a/src/PSRule/Help/MarkdownTokenFlagExtensions.cs b/src/PSRule/Help/MarkdownTokenFlagExtensions.cs new file mode 100644 index 0000000000..fa00249750 --- /dev/null +++ b/src/PSRule/Help/MarkdownTokenFlagExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Help; + +internal static class MarkdownTokenFlagExtensions +{ + public static bool IsEnding(this MarkdownTokens flags) + { + return flags.HasFlag(MarkdownTokens.LineEnding) || flags.HasFlag(MarkdownTokens.LineBreak); + } + + public static bool IsLineBreak(this MarkdownTokens flags) + { + return flags.HasFlag(MarkdownTokens.LineBreak); + } + + public static bool ShouldPreserve(this MarkdownTokens flags) + { + return flags.HasFlag(MarkdownTokens.Preserve); + } +} diff --git a/src/PSRule/Help/MarkdownTokenType.cs b/src/PSRule/Help/MarkdownTokenType.cs new file mode 100644 index 0000000000..5fef3f0f0a --- /dev/null +++ b/src/PSRule/Help/MarkdownTokenType.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Help; + +internal enum MarkdownTokenType +{ + None = 0, + + Text, + + Header, + + FencedBlock, + + LineBreak, + + ParagraphStart, + + ParagraphEnd, + + LinkReference, + + Link, + + LinkReferenceDefinition, + + YamlKeyValue +} diff --git a/src/PSRule/Help/MarkdownTokens.cs b/src/PSRule/Help/MarkdownTokens.cs new file mode 100644 index 0000000000..b6173f64f9 --- /dev/null +++ b/src/PSRule/Help/MarkdownTokens.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Help; + +[Flags()] +internal enum MarkdownTokens +{ + None = 0, + + Italic = 1, + + Bold = 2, + + Code = 4, + + LineEnding = 8, + + LineBreak = 16, + + Preserve = 32, + + // Accelerators + PreserveLineEnding = 40 +} diff --git a/src/PSRule/Help/SourceExtent.cs b/src/PSRule/Help/SourceExtent.cs new file mode 100644 index 0000000000..31a66601a9 --- /dev/null +++ b/src/PSRule/Help/SourceExtent.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; + +namespace PSRule.Help; + +[DebuggerDisplay("StartPos = (L: {Start}, C: {Column}), EndPos = (L: {End}, C: {Column.End}), Text = {Text}")] +internal sealed class SourceExtent +{ + private readonly string _Source; + + // Lazily cache extracted text + private string _Text; + + internal SourceExtent(string source, string path, int start, int end, int line, int column) + { + _Text = null; + _Source = source; + Path = path; + Start = start; + End = end; + Line = line; + Column = column; + } + + public readonly string Path; + + public readonly int Start; + + public readonly int End; + + public readonly int Line; + + public readonly int Column; + + public string Text + { + get + { + if (_Text == null) + { + _Text = _Source.Substring(Start, (End - Start)); + } + + return _Text; + } + } +} diff --git a/src/PSRule/Help/TokenStream.cs b/src/PSRule/Help/TokenStream.cs index cfd54a5bcb..c2e032fed1 100644 --- a/src/PSRule/Help/TokenStream.cs +++ b/src/PSRule/Help/TokenStream.cs @@ -6,193 +6,6 @@ namespace PSRule.Help; -internal static class TokenStreamExtensions -{ - /// - /// Add a header. - /// - public static void Header(this TokenStream stream, int depth, string text, SourceExtent extent, bool lineBreak) - { - stream.Add(new MarkdownToken() - { - Depth = depth, - Extent = extent, - Text = text, - Type = MarkdownTokenType.Header, - Flag = lineBreak ? MarkdownTokens.LineBreak : MarkdownTokens.LineEnding | MarkdownTokens.Preserve - }); - } - - public static void YamlKeyValue(this TokenStream stream, string key, string value) - { - stream.Add(new MarkdownToken() - { - Meta = key, - Text = value, - Type = MarkdownTokenType.YamlKeyValue - }); - } - - /// - /// Add a code fence. - /// - public static void FencedBlock(this TokenStream stream, string meta, string text, SourceExtent extent, bool lineBreak) - { - stream.Add(new MarkdownToken() - { - Extent = extent, - Meta = meta, - Text = text, - Type = MarkdownTokenType.FencedBlock, - Flag = (lineBreak ? MarkdownTokens.LineBreak : MarkdownTokens.LineEnding) | MarkdownTokens.Preserve - }); - } - - /// - /// Add a line break. - /// - public static void LineBreak(this TokenStream stream, int count) - { - // Ignore line break at the very start of file - if (stream.Count == 0) - { - return; - } - - for (var i = 0; i < count; i++) - { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LineBreak, Flag = MarkdownTokens.LineBreak }); - } - } - - public static void Text(this TokenStream stream, string text, MarkdownTokens flag = MarkdownTokens.None) - { - if (MergeText(stream.Current, text, flag)) - { - return; - } - - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.Text, Text = text, Flag = flag }); - } - - private static bool MergeText(MarkdownToken current, string text, MarkdownTokens flag) - { - // Only allow merge if the previous token was text - if (current == null || current.Type != MarkdownTokenType.Text) - { - return false; - } - - if (current.Flag.ShouldPreserve()) - { - return false; - } - - // If the previous token was text, lessen the break but still don't allow merging - if (current.Flag.HasFlag(MarkdownTokens.LineBreak) && !current.Flag.ShouldPreserve()) - { - return false; - } - - // Text must have the same flags set - if (current.Flag.HasFlag(MarkdownTokens.Italic) != flag.HasFlag(MarkdownTokens.Italic)) - { - return false; - } - - if (current.Flag.HasFlag(MarkdownTokens.Bold) != flag.HasFlag(MarkdownTokens.Bold)) - { - return false; - } - - if (current.Flag.HasFlag(MarkdownTokens.Code) != flag.HasFlag(MarkdownTokens.Code)) - { - return false; - } - - if (!current.Flag.IsEnding()) - { - current.Text = string.Concat(current.Text, text); - } - else if (current.Flag == MarkdownTokens.LineEnding) - { - return false; - } - - // Take on the ending of the merged token - current.Flag = flag; - - return true; - } - - public static void Link(this TokenStream stream, string text, string uri) - { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.Link, Meta = text, Text = uri }); - } - - public static void LinkReference(this TokenStream stream, string text, string linkRef) - { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LinkReference, Meta = text, Text = linkRef }); - } - - public static void LinkReferenceDefinition(this TokenStream stream, string text, string linkTarget) - { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LinkReferenceDefinition, Meta = text, Text = linkTarget }); - } - - /// - /// Add a marker for the start of a paragraph. - /// - public static void ParagraphStart(this TokenStream stream) - { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphStart }); - } - - /// - /// Add a marker for the end of a paragraph. - /// - public static void ParagraphEnd(this TokenStream stream) - { - if (stream.Count > 0) - { - if (stream.Current.Type == MarkdownTokenType.ParagraphStart) - { - stream.Pop(); - - return; - } - - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphEnd }); - } - } - - public static IEnumerable GetSection(this TokenStream stream, string header) - { - return stream.Count == 0 - ? Enumerable.Empty() - : stream - // Skip until we reach the header - .SkipWhile(token => token.Type != MarkdownTokenType.Header || token.Text != header) - - // Get all tokens to the next header - .Skip(1) - .TakeWhile(token => token.Type != MarkdownTokenType.Header); - } - - public static IEnumerable GetSections(this TokenStream stream) - { - return stream.Count == 0 - ? Enumerable.Empty() - : stream - // Skip until we reach the header - .SkipWhile(token => token.Type != MarkdownTokenType.Header) - - // Get all tokens to the next header - .Skip(1) - .TakeWhile(token => token.Type != MarkdownTokenType.Header); - } -} - [DebuggerDisplay("Current = {Current?.Text}")] internal sealed class TokenStream : IEnumerable { diff --git a/src/PSRule/Help/TokenStreamExtensions.cs b/src/PSRule/Help/TokenStreamExtensions.cs new file mode 100644 index 0000000000..e7c0cdb9f8 --- /dev/null +++ b/src/PSRule/Help/TokenStreamExtensions.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Help; + +internal static class TokenStreamExtensions +{ + /// + /// Add a header. + /// + public static void Header(this TokenStream stream, int depth, string text, SourceExtent extent, bool lineBreak) + { + stream.Add(new MarkdownToken() + { + Depth = depth, + Extent = extent, + Text = text, + Type = MarkdownTokenType.Header, + Flag = lineBreak ? MarkdownTokens.LineBreak : MarkdownTokens.LineEnding | MarkdownTokens.Preserve + }); + } + + public static void YamlKeyValue(this TokenStream stream, string key, string value) + { + stream.Add(new MarkdownToken() + { + Meta = key, + Text = value, + Type = MarkdownTokenType.YamlKeyValue + }); + } + + /// + /// Add a code fence. + /// + public static void FencedBlock(this TokenStream stream, string meta, string text, SourceExtent extent, bool lineBreak) + { + stream.Add(new MarkdownToken() + { + Extent = extent, + Meta = meta, + Text = text, + Type = MarkdownTokenType.FencedBlock, + Flag = (lineBreak ? MarkdownTokens.LineBreak : MarkdownTokens.LineEnding) | MarkdownTokens.Preserve + }); + } + + /// + /// Add a line break. + /// + public static void LineBreak(this TokenStream stream, int count) + { + // Ignore line break at the very start of file + if (stream.Count == 0) + { + return; + } + + for (var i = 0; i < count; i++) + { + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LineBreak, Flag = MarkdownTokens.LineBreak }); + } + } + + public static void Text(this TokenStream stream, string text, MarkdownTokens flag = MarkdownTokens.None) + { + if (MergeText(stream.Current, text, flag)) + { + return; + } + + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.Text, Text = text, Flag = flag }); + } + + private static bool MergeText(MarkdownToken current, string text, MarkdownTokens flag) + { + // Only allow merge if the previous token was text + if (current == null || current.Type != MarkdownTokenType.Text) + { + return false; + } + + if (current.Flag.ShouldPreserve()) + { + return false; + } + + // If the previous token was text, lessen the break but still don't allow merging + if (current.Flag.HasFlag(MarkdownTokens.LineBreak) && !current.Flag.ShouldPreserve()) + { + return false; + } + + // Text must have the same flags set + if (current.Flag.HasFlag(MarkdownTokens.Italic) != flag.HasFlag(MarkdownTokens.Italic)) + { + return false; + } + + if (current.Flag.HasFlag(MarkdownTokens.Bold) != flag.HasFlag(MarkdownTokens.Bold)) + { + return false; + } + + if (current.Flag.HasFlag(MarkdownTokens.Code) != flag.HasFlag(MarkdownTokens.Code)) + { + return false; + } + + if (!current.Flag.IsEnding()) + { + current.Text = string.Concat(current.Text, text); + } + else if (current.Flag == MarkdownTokens.LineEnding) + { + return false; + } + + // Take on the ending of the merged token + current.Flag = flag; + + return true; + } + + public static void Link(this TokenStream stream, string text, string uri) + { + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.Link, Meta = text, Text = uri }); + } + + public static void LinkReference(this TokenStream stream, string text, string linkRef) + { + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LinkReference, Meta = text, Text = linkRef }); + } + + public static void LinkReferenceDefinition(this TokenStream stream, string text, string linkTarget) + { + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LinkReferenceDefinition, Meta = text, Text = linkTarget }); + } + + /// + /// Add a marker for the start of a paragraph. + /// + public static void ParagraphStart(this TokenStream stream) + { + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphStart }); + } + + /// + /// Add a marker for the end of a paragraph. + /// + public static void ParagraphEnd(this TokenStream stream) + { + if (stream.Count > 0) + { + if (stream.Current.Type == MarkdownTokenType.ParagraphStart) + { + stream.Pop(); + + return; + } + + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphEnd }); + } + } + + public static IEnumerable GetSection(this TokenStream stream, string header) + { + return stream.Count == 0 + ? Enumerable.Empty() + : stream + // Skip until we reach the header + .SkipWhile(token => token.Type != MarkdownTokenType.Header || token.Text != header) + + // Get all tokens to the next header + .Skip(1) + .TakeWhile(token => token.Type != MarkdownTokenType.Header); + } + + public static IEnumerable GetSections(this TokenStream stream) + { + return stream.Count == 0 + ? Enumerable.Empty() + : stream + // Skip until we reach the header + .SkipWhile(token => token.Type != MarkdownTokenType.Header) + + // Get all tokens to the next header + .Skip(1) + .TakeWhile(token => token.Type != MarkdownTokenType.Header); + } +} diff --git a/src/PSRule/Host/AssertVariable.cs b/src/PSRule/Host/AssertVariable.cs new file mode 100644 index 0000000000..5602b92ac0 --- /dev/null +++ b/src/PSRule/Host/AssertVariable.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; +using PSRule.Runtime; + +namespace PSRule.Host; + +/// +/// An assertion helper variable $Assert used during Rule execution. +/// +internal sealed class AssertVariable : PSVariable +{ + private const string VARIABLE_NAME = "Assert"; + private readonly Assert _Value; + + public AssertVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) + { + _Value = new Assert(); + } + + public override object Value => _Value; +} diff --git a/src/PSRule/Host/ConfigurationVariable.cs b/src/PSRule/Host/ConfigurationVariable.cs new file mode 100644 index 0000000000..21976f4551 --- /dev/null +++ b/src/PSRule/Host/ConfigurationVariable.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; +using PSRule.Runtime; + +namespace PSRule.Host; + +internal sealed class ConfigurationVariable : PSVariable +{ + private const string VARIABLE_NAME = "Configuration"; + private readonly Runtime.Configuration _Value; + + public ConfigurationVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) + { + _Value = new Runtime.Configuration(RunspaceContext.CurrentThread); + } + + public override object Value => _Value; +} diff --git a/src/PSRule/Host/Host.cs b/src/PSRule/Host/HostState.cs similarity index 55% rename from src/PSRule/Host/Host.cs rename to src/PSRule/Host/HostState.cs index 409c41f80c..d5d5b4b0e1 100644 --- a/src/PSRule/Host/Host.cs +++ b/src/PSRule/Host/HostState.cs @@ -4,111 +4,9 @@ using System.Management.Automation; using System.Management.Automation.Runspaces; using PSRule.Commands; -using PSRule.Runtime; namespace PSRule.Host; -/// -/// A dynamic variable $PSRule used during Rule execution. -/// -internal sealed class PSRuleVariable : PSVariable -{ - private const string VARIABLE_NAME = "PSRule"; - - private readonly Runtime.PSRule _Value; - - public PSRuleVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new Runtime.PSRule(RunspaceContext.CurrentThread); - } - - public override object Value => _Value; -} - -/// -/// A dynamic variable $Rule used during Rule execution. -/// -internal sealed class RuleVariable : PSVariable -{ - private const string VARIABLE_NAME = "Rule"; - - private readonly Rule _Value; - - public RuleVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new Rule(); - } - - public override object Value => _Value; -} - -/// -/// A dynamic variable $LocalizedData used during Rule execution. -/// -internal sealed class LocalizedDataVariable : PSVariable -{ - private const string VARIABLE_NAME = "LocalizedData"; - - private readonly LocalizedData _Value; - - public LocalizedDataVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new LocalizedData(); - } - - public override object Value => _Value; -} - -/// -/// An assertion helper variable $Assert used during Rule execution. -/// -internal sealed class AssertVariable : PSVariable -{ - private const string VARIABLE_NAME = "Assert"; - private readonly Assert _Value; - - public AssertVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new Assert(); - } - - public override object Value => _Value; -} - -/// -/// A dynamic variable used during Rule execution. -/// -internal sealed class TargetObjectVariable : PSVariable -{ - private const string VARIABLE_NAME = "TargetObject"; - - public TargetObjectVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - - } - - public override object Value => RunspaceContext.CurrentThread.TargetObject?.Value; -} - -internal sealed class ConfigurationVariable : PSVariable -{ - private const string VARIABLE_NAME = "Configuration"; - private readonly Runtime.Configuration _Value; - - public ConfigurationVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new Runtime.Configuration(RunspaceContext.CurrentThread); - } - - public override object Value => _Value; -} - internal static class HostState { /// diff --git a/src/PSRule/Host/LocalizedDataVariable.cs b/src/PSRule/Host/LocalizedDataVariable.cs new file mode 100644 index 0000000000..ff5cf9eeb6 --- /dev/null +++ b/src/PSRule/Host/LocalizedDataVariable.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; +using PSRule.Runtime; + +namespace PSRule.Host; + +/// +/// A dynamic variable $LocalizedData used during Rule execution. +/// +internal sealed class LocalizedDataVariable : PSVariable +{ + private const string VARIABLE_NAME = "LocalizedData"; + + private readonly LocalizedData _Value; + + public LocalizedDataVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) + { + _Value = new LocalizedData(); + } + + public override object Value => _Value; +} diff --git a/src/PSRule/Host/PSRuleVariable.cs b/src/PSRule/Host/PSRuleVariable.cs new file mode 100644 index 0000000000..5c40fc5379 --- /dev/null +++ b/src/PSRule/Host/PSRuleVariable.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; +using PSRule.Runtime; + +namespace PSRule.Host; + +/// +/// A dynamic variable $PSRule used during Rule execution. +/// +internal sealed class PSRuleVariable : PSVariable +{ + private const string VARIABLE_NAME = "PSRule"; + + private readonly Runtime.PSRule _Value; + + public PSRuleVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) + { + _Value = new Runtime.PSRule(RunspaceContext.CurrentThread); + } + + public override object Value => _Value; +} diff --git a/src/PSRule/Host/RuleVariable.cs b/src/PSRule/Host/RuleVariable.cs new file mode 100644 index 0000000000..e0ae39a27c --- /dev/null +++ b/src/PSRule/Host/RuleVariable.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; +using PSRule.Runtime; + +namespace PSRule.Host; + +/// +/// A dynamic variable $Rule used during Rule execution. +/// +internal sealed class RuleVariable : PSVariable +{ + private const string VARIABLE_NAME = "Rule"; + + private readonly Rule _Value; + + public RuleVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) + { + _Value = new Rule(); + } + + public override object Value => _Value; +} diff --git a/src/PSRule/Host/TargetObjectVariable.cs b/src/PSRule/Host/TargetObjectVariable.cs new file mode 100644 index 0000000000..a4f1d82f6d --- /dev/null +++ b/src/PSRule/Host/TargetObjectVariable.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; +using PSRule.Runtime; + +namespace PSRule.Host; + +/// +/// A dynamic variable used during Rule execution. +/// +internal sealed class TargetObjectVariable : PSVariable +{ + private const string VARIABLE_NAME = "TargetObject"; + + public TargetObjectVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) + { + + } + + public override object Value => RunspaceContext.CurrentThread.TargetObject?.Value; +} diff --git a/src/PSRule/Pipeline/Dependencies/LockEntry.cs b/src/PSRule/Pipeline/Dependencies/LockEntry.cs new file mode 100644 index 0000000000..b7952671f6 --- /dev/null +++ b/src/PSRule/Pipeline/Dependencies/LockEntry.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using PSRule.Data; + +namespace PSRule.Pipeline.Dependencies; + +/// +/// An entry within the lock file. +/// +public sealed class LockEntry +{ + /// + /// The version to use. + /// + [JsonProperty("version", NullValueHandling = NullValueHandling.Include)] + public SemanticVersion.Version Version { get; set; } +} diff --git a/src/PSRule/Pipeline/Dependencies/LockFile.cs b/src/PSRule/Pipeline/Dependencies/LockFile.cs index 46d6be3d30..1a4639ef4c 100644 --- a/src/PSRule/Pipeline/Dependencies/LockFile.cs +++ b/src/PSRule/Pipeline/Dependencies/LockFile.cs @@ -3,22 +3,9 @@ using System.Text; using Newtonsoft.Json; -using PSRule.Data; namespace PSRule.Pipeline.Dependencies; -/// -/// An entry within the lock file. -/// -public sealed class LockEntry -{ - /// - /// The version to use. - /// - [JsonProperty("version", NullValueHandling = NullValueHandling.Include)] - public SemanticVersion.Version Version { get; set; } -} - /// /// Define the structure for the PSRule lock file. /// By default, this file is ps-rule.lock.json. diff --git a/src/PSRule/Pipeline/Emitters/EmitterChain.cs b/src/PSRule/Pipeline/Emitters/EmitterChain.cs index 5722323914..a67d65602b 100644 --- a/src/PSRule/Pipeline/Emitters/EmitterChain.cs +++ b/src/PSRule/Pipeline/Emitters/EmitterChain.cs @@ -3,10 +3,9 @@ using PSRule.Emitters; -namespace PSRule.Pipeline.Emitters -{ - /// - /// A chain of emitters. - /// - internal delegate bool EmitterChain(IEmitterContext context, object o, Type type); -} +namespace PSRule.Pipeline.Emitters; + +/// +/// A chain of emitters. +/// +internal delegate bool EmitterChain(IEmitterContext context, object o, Type type); diff --git a/src/PSRule/Pipeline/Emitters/InternalFileInfo.cs b/src/PSRule/Pipeline/Emitters/InternalFileInfo.cs index f9a0a3da22..1d80852396 100644 --- a/src/PSRule/Pipeline/Emitters/InternalFileInfo.cs +++ b/src/PSRule/Pipeline/Emitters/InternalFileInfo.cs @@ -4,51 +4,50 @@ using System.Diagnostics; using PSRule.Data; -namespace PSRule.Pipeline.Emitters +namespace PSRule.Pipeline.Emitters; + +[DebuggerDisplay("{Path}")] +internal sealed class InternalFileInfo : IFileInfo, IDisposable { - [DebuggerDisplay("{Path}")] - internal sealed class InternalFileInfo : IFileInfo, IDisposable - { - private FileStream _Stream; - private bool _Disposed; + private FileStream _Stream; + private bool _Disposed; - public InternalFileInfo(string path, string extension) - { - Path = path; - Extension = extension; - } + public InternalFileInfo(string path, string extension) + { + Path = path; + Extension = extension; + } - /// - public string Path { get; } + /// + public string Path { get; } - /// - public string Extension { get; } + /// + public string Extension { get; } - /// - public IFileStream GetFileStream() - { - _Stream ??= File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - _Stream.Position = 0; - return new InternalFileStream(this, _Stream); - } + /// + public IFileStream GetFileStream() + { + _Stream ??= File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + _Stream.Position = 0; + return new InternalFileStream(this, _Stream); + } - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) - { - _Stream.Dispose(); - } - _Disposed = true; + _Stream.Dispose(); } + _Disposed = true; } + } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } diff --git a/src/PSRule/Pipeline/Emitters/InternalFileStream.cs b/src/PSRule/Pipeline/Emitters/InternalFileStream.cs index d962fee109..cb288b735e 100644 --- a/src/PSRule/Pipeline/Emitters/InternalFileStream.cs +++ b/src/PSRule/Pipeline/Emitters/InternalFileStream.cs @@ -4,52 +4,51 @@ using System.Text; using PSRule.Data; -namespace PSRule.Pipeline.Emitters +namespace PSRule.Pipeline.Emitters; + +internal sealed class InternalFileStream : IFileStream { - internal sealed class InternalFileStream : IFileStream - { - private readonly Stream _Stream; + private readonly Stream _Stream; - private bool _Disposed; + private bool _Disposed; - internal InternalFileStream(IFileInfo info, Stream stream) - { - Info = info; - _Stream = stream; - } + internal InternalFileStream(IFileInfo info, Stream stream) + { + Info = info; + _Stream = stream; + } - /// - public IFileInfo Info { get; } + /// + public IFileInfo Info { get; } - /// - public TextReader AsTextReader() - { - return new StreamReader( - stream: _Stream, - encoding: Encoding.UTF8, - detectEncodingFromByteOrderMarks: true, - bufferSize: 1024, - leaveOpen: true - ); - } + /// + public TextReader AsTextReader() + { + return new StreamReader( + stream: _Stream, + encoding: Encoding.UTF8, + detectEncodingFromByteOrderMarks: true, + bufferSize: 1024, + leaveOpen: true + ); + } - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) - { - _Stream?.Dispose(); - } - _Disposed = true; + _Stream?.Dispose(); } + _Disposed = true; } + } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } diff --git a/src/PSRule/Pipeline/Emitters/JsonEmitter.cs b/src/PSRule/Pipeline/Emitters/JsonEmitter.cs index 001914048f..4e86dc91ec 100644 --- a/src/PSRule/Pipeline/Emitters/JsonEmitter.cs +++ b/src/PSRule/Pipeline/Emitters/JsonEmitter.cs @@ -24,10 +24,9 @@ internal sealed class JsonEmitter : FileEmitter public JsonEmitter() { - _Settings = new JsonSerializerSettings { - + }; _Deserializer = JsonSerializer.CreateDefault(_Settings); // Think about caching this. _Deserializer.Converters.Add(new PSObjectArrayJsonConverter(null)); @@ -88,13 +87,13 @@ protected override bool VisitString(IEmitterContext context, string content) VisitItems(context, value, null); return true; } - catch (Exception ex) + catch (Exception) { throw; } finally { - + } } diff --git a/src/PSRule/Pipeline/Emitters/YamlEmitter.cs b/src/PSRule/Pipeline/Emitters/YamlEmitter.cs index 8188ce2284..d77cc9f737 100644 --- a/src/PSRule/Pipeline/Emitters/YamlEmitter.cs +++ b/src/PSRule/Pipeline/Emitters/YamlEmitter.cs @@ -99,7 +99,7 @@ protected override bool VisitString(IEmitterContext context, string content) } return true; } - catch (Exception ex) + catch (Exception) { throw; } diff --git a/src/PSRule/Pipeline/Formatters/AssertFormatter.cs b/src/PSRule/Pipeline/Formatters/AssertFormatterBase.cs similarity index 85% rename from src/PSRule/Pipeline/Formatters/AssertFormatter.cs rename to src/PSRule/Pipeline/Formatters/AssertFormatterBase.cs index 9f9188a2c3..1e567a82d1 100644 --- a/src/PSRule/Pipeline/Formatters/AssertFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/AssertFormatterBase.cs @@ -9,115 +9,6 @@ namespace PSRule.Pipeline.Formatters; -internal interface IAssertFormatter : IPipelineWriter -{ - void Result(InvokeResult result); - - void Error(ErrorRecord errorRecord); - - void Warning(WarningRecord warningRecord); - - void End(int total, int fail, int error); -} - -/// -/// Configures formatted output. -/// -internal sealed class TerminalSupport -{ - public TerminalSupport(int indent) - { - BodyIndent = new string(' ', indent); - MessageIdent = BodyIndent; - StartResultIndent = FormatterStrings.StartObjectPrefix; - SourceLocationPrefix = "| "; - SynopsisPrefix = FormatterStrings.SynopsisPrefix; - PassStatus = FormatterStrings.Result_Pass; - FailStatus = FormatterStrings.Result_Fail; - InformationStatus = FormatterStrings.Result_Information; - WarningStatus = FormatterStrings.Result_Warning; - ErrorStatus = FormatterStrings.Result_Error; - RecommendationHeading = FormatterStrings.Recommend; - RecommendationPrefix = "| "; - ReasonHeading = FormatterStrings.Reason; - ReasonItemPrefix = "| - "; - HelpHeading = FormatterStrings.Help; - HelpLinkPrefix = "| - "; - } - - public string BodyIndent { get; } - - public string MessageIdent { get; internal set; } - - public string StartResultIndent { get; internal set; } - - public ConsoleColor? StartResultForegroundColor { get; internal set; } - - public string SourceLocationPrefix { get; internal set; } - - public ConsoleColor? SourceLocationForegroundColor { get; internal set; } - - public string SynopsisPrefix { get; internal set; } - - public ConsoleColor? SynopsisForegroundColor { get; internal set; } - - public string PassStatus { get; internal set; } - - public ConsoleColor? PassForegroundColor { get; internal set; } - - public ConsoleColor? PassBackgroundColor { get; internal set; } - - public ConsoleColor? PassStatusBackgroundColor { get; internal set; } - - public ConsoleColor? PassStatusForegroundColor { get; internal set; } - - public string FailStatus { get; internal set; } - - public ConsoleColor? FailForegroundColor { get; internal set; } - - public ConsoleColor? FailBackgroundColor { get; internal set; } - - public ConsoleColor? FailStatusBackgroundColor { get; internal set; } - - public ConsoleColor? FailStatusForegroundColor { get; internal set; } - - public string InformationStatus { get; internal set; } - - public string WarningStatus { get; internal set; } - - public ConsoleColor? WarningForegroundColor { get; internal set; } - - public ConsoleColor? WarningBackgroundColor { get; internal set; } - - public ConsoleColor? WarningStatusBackgroundColor { get; internal set; } - - public ConsoleColor? WarningStatusForegroundColor { get; internal set; } - - public string ErrorStatus { get; internal set; } - - public ConsoleColor? ErrorForegroundColor { get; internal set; } - - public ConsoleColor? ErrorBackgroundColor { get; internal set; } - - public ConsoleColor? ErrorStatusBackgroundColor { get; internal set; } - - public ConsoleColor? ErrorStatusForegroundColor { get; internal set; } - - public ConsoleColor? BodyForegroundColor { get; internal set; } - - public string RecommendationHeading { get; internal set; } - - public string RecommendationPrefix { get; internal set; } - - public string ReasonHeading { get; internal set; } - - public string ReasonItemPrefix { get; internal set; } - - public string HelpHeading { get; internal set; } - - public string HelpLinkPrefix { get; internal set; } -} - /// /// A base class for a formatter. /// diff --git a/src/PSRule/Pipeline/Formatters/IAssertFormatter.cs b/src/PSRule/Pipeline/Formatters/IAssertFormatter.cs new file mode 100644 index 0000000000..c225670d17 --- /dev/null +++ b/src/PSRule/Pipeline/Formatters/IAssertFormatter.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; + +namespace PSRule.Pipeline.Formatters; + +internal interface IAssertFormatter : IPipelineWriter +{ + void Result(InvokeResult result); + + void Error(ErrorRecord errorRecord); + + void Warning(WarningRecord warningRecord); + + void End(int total, int fail, int error); +} diff --git a/src/PSRule/Pipeline/Formatters/TerminalSupport.cs b/src/PSRule/Pipeline/Formatters/TerminalSupport.cs new file mode 100644 index 0000000000..8556aef3b2 --- /dev/null +++ b/src/PSRule/Pipeline/Formatters/TerminalSupport.cs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Resources; + +namespace PSRule.Pipeline.Formatters; + +/// +/// Configures formatted output. +/// +internal sealed class TerminalSupport +{ + public TerminalSupport(int indent) + { + BodyIndent = new string(' ', indent); + MessageIdent = BodyIndent; + StartResultIndent = FormatterStrings.StartObjectPrefix; + SourceLocationPrefix = "| "; + SynopsisPrefix = FormatterStrings.SynopsisPrefix; + PassStatus = FormatterStrings.Result_Pass; + FailStatus = FormatterStrings.Result_Fail; + InformationStatus = FormatterStrings.Result_Information; + WarningStatus = FormatterStrings.Result_Warning; + ErrorStatus = FormatterStrings.Result_Error; + RecommendationHeading = FormatterStrings.Recommend; + RecommendationPrefix = "| "; + ReasonHeading = FormatterStrings.Reason; + ReasonItemPrefix = "| - "; + HelpHeading = FormatterStrings.Help; + HelpLinkPrefix = "| - "; + } + + public string BodyIndent { get; } + + public string MessageIdent { get; internal set; } + + public string StartResultIndent { get; internal set; } + + public ConsoleColor? StartResultForegroundColor { get; internal set; } + + public string SourceLocationPrefix { get; internal set; } + + public ConsoleColor? SourceLocationForegroundColor { get; internal set; } + + public string SynopsisPrefix { get; internal set; } + + public ConsoleColor? SynopsisForegroundColor { get; internal set; } + + public string PassStatus { get; internal set; } + + public ConsoleColor? PassForegroundColor { get; internal set; } + + public ConsoleColor? PassBackgroundColor { get; internal set; } + + public ConsoleColor? PassStatusBackgroundColor { get; internal set; } + + public ConsoleColor? PassStatusForegroundColor { get; internal set; } + + public string FailStatus { get; internal set; } + + public ConsoleColor? FailForegroundColor { get; internal set; } + + public ConsoleColor? FailBackgroundColor { get; internal set; } + + public ConsoleColor? FailStatusBackgroundColor { get; internal set; } + + public ConsoleColor? FailStatusForegroundColor { get; internal set; } + + public string InformationStatus { get; internal set; } + + public string WarningStatus { get; internal set; } + + public ConsoleColor? WarningForegroundColor { get; internal set; } + + public ConsoleColor? WarningBackgroundColor { get; internal set; } + + public ConsoleColor? WarningStatusBackgroundColor { get; internal set; } + + public ConsoleColor? WarningStatusForegroundColor { get; internal set; } + + public string ErrorStatus { get; internal set; } + + public ConsoleColor? ErrorForegroundColor { get; internal set; } + + public ConsoleColor? ErrorBackgroundColor { get; internal set; } + + public ConsoleColor? ErrorStatusBackgroundColor { get; internal set; } + + public ConsoleColor? ErrorStatusForegroundColor { get; internal set; } + + public ConsoleColor? BodyForegroundColor { get; internal set; } + + public string RecommendationHeading { get; internal set; } + + public string RecommendationPrefix { get; internal set; } + + public string ReasonHeading { get; internal set; } + + public string ReasonItemPrefix { get; internal set; } + + public string HelpHeading { get; internal set; } + + public string HelpLinkPrefix { get; internal set; } +} diff --git a/src/PSRule/Rules/IRuleHelpInfoV2.cs b/src/PSRule/Rules/IRuleHelpInfoV2.cs new file mode 100644 index 0000000000..e47c2065ac --- /dev/null +++ b/src/PSRule/Rules/IRuleHelpInfoV2.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using PSRule.Definitions; + +namespace PSRule.Rules; + +/// +/// A rule help information structure. +/// +public interface IRuleHelpInfoV2 : IResourceHelpInfo +{ + /// + /// The rule recommendation. + /// + InfoString Recommendation { get; } + + /// + /// Additional annotations, which are string key/ value pairs. + /// + Hashtable Annotations { get; } + + /// + /// The name of the module where the rule was loaded from. + /// + string ModuleName { get; } + + /// + /// Additional online links to reference information for the rule. + /// + Link[] Links { get; } +} diff --git a/src/PSRule/Rules/Link.cs b/src/PSRule/Rules/Link.cs new file mode 100644 index 0000000000..378fe2e502 --- /dev/null +++ b/src/PSRule/Rules/Link.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Rules; + +/// +/// An URL link to reference information. +/// +public sealed class Link +{ + internal Link(string name, string uri) + { + Name = name; + Uri = uri; + } + + /// + /// The display name of the link. + /// + public string Name { get; } + + /// + /// The URL to the information, or the target link. + /// + public string Uri { get; } +} diff --git a/src/PSRule/Rules/RuleHelpInfo.cs b/src/PSRule/Rules/RuleHelpInfo.cs index 67a2ba3997..6645f70034 100644 --- a/src/PSRule/Rules/RuleHelpInfo.cs +++ b/src/PSRule/Rules/RuleHelpInfo.cs @@ -9,108 +9,6 @@ namespace PSRule.Rules; -/// -/// A rule help information structure. -/// -public interface IRuleHelpInfoV2 : IResourceHelpInfo -{ - /// - /// The rule recommendation. - /// - InfoString Recommendation { get; } - - /// - /// Additional annotations, which are string key/ value pairs. - /// - Hashtable Annotations { get; } - - /// - /// The name of the module where the rule was loaded from. - /// - string ModuleName { get; } - - /// - /// Additional online links to reference information for the rule. - /// - Link[] Links { get; } -} - -/// -/// Extension methods for rule help information. -/// -public static class RuleHelpInfoExtensions -{ - private const string ONLINE_HELP_LINK_ANNOTATION = "online version"; - - /// - /// Get the URI for the online version of the documentation. - /// - /// Returns the URI when a valid link is set, otherwise null is returned. - public static Uri GetOnlineHelpUri(this IRuleHelpInfoV2 info) - { - var link = GetOnlineHelpUrl(info); - return link == null || - !Uri.TryCreate(link, UriKind.Absolute, out var result) ? - null : result; - } - - /// - /// Get the URL for the online version of the documentation. - /// - /// Returns the URL when set, otherwise null is returned. - public static string GetOnlineHelpUrl(this IRuleHelpInfoV2 info) - { - return info == null || - info.Annotations == null || - !info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION) ? - null : info.Annotations[ONLINE_HELP_LINK_ANNOTATION].ToString(); - } - - /// - /// Determines if the online help link is set. - /// - /// Returns true when the online help link is set. Otherwise this method returns false. - internal static bool HasOnlineHelp(this IRuleHelpInfoV2 info) - { - return info != null && - info.Annotations != null && - info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION); - } - - /// - /// Set the online help link from the parameter. - /// - /// The info object. - /// A URL to the online help location. - internal static void SetOnlineHelpUrl(this IRuleHelpInfoV2 info, string url) - { - if (info == null || info.Annotations == null || string.IsNullOrEmpty(url)) return; - info.Annotations[ONLINE_HELP_LINK_ANNOTATION] = url; - } -} - -/// -/// An URL link to reference information. -/// -public sealed class Link -{ - internal Link(string name, string uri) - { - Name = name; - Uri = uri; - } - - /// - /// The display name of the link. - /// - public string Name { get; } - - /// - /// The URL to the information, or the target link. - /// - public string Uri { get; } -} - /// /// Output view helper class for rule help. /// diff --git a/src/PSRule/Rules/RuleHelpInfoExtensions.cs b/src/PSRule/Rules/RuleHelpInfoExtensions.cs new file mode 100644 index 0000000000..927d2adea1 --- /dev/null +++ b/src/PSRule/Rules/RuleHelpInfoExtensions.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Rules; + +/// +/// Extension methods for rule help information. +/// +public static class RuleHelpInfoExtensions +{ + private const string ONLINE_HELP_LINK_ANNOTATION = "online version"; + + /// + /// Get the URI for the online version of the documentation. + /// + /// Returns the URI when a valid link is set, otherwise null is returned. + public static Uri GetOnlineHelpUri(this IRuleHelpInfoV2 info) + { + var link = GetOnlineHelpUrl(info); + return link == null || + !Uri.TryCreate(link, UriKind.Absolute, out var result) ? + null : result; + } + + /// + /// Get the URL for the online version of the documentation. + /// + /// Returns the URL when set, otherwise null is returned. + public static string GetOnlineHelpUrl(this IRuleHelpInfoV2 info) + { + return info == null || + info.Annotations == null || + !info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION) ? + null : info.Annotations[ONLINE_HELP_LINK_ANNOTATION].ToString(); + } + + /// + /// Determines if the online help link is set. + /// + /// Returns true when the online help link is set. Otherwise this method returns false. + internal static bool HasOnlineHelp(this IRuleHelpInfoV2 info) + { + return info != null && + info.Annotations != null && + info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION); + } + + /// + /// Set the online help link from the parameter. + /// + /// The info object. + /// A URL to the online help location. + internal static void SetOnlineHelpUrl(this IRuleHelpInfoV2 info, string url) + { + if (info == null || info.Annotations == null || string.IsNullOrEmpty(url)) return; + info.Annotations[ONLINE_HELP_LINK_ANNOTATION] = url; + } +} diff --git a/src/PSRule/Runtime/ILanguageScope.cs b/src/PSRule/Runtime/ILanguageScope.cs new file mode 100644 index 0000000000..fed742553c --- /dev/null +++ b/src/PSRule/Runtime/ILanguageScope.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Configuration; +using PSRule.Definitions; +using PSRule.Pipeline; + +namespace PSRule.Runtime; + +/// +/// A named scope for language elements. +/// +internal interface ILanguageScope : IDisposable +{ + /// + /// The name of the scope. + /// + string Name { get; } + + BindingOption Binding { get; } + + /// + /// Get an ordered culture preference list which will be tries for finding help. + /// + string[] Culture { get; } + + void Configure(OptionContext context); + + /// + /// Try to get a specific configuration value by name. + /// + bool TryConfigurationValue(string key, out object value); + + void WithFilter(IResourceFilter resourceFilter); + + /// + /// Get a filter for a specific resource kind. + /// + IResourceFilter GetFilter(ResourceKind kind); + + /// + /// Add a service to the scope. + /// + void AddService(string name, object service); + + /// + /// Get a previously added service. + /// + object GetService(string name); + + bool TryGetType(object o, out string type, out string path); + + bool TryGetName(object o, out string name, out string path); + + bool TryGetScope(object o, out string[] scope); +} diff --git a/src/PSRule/Runtime/ILogger.cs b/src/PSRule/Runtime/ILogger.cs index 833f42f583..e798490126 100644 --- a/src/PSRule/Runtime/ILogger.cs +++ b/src/PSRule/Runtime/ILogger.cs @@ -3,25 +3,6 @@ namespace PSRule.Runtime; -/// -/// A set of log levels which indicate different types of diagnostic messages. -/// -[Flags] -internal enum LogLevel -{ - None = 0, - - Error = 1, - - Warning = 2, - - Info = 4, - - Verbose = 8, - - Debug = 16, -} - /// /// A generic interface for diagnostic logging within PSRule. /// @@ -45,6 +26,6 @@ internal interface ILogger /// Write an error from an exception. /// /// The exception to write. - /// A string identififer for the error. + /// A string identifier for the error. void Error(Exception exception, string errorId = null); } diff --git a/src/PSRule/Runtime/IOperand.cs b/src/PSRule/Runtime/IOperand.cs new file mode 100644 index 0000000000..55bb08897f --- /dev/null +++ b/src/PSRule/Runtime/IOperand.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime; + +/// +/// An operand that is compared with PSRule expressions. +/// +public interface IOperand +{ + /// + /// The value of the operand. + /// + object Value { get; } + + /// + /// The type of operand. + /// + OperandKind Kind { get; } + + /// + /// The object path to the operand. + /// + string Path { get; } + + /// + /// A logical prefix to add to the object path. + /// + string Prefix { get; set; } +} diff --git a/src/PSRule/Runtime/LanguageScope.cs b/src/PSRule/Runtime/LanguageScope.cs index bfe59bc1d8..23fe412978 100644 --- a/src/PSRule/Runtime/LanguageScope.cs +++ b/src/PSRule/Runtime/LanguageScope.cs @@ -8,54 +8,6 @@ namespace PSRule.Runtime; -/// -/// A named scope for language elements. -/// -internal interface ILanguageScope : IDisposable -{ - /// - /// The name of the scope. - /// - string Name { get; } - - BindingOption Binding { get; } - - /// - /// Get an ordered culture preference list which will be tries for finding help. - /// - string[] Culture { get; } - - void Configure(OptionContext context); - - /// - /// Try to get a specific configuration value by name. - /// - bool TryConfigurationValue(string key, out object value); - - void WithFilter(IResourceFilter resourceFilter); - - /// - /// Get a filter for a specific resource kind. - /// - IResourceFilter GetFilter(ResourceKind kind); - - /// - /// Add a service to the scope. - /// - void AddService(string name, object service); - - /// - /// Get a previously added service. - /// - object GetService(string name); - - bool TryGetType(object o, out string type, out string path); - - bool TryGetName(object o, out string name, out string path); - - bool TryGetScope(object o, out string[] scope); -} - [DebuggerDisplay("{Name}")] internal sealed class LanguageScope : ILanguageScope { @@ -225,102 +177,3 @@ public void Dispose() GC.SuppressFinalize(this); } } - -/// -/// A collection of . -/// -internal sealed class LanguageScopeSet : IDisposable -{ - private readonly RunspaceContext _Context; - private readonly Dictionary _Scopes; - - private ILanguageScope _Current; - private bool _Disposed; - - public LanguageScopeSet(RunspaceContext context) - { - _Context = context; - _Scopes = new Dictionary(StringComparer.OrdinalIgnoreCase); - Import(null, out _Current); - } - - public ILanguageScope Current - { - get - { - return _Current; - } - } - - #region IDisposable - - private void Dispose(bool disposing) - { - if (!_Disposed) - { - if (disposing) - { - // Release and dispose scopes - if (_Scopes != null && _Scopes.Count > 0) - { - foreach (var kv in _Scopes) - kv.Value.Dispose(); - - _Scopes.Clear(); - } - } - _Disposed = true; - } - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - #endregion IDisposable - - internal void Add(ILanguageScope languageScope) - { - _Scopes.Add(languageScope.Name, languageScope); - } - - internal IEnumerable Get() - { - return _Scopes.Values; - } - - /// - /// Switch to a specific language scope by name. - /// - /// The name of the language scope to switch to. - internal void UseScope(string name) - { - if (!_Scopes.TryGetValue(GetScopeName(name), out var scope)) - throw new Exception($"The specified scope '{name}' was not found."); - - _Current = scope; - } - - internal bool TryScope(string name, out ILanguageScope scope) - { - return _Scopes.TryGetValue(GetScopeName(name), out scope); - } - - internal bool Import(string name, out ILanguageScope scope) - { - if (_Scopes.TryGetValue(GetScopeName(name), out scope)) - return false; - - scope = new LanguageScope(_Context, name); - Add(scope); - return true; - } - - private static string GetScopeName(string name) - { - return LanguageScope.Normalize(name); - } -} diff --git a/src/PSRule/Runtime/LanguageScopeSet.cs b/src/PSRule/Runtime/LanguageScopeSet.cs new file mode 100644 index 0000000000..1151764675 --- /dev/null +++ b/src/PSRule/Runtime/LanguageScopeSet.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime; + +/// +/// A collection of . +/// +internal sealed class LanguageScopeSet : IDisposable +{ + private readonly RunspaceContext _Context; + private readonly Dictionary _Scopes; + + private ILanguageScope _Current; + private bool _Disposed; + + public LanguageScopeSet(RunspaceContext context) + { + _Context = context; + _Scopes = new Dictionary(StringComparer.OrdinalIgnoreCase); + Import(null, out _Current); + } + + public ILanguageScope Current + { + get + { + return _Current; + } + } + + #region IDisposable + + private void Dispose(bool disposing) + { + if (!_Disposed) + { + if (disposing) + { + // Release and dispose scopes + if (_Scopes != null && _Scopes.Count > 0) + { + foreach (var kv in _Scopes) + kv.Value.Dispose(); + + _Scopes.Clear(); + } + } + _Disposed = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + #endregion IDisposable + + internal void Add(ILanguageScope languageScope) + { + _Scopes.Add(languageScope.Name, languageScope); + } + + internal IEnumerable Get() + { + return _Scopes.Values; + } + + /// + /// Switch to a specific language scope by name. + /// + /// The name of the language scope to switch to. + internal void UseScope(string name) + { + if (!_Scopes.TryGetValue(GetScopeName(name), out var scope)) + throw new Exception($"The specified scope '{name}' was not found."); + + _Current = scope; + } + + internal bool TryScope(string name, out ILanguageScope scope) + { + return _Scopes.TryGetValue(GetScopeName(name), out scope); + } + + internal bool Import(string name, out ILanguageScope scope) + { + if (_Scopes.TryGetValue(GetScopeName(name), out scope)) + return false; + + scope = new LanguageScope(_Context, name); + Add(scope); + return true; + } + + private static string GetScopeName(string name) + { + return LanguageScope.Normalize(name); + } +} diff --git a/src/PSRule/Runtime/LogLevel.cs b/src/PSRule/Runtime/LogLevel.cs new file mode 100644 index 0000000000..2dcb10ba5b --- /dev/null +++ b/src/PSRule/Runtime/LogLevel.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime; + +/// +/// A set of log levels which indicate different types of diagnostic messages. +/// +[Flags] +internal enum LogLevel +{ + None = 0, + + Error = 1, + + Warning = 2, + + Info = 4, + + Verbose = 8, + + Debug = 16, +} diff --git a/src/PSRule/Runtime/NameToken.cs b/src/PSRule/Runtime/NameToken.cs index 8885ea3c38..2e06cc32c0 100644 --- a/src/PSRule/Runtime/NameToken.cs +++ b/src/PSRule/Runtime/NameToken.cs @@ -5,27 +5,6 @@ namespace PSRule.Runtime; -/// -/// The type of NameToken. -/// -internal enum NameTokenType -{ - /// - /// The token represents a field/ property of an object. - /// - Field = 0, - - /// - /// The token is an index in an object. - /// - Index = 1, - - /// - /// The token is a reference to the parent object. Can only be the first token. - /// - Self = 2 -} - /// /// A token for expressing a path through a tree of fields. /// diff --git a/src/PSRule/Runtime/NameTokenType.cs b/src/PSRule/Runtime/NameTokenType.cs new file mode 100644 index 0000000000..f17d492808 --- /dev/null +++ b/src/PSRule/Runtime/NameTokenType.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime; + +/// +/// The type of NameToken. +/// +internal enum NameTokenType +{ + /// + /// The token represents a field/ property of an object. + /// + Field = 0, + + /// + /// The token is an index in an object. + /// + Index = 1, + + /// + /// The token is a reference to the parent object. Can only be the first token. + /// + Self = 2 +} diff --git a/src/PSRule/Runtime/ObjectPath/FilterOperator.cs b/src/PSRule/Runtime/ObjectPath/FilterOperator.cs new file mode 100644 index 0000000000..7eb103d969 --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/FilterOperator.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime.ObjectPath; + +internal enum FilterOperator +{ + None = 0, + + // Comparison + Equal, + NotEqual, + LessOrEqual, + Less, + GreaterOrEqual, + Greater, + RegEx, + + // Logical + Or, + And, +} diff --git a/src/PSRule/Runtime/ObjectPath/IPathToken.cs b/src/PSRule/Runtime/ObjectPath/IPathToken.cs new file mode 100644 index 0000000000..a2dd3457cb --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/IPathToken.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime.ObjectPath; + +internal interface IPathToken +{ + PathTokenType Type { get; } + + PathTokenOption Option { get; } + + object Arg { get; } + + T As(); +} diff --git a/src/PSRule/Runtime/ObjectPath/ITokenReader.cs b/src/PSRule/Runtime/ObjectPath/ITokenReader.cs new file mode 100644 index 0000000000..d388a35720 --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/ITokenReader.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime.ObjectPath; + +internal interface ITokenReader +{ + IPathToken Current { get; } + + bool Next(out IPathToken token); + + bool Consume(PathTokenType type); + + bool Peak(out IPathToken token); +} diff --git a/src/PSRule/Runtime/ObjectPath/ITokenWriter.cs b/src/PSRule/Runtime/ObjectPath/ITokenWriter.cs new file mode 100644 index 0000000000..a2c2a99d18 --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/ITokenWriter.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime.ObjectPath; + +internal interface ITokenWriter +{ + IPathToken Last { get; } + + void Add(IPathToken token); +} diff --git a/src/PSRule/Runtime/ObjectPath/PathToken.cs b/src/PSRule/Runtime/ObjectPath/PathToken.cs new file mode 100644 index 0000000000..21314fd371 --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/PathToken.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; + +namespace PSRule.Runtime.ObjectPath; + +[DebuggerDisplay("Type = {Type}, Arg = {Arg}")] +internal sealed class PathToken : IPathToken +{ + public static readonly PathToken RootRef = new(PathTokenType.RootRef); + public static readonly PathToken CurrentRef = new(PathTokenType.CurrentRef); + + public PathTokenType Type { get; } + + public PathTokenOption Option { get; } + + public PathToken(PathTokenType type) + { + Type = type; + } + + public PathToken(PathTokenType type, object arg, PathTokenOption option = PathTokenOption.None) + { + Type = type; + Arg = arg; + Option = option; + } + + public object Arg { get; } + + public T As() + { + return Arg is T result ? result : default; + } +} diff --git a/src/PSRule/Runtime/ObjectPath/PathTokenOption.cs b/src/PSRule/Runtime/ObjectPath/PathTokenOption.cs new file mode 100644 index 0000000000..8cdfc7f955 --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/PathTokenOption.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime.ObjectPath; + +internal enum PathTokenOption +{ + None = 0, + + CaseSensitive +} diff --git a/src/PSRule/Runtime/ObjectPath/PathTokenType.cs b/src/PSRule/Runtime/ObjectPath/PathTokenType.cs new file mode 100644 index 0000000000..928065e59c --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/PathTokenType.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime.ObjectPath; + +internal enum PathTokenType +{ + None = 0, + + /// + /// Token: $ + /// + RootRef, + + /// + /// Token: @ + /// + CurrentRef, + + /// + /// Token: .Name + /// + DotSelector, + + /// + /// Token: [index] + /// + IndexSelector, + + /// + /// Token: [*] + /// + IndexWildSelector, + + StartFilter, + ComparisonOperator, + Boolean, + EndFilter, + String, + Integer, + LogicalOperator, + + StartGroup, + EndGroup, + + /// + /// Token: ! + /// + NotOperator, + + /// + /// Token: .. + /// + DescendantSelector, + + /// + /// Token: .* + /// + DotWildSelector, + + ArraySliceSelector, + UnionIndexSelector, + UnionQuotedMemberSelector, +} diff --git a/src/PSRule/Runtime/ObjectPath/TokenReader.cs b/src/PSRule/Runtime/ObjectPath/TokenReader.cs new file mode 100644 index 0000000000..66735bbee0 --- /dev/null +++ b/src/PSRule/Runtime/ObjectPath/TokenReader.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime.ObjectPath; + +internal sealed class TokenReader : ITokenReader +{ + private readonly IPathToken[] _Tokens; + private readonly int _Last; + + private int _Index; + + public TokenReader(IPathToken[] tokens) + { + _Tokens = tokens; + _Last = tokens.Length - 1; + _Index = -1; + } + + public IPathToken Current { get; private set; } + + public bool Consume(PathTokenType type) + { + return Peak(out var token) && token.Type == type && Next(); + } + + public bool Next(out IPathToken token) + { + token = null; + if (!Next()) + return false; + + token = Current; + return true; + } + + private bool Next() + { + Current = _Index < _Last ? _Tokens[++_Index] : null; + return Current != null; + } + + public bool Peak(out IPathToken token) + { + token = _Index < _Last ? _Tokens[_Index + 1] : null; + return token != null; + } +} diff --git a/src/PSRule/Runtime/ObjectPath/Tokens.cs b/src/PSRule/Runtime/ObjectPath/Tokens.cs deleted file mode 100644 index bda73d360d..0000000000 --- a/src/PSRule/Runtime/ObjectPath/Tokens.cs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Diagnostics; - -namespace PSRule.Runtime.ObjectPath; - -internal enum PathTokenType -{ - None = 0, - - /// - /// Token: $ - /// - RootRef, - - /// - /// Token: @ - /// - CurrentRef, - - /// - /// Token: .Name - /// - DotSelector, - - /// - /// Token: [index] - /// - IndexSelector, - - /// - /// Token: [*] - /// - IndexWildSelector, - - StartFilter, - ComparisonOperator, - Boolean, - EndFilter, - String, - Integer, - LogicalOperator, - - StartGroup, - EndGroup, - - /// - /// Token: ! - /// - NotOperator, - - /// - /// Token: .. - /// - DescendantSelector, - - /// - /// Token: .* - /// - DotWildSelector, - - ArraySliceSelector, - UnionIndexSelector, - UnionQuotedMemberSelector, -} - -internal enum PathTokenOption -{ - None = 0, - - CaseSensitive -} - -internal enum FilterOperator -{ - None = 0, - - // Comparison - Equal, - NotEqual, - LessOrEqual, - Less, - GreaterOrEqual, - Greater, - RegEx, - - // Logical - Or, - And, -} - -internal interface IPathToken -{ - PathTokenType Type { get; } - - PathTokenOption Option { get; } - - object Arg { get; } - - T As(); -} - -[DebuggerDisplay("Type = {Type}, Arg = {Arg}")] -internal sealed class PathToken : IPathToken -{ - public static readonly PathToken RootRef = new(PathTokenType.RootRef); - public static readonly PathToken CurrentRef = new(PathTokenType.CurrentRef); - - public PathTokenType Type { get; } - - public PathTokenOption Option { get; } - - public PathToken(PathTokenType type) - { - Type = type; - } - - public PathToken(PathTokenType type, object arg, PathTokenOption option = PathTokenOption.None) - { - Type = type; - Arg = arg; - Option = option; - } - - public object Arg { get; } - - public T As() - { - return Arg is T result ? result : default; - } -} - -internal interface ITokenWriter -{ - IPathToken Last { get; } - - void Add(IPathToken token); -} - -internal interface ITokenReader -{ - IPathToken Current { get; } - - bool Next(out IPathToken token); - - bool Consume(PathTokenType type); - - bool Peak(out IPathToken token); -} - -internal sealed class TokenReader : ITokenReader -{ - private readonly IPathToken[] _Tokens; - private readonly int _Last; - - private int _Index; - - public TokenReader(IPathToken[] tokens) - { - _Tokens = tokens; - _Last = tokens.Length - 1; - _Index = -1; - } - - public IPathToken Current { get; private set; } - - public bool Consume(PathTokenType type) - { - return Peak(out var token) && token.Type == type && Next(); - } - - public bool Next(out IPathToken token) - { - token = null; - if (!Next()) - return false; - - token = Current; - return true; - } - - private bool Next() - { - Current = _Index < _Last ? _Tokens[++_Index] : null; - return Current != null; - } - - public bool Peak(out IPathToken token) - { - token = _Index < _Last ? _Tokens[_Index + 1] : null; - return token != null; - } -} diff --git a/src/PSRule/Runtime/Operand.cs b/src/PSRule/Runtime/Operand.cs index fa51e2c49a..7dcece7f90 100644 --- a/src/PSRule/Runtime/Operand.cs +++ b/src/PSRule/Runtime/Operand.cs @@ -3,78 +3,6 @@ namespace PSRule.Runtime; -/// -/// The type of operand that is compared with the expression. -/// -public enum OperandKind -{ - /// - /// Unknown. - /// - None = 0, - - /// - /// An object path. - /// - Path = 1, - - /// - /// The object target type. - /// - Type = 2, - - /// - /// The object target name. - /// - Name = 3, - - /// - /// The object source information. - /// - Source = 4, - - /// - /// The target object itself. - /// - Target = 5, - - /// - /// A literal value or function. - /// - Value = 6, - - /// - /// The object scope. - /// - Scope = 7 -} - -/// -/// An operand that is compared with PSRule expressions. -/// -public interface IOperand -{ - /// - /// The value of the operand. - /// - object Value { get; } - - /// - /// The type of operand. - /// - OperandKind Kind { get; } - - /// - /// The object path to the operand. - /// - string Path { get; } - - /// - /// A logical prefix to add to the object path. - /// - string Prefix { get; set; } -} - internal sealed class Operand : IOperand { private const string Dot = "."; diff --git a/src/PSRule/Runtime/OperandKind.cs b/src/PSRule/Runtime/OperandKind.cs new file mode 100644 index 0000000000..369a586daa --- /dev/null +++ b/src/PSRule/Runtime/OperandKind.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime; + +/// +/// The type of operand that is compared with the expression. +/// +public enum OperandKind +{ + /// + /// Unknown. + /// + None = 0, + + /// + /// An object path. + /// + Path = 1, + + /// + /// The object target type. + /// + Type = 2, + + /// + /// The object target name. + /// + Name = 3, + + /// + /// The object source information. + /// + Source = 4, + + /// + /// The target object itself. + /// + Target = 5, + + /// + /// A literal value or function. + /// + Value = 6, + + /// + /// The object scope. + /// + Scope = 7 +} diff --git a/src/PSRule/Runtime/PSRuleMemberInfo.cs b/src/PSRule/Runtime/PSRuleTargetInfo.cs similarity index 100% rename from src/PSRule/Runtime/PSRuleMemberInfo.cs rename to src/PSRule/Runtime/PSRuleTargetInfo.cs diff --git a/src/PSRule/Runtime/RuleConditionHelper.cs b/src/PSRule/Runtime/RuleConditionHelper.cs new file mode 100644 index 0000000000..949b5c290c --- /dev/null +++ b/src/PSRule/Runtime/RuleConditionHelper.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime; + +internal static class RuleConditionHelper +{ + private static readonly RuleConditionResult Empty = new(pass: 0, count: 0, hadErrors: false); + + internal static RuleConditionResult Create(IEnumerable value) + { + if (value == null) + return Empty; + + var count = 0; + var pass = 0; + var hasErrors = false; + foreach (var v in value) + { + count++; + if (v == null) + continue; + + var baseObject = ExpressionHelpers.GetBaseObject(v); + if (!(TryAssertResult(baseObject, out var result) || TryBoolean(baseObject, out result))) + { + RunspaceContext.CurrentThread.ErrorInvaildRuleResult(); + hasErrors = true; + } + else if (result) + { + pass++; + } + } + return new RuleConditionResult(pass, count, hasErrors); + } + + private static bool TryBoolean(object o, out bool result) + { + result = false; + if (o is not bool bresult) + return false; + + result = bresult; + return true; + } + + private static bool TryAssertResult(object o, out bool result) + { + result = false; + if (o is not AssertResult assert) + return false; + + result = assert.Result; + + // Complete results + if (RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) + assert.Complete(); + + return true; + } +} diff --git a/src/PSRule/Runtime/RuleConditionResult.cs b/src/PSRule/Runtime/RuleConditionResult.cs index 5e401c9a52..c325ad4c07 100644 --- a/src/PSRule/Runtime/RuleConditionResult.cs +++ b/src/PSRule/Runtime/RuleConditionResult.cs @@ -5,64 +5,6 @@ namespace PSRule.Runtime; -internal static class RuleConditionHelper -{ - private static readonly RuleConditionResult Empty = new(pass: 0, count: 0, hadErrors: false); - - internal static RuleConditionResult Create(IEnumerable value) - { - if (value == null) - return Empty; - - var count = 0; - var pass = 0; - var hasErrors = false; - foreach (var v in value) - { - count++; - if (v == null) - continue; - - var baseObject = ExpressionHelpers.GetBaseObject(v); - if (!(TryAssertResult(baseObject, out var result) || TryBoolean(baseObject, out result))) - { - RunspaceContext.CurrentThread.ErrorInvaildRuleResult(); - hasErrors = true; - } - else if (result) - { - pass++; - } - } - return new RuleConditionResult(pass, count, hasErrors); - } - - private static bool TryBoolean(object o, out bool result) - { - result = false; - if (o is not bool bresult) - return false; - - result = bresult; - return true; - } - - private static bool TryAssertResult(object o, out bool result) - { - result = false; - if (o is not AssertResult assert) - return false; - - result = assert.Result; - - // Complete results - if (RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) - assert.Complete(); - - return true; - } -} - internal sealed class RuleConditionResult : IConditionResult { internal RuleConditionResult(int pass, int count, bool hadErrors) diff --git a/src/PSRule/Runtime/RunspaceContext.cs b/src/PSRule/Runtime/RunspaceContext.cs index 2bf9f5dbf6..bcb84cf033 100644 --- a/src/PSRule/Runtime/RunspaceContext.cs +++ b/src/PSRule/Runtime/RunspaceContext.cs @@ -14,42 +14,6 @@ namespace PSRule.Runtime; -/// -/// The available language scope types. -/// -[Flags] -public enum RunspaceScope -{ - None = 0, - - Source = 1, - - /// - /// Executing a rule. - /// - Rule = 2, - - /// - /// Executing a rule precondition. - /// - Precondition = 4, - - /// - /// Execution is currently parsing YAML objects. - /// - Resource = 8, - - ConventionBegin = 16, - ConventionProcess = 32, - ConventionEnd = 64, - ConventionInitialize = 128, - - Convention = ConventionInitialize | ConventionBegin | ConventionProcess | ConventionEnd, - Target = Rule | Precondition | ConventionBegin | ConventionProcess, - Runtime = Rule | Precondition | Convention, - All = Source | Rule | Precondition | Resource | Convention, -} - /// /// A context for a PSRule runspace. /// diff --git a/src/PSRule/Runtime/RunspaceScope.cs b/src/PSRule/Runtime/RunspaceScope.cs new file mode 100644 index 0000000000..6764bc08da --- /dev/null +++ b/src/PSRule/Runtime/RunspaceScope.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Runtime; + +/// +/// The available language scope types. +/// +[Flags] +public enum RunspaceScope +{ + None = 0, + + Source = 1, + + /// + /// Executing a rule. + /// + Rule = 2, + + /// + /// Executing a rule precondition. + /// + Precondition = 4, + + /// + /// Execution is currently parsing YAML objects. + /// + Resource = 8, + + ConventionBegin = 16, + ConventionProcess = 32, + ConventionEnd = 64, + ConventionInitialize = 128, + + Convention = ConventionInitialize | ConventionBegin | ConventionProcess | ConventionEnd, + Target = Rule | Precondition | ConventionBegin | ConventionProcess, + Runtime = Rule | Precondition | Convention, + All = Source | Rule | Precondition | Resource | Convention, +} diff --git a/tests/PSRule.Tests/RuleFilterTests.cs b/tests/PSRule.Tests/RuleFilterTests.cs index 1f0b8769d3..6f64c917dc 100644 --- a/tests/PSRule.Tests/RuleFilterTests.cs +++ b/tests/PSRule.Tests/RuleFilterTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Collections; using PSRule.Definitions; using PSRule.Definitions.Rules; @@ -10,24 +11,24 @@ namespace PSRule; /// /// Define tests to validate . /// -public sealed class RuleFilterTests +public sealed partial class RuleFilterTests { [Fact] public void MatchInclude() { - var filter = new RuleFilter(new string[] { "rule1", "rule2" }, null, null, null, null); - Assert.True(filter.Match("rule1", null, null)); - Assert.True(filter.Match("Rule2", null, null)); - Assert.False(filter.Match("rule3", null, null)); + var filter = new RuleFilter(["rule1", "rule2"], null, null, null, null); + Assert.True(filter.Match(GetResource("rule1"))); + Assert.True(filter.Match(GetResource("Rule2"))); + Assert.False(filter.Match(GetResource("rule3"))); } [Fact] public void MatchExclude() { - var filter = new RuleFilter(null, null, new string[] { "rule3" }, null, null); - Assert.True(filter.Match("rule1", null, null)); - Assert.True(filter.Match("rule2", null, null)); - Assert.False(filter.Match("Rule3", null, null)); + var filter = new RuleFilter(null, null, ["rule3"], null, null); + Assert.True(filter.Match(GetResource("rule1"))); + Assert.True(filter.Match(GetResource("rule2"))); + Assert.False(filter.Match(GetResource("Rule3"))); } [Fact] @@ -45,12 +46,23 @@ public void MatchTag() // Check basic match resourceTags["category"] = "group2"; - Assert.True(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); + Assert.True(filter.Match(GetResource("rule", ResourceTags.FromHashtable(resourceTags)))); resourceTags["category"] = "group1"; - Assert.True(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); + Assert.True(filter.Match(GetResource("rule", ResourceTags.FromHashtable(resourceTags)))); resourceTags["category"] = "group3"; - Assert.False(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); - Assert.False(filter.Match("rule", null, null)); + Assert.False(filter.Match(GetResource("rule", ResourceTags.FromHashtable(resourceTags)))); + Assert.False(filter.Match(GetResource("rule"))); + + // Include local + filter = new RuleFilter(null, tag, null, true, null); + resourceTags["category"] = "group1"; + Assert.True(filter.Match(GetResource("module1\\rule", ResourceTags.FromHashtable(resourceTags)))); + resourceTags["category"] = "group3"; + Assert.False(filter.Match(GetResource("module1\\rule", ResourceTags.FromHashtable(resourceTags)))); + resourceTags["category"] = "group3"; + Assert.True(filter.Match(GetResource(".\\rule", ResourceTags.FromHashtable(resourceTags)))); + Assert.False(filter.Match(GetResource("module1\\rule"))); + Assert.True(filter.Match(GetResource(".\\rule"))); } [Fact] @@ -62,19 +74,28 @@ public void MatchLabels() // Create a filter var labels = new ResourceLabels { - ["framework.v1/control"] = new string[] { "c-1", "c-2" } + ["framework.v1/control"] = ["c-1", "c-2"] }; var filter = new RuleFilter(null, null, null, null, labels); resourceLabels["framework.v1/control"] = new string[] { "c-2", "c-1" }; - Assert.True(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); + Assert.True(filter.Match(GetResource("rule", null, ResourceLabels.FromHashtable(resourceLabels)))); resourceLabels["framework.v1/control"] = new string[] { "c-3", "c-1" }; - Assert.True(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); + Assert.True(filter.Match(GetResource("rule", null, ResourceLabels.FromHashtable(resourceLabels)))); resourceLabels["framework.v1/control"] = new string[] { "c-1", "c-3" }; - Assert.True(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); + Assert.True(filter.Match(GetResource("rule", null, ResourceLabels.FromHashtable(resourceLabels)))); resourceLabels["framework.v1/control"] = new string[] { "c-3", "c-4" }; - Assert.False(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); - resourceLabels["framework.v1/control"] = System.Array.Empty(); - Assert.False(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); + Assert.False(filter.Match(GetResource("rule", null, ResourceLabels.FromHashtable(resourceLabels)))); + resourceLabels["framework.v1/control"] = Array.Empty(); + Assert.False(filter.Match(GetResource("rule", null, ResourceLabels.FromHashtable(resourceLabels)))); } + + #region Helper Methods + + private static IResource GetResource(string id, ResourceTags resourceTags = null, ResourceLabels resourceLabels = null) + { + return new TestResourceName(ResourceId.Parse(id), resourceTags, resourceLabels); + } + + #endregion Helper Methods } diff --git a/tests/PSRule.Tests/TestResourceName.cs b/tests/PSRule.Tests/TestResourceName.cs new file mode 100644 index 0000000000..c415477916 --- /dev/null +++ b/tests/PSRule.Tests/TestResourceName.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using PSRule.Definitions; +using PSRule.Pipeline; + +namespace PSRule; + +internal sealed class TestResourceName : IResource +{ + public TestResourceName(ResourceId id, ResourceTags resourceTags = null, ResourceLabels resourceLabels = null) + { + Id = id; + Name = Id.Name; + Module = Id.Scope != null && Id.Scope != "." ? Id.Scope : null; + Tags = resourceTags ?? new ResourceTags(); + Labels = resourceLabels ?? new ResourceLabels(); + } + + public ResourceKind Kind => throw new System.NotImplementedException(); + + public string ApiVersion => throw new System.NotImplementedException(); + + public string Name { get; } + + public ResourceId? Ref => null; + + public ResourceId[] Alias => Array.Empty(); + + public ResourceTags Tags { get; } + + public ResourceLabels Labels { get; } + + public ResourceFlags Flags => ResourceFlags.None; + + public ISourceExtent Extent => throw new System.NotImplementedException(); + + public IResourceHelpInfo Info => throw new System.NotImplementedException(); + + public ResourceId Id { get; } + + public string SourcePath => throw new System.NotImplementedException(); + + public string Module { get; } + + public SourceFile Source => throw new System.NotImplementedException(); +} diff --git a/tests/PSRule.Tests/Usings.cs b/tests/PSRule.Tests/Usings.cs index 85dfd5f970..90be44137f 100644 --- a/tests/PSRule.Tests/Usings.cs +++ b/tests/PSRule.Tests/Usings.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +global using Moq; global using Xunit; global using Assert = Xunit.Assert; -global using Moq;