This commit is contained in:
Bernie White 2024-06-08 17:41:57 +10:00 коммит произвёл GitHub
Родитель 3b6be55376
Коммит cdc08bb115
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
86 изменённых файлов: 2359 добавлений и 2033 удалений

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

@ -3,7 +3,6 @@
using System.CommandLine.Invocation; using System.CommandLine.Invocation;
using PSRule.Configuration; using PSRule.Configuration;
using PSRule.Options;
namespace PSRule.CommandLine; namespace PSRule.CommandLine;

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

@ -1,12 +1,11 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
namespace PSRule.Emitters namespace PSRule.Emitters;
internal class InternalFileStream
{ {
internal class InternalFileStream public InternalFileStream()
{ {
public InternalFileStream()
{
}
} }
} }

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

@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Badges;
/// <summary>
/// An instance of a badge created by the Badge API.
/// </summary>
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;
}
/// <inheritdoc/>
public override string ToString()
{
return ToSvg();
}
/// <inheritdoc/>
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();
}
/// <inheritdoc/>
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());
}
}

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

@ -7,136 +7,6 @@ using PSRule.Rules;
namespace PSRule.Badges; namespace PSRule.Badges;
/// <summary>
/// The type of badge.
/// </summary>
public enum BadgeType
{
/// <summary>
/// A badge that reports an unknown state.
/// </summary>
Unknown = 0,
/// <summary>
/// A badge reporting a successful state.
/// </summary>
Success = 1,
/// <summary>
/// A bagde reporting a failed state.
/// </summary>
Failure = 2
}
/// <summary>
/// An instance of a badge created by the badge API.
/// </summary>
public interface IBadge
{
/// <summary>
/// Get the badge as SVG text content.
/// </summary>
string ToSvg();
/// <summary>
/// Write the SVG badge content directly to disk.
/// </summary>
void ToFile(string path);
}
/// <summary>
/// A builder for the badge API.
/// </summary>
public interface IBadgeBuilder
{
/// <summary>
/// Create a badge for the worst case of an analyzed object.
/// </summary>
/// <param name="result">A single result. The worst case for all records of an object is used for the badge.</param>
/// <returns>An instance of a badge.</returns>
IBadge Create(InvokeResult result);
/// <summary>
/// Create a badge for the worst case of all analyzed objects.
/// </summary>
/// <param name="result">A enumeration of results. The worst case from all results is used for the badge.</param>
/// <returns>An instance of a badge.</returns>
IBadge Create(IEnumerable<InvokeResult> result);
/// <summary>
/// Create a custom badge.
/// </summary>
/// <param name="title">The left badge text.</param>
/// <param name="type">Determines if the result is Unknown, Success, or Failure.</param>
/// <param name="label">The right badge text.</param>
/// <returns>An instance of a badge.</returns>
IBadge Create(string title, BadgeType type, string label);
}
/// <summary>
/// An instance of a badge created by the Badge API.
/// </summary>
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;
}
/// <inheritdoc/>
public override string ToString()
{
return ToSvg();
}
/// <inheritdoc/>
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();
}
/// <inheritdoc/>
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());
}
}
/// <summary> /// <summary>
/// A badge builder that implements the Badge API within PSRule. /// A badge builder that implements the Badge API within PSRule.
/// </summary> /// </summary>

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

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Badges;
/// <summary>
/// The type of badge.
/// </summary>
public enum BadgeType
{
/// <summary>
/// A badge that reports an unknown state.
/// </summary>
Unknown = 0,
/// <summary>
/// A badge reporting a successful state.
/// </summary>
Success = 1,
/// <summary>
/// A badge reporting a failed state.
/// </summary>
Failure = 2
}

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

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Badges;
/// <summary>
/// An instance of a badge created by the badge API.
/// </summary>
public interface IBadge
{
/// <summary>
/// Get the badge as SVG text content.
/// </summary>
string ToSvg();
/// <summary>
/// Write the SVG badge content directly to disk.
/// </summary>
void ToFile(string path);
}

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

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Pipeline;
namespace PSRule.Badges;
/// <summary>
/// A builder for the badge API.
/// </summary>
public interface IBadgeBuilder
{
/// <summary>
/// Create a badge for the worst case of an analyzed object.
/// </summary>
/// <param name="result">A single result. The worst case for all records of an object is used for the badge.</param>
/// <returns>An instance of a badge.</returns>
IBadge Create(InvokeResult result);
/// <summary>
/// Create a badge for the worst case of all analyzed objects.
/// </summary>
/// <param name="result">A enumeration of results. The worst case from all results is used for the badge.</param>
/// <returns>An instance of a badge.</returns>
IBadge Create(IEnumerable<InvokeResult> result);
/// <summary>
/// Create a custom badge.
/// </summary>
/// <param name="title">The left badge text.</param>
/// <param name="type">Determines if the result is Unknown, Success, or Failure.</param>
/// <param name="label">The right badge text.</param>
/// <returns>An instance of a badge.</returns>
IBadge Create(string title, BadgeType type, string label);
}

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

@ -25,7 +25,8 @@ internal static class ResourceExtensions
internal static bool IsLocalScope(this IResource 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<ResourceId> GetIds(this IResource resource) internal static IEnumerable<ResourceId> GetIds(this IResource resource)

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

@ -2,19 +2,9 @@
// Licensed under the MIT License. // Licensed under the MIT License.
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
namespace PSRule.Configuration; 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;
}
}
/// <summary> /// <summary>
/// Options that affect property binding of TargetName and TargetType. /// Options that affect property binding of TargetName and TargetType.
/// </summary> /// </summary>

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

@ -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;
}
}

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

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions;
/// <summary>
/// A resource language block.
/// </summary>
public interface IResource : ILanguageBlock
{
/// <summary>
/// The type of resource.
/// </summary>
ResourceKind Kind { get; }
/// <summary>
/// The API version of the resource.
/// </summary>
string ApiVersion { get; }
/// <summary>
/// The name of the resource.
/// </summary>
string Name { get; }
/// <summary>
/// An optional reference identifer for the resource.
/// </summary>
ResourceId? Ref { get; }
/// <summary>
/// Any additional aliases for the resource.
/// </summary>
ResourceId[] Alias { get; }
/// <summary>
/// Any resource tags.
/// </summary>
ResourceTags Tags { get; }
/// <summary>
/// Any taxonomy references.
/// </summary>
ResourceLabels Labels { get; }
/// <summary>
/// Flags for the resource.
/// </summary>
ResourceFlags Flags { get; }
/// <summary>
/// The source location of the resource.
/// </summary>
ISourceExtent Extent { get; }
/// <summary>
/// Additional information about the resource.
/// </summary>
IResourceHelpInfo Info { get; }
}

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

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions;
internal interface IResourceVisitor
{
bool Visit(IResource resource);
}

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

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Pipeline;
using YamlDotNet.Serialization;
namespace PSRule.Definitions;
/// <summary>
/// A base class for built-in resource types.
/// </summary>
/// <typeparam name="TSpec">The type of the related <seealso cref="Spec"/> for the resource.</typeparam>
public abstract class InternalResource<TSpec> : Resource<TSpec>, IResource, IAnnotated<ResourceAnnotation> where TSpec : Spec, new()
{
private readonly Dictionary<Type, ResourceAnnotation> _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<Type, ResourceAnnotation>();
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<ResourceAnnotation>.GetAnnotation<TAnnotation>()
{
return _Annotations.TryGetValue(typeof(TAnnotation), out var annotation) ? (TAnnotation)annotation : null;
}
void IAnnotated<ResourceAnnotation>.SetAnnotation<TAnnotation>(TAnnotation annotation)
{
_Annotations[typeof(TAnnotation)] = annotation;
}
}

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

@ -1,477 +1,12 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
using System.Collections;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using PSRule.Converters.Yaml;
using PSRule.Definitions.Rules;
using PSRule.Pipeline; using PSRule.Pipeline;
using PSRule.Runtime;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
namespace PSRule.Definitions; namespace PSRule.Definitions;
/// <summary>
/// The type of resource.
/// </summary>
public enum ResourceKind
{
/// <summary>
/// Unknown or empty.
/// </summary>
None = 0,
/// <summary>
/// A rule resource.
/// </summary>
Rule = 1,
/// <summary>
/// A baseline resource.
/// </summary>
Baseline = 2,
/// <summary>
/// A module configuration resource.
/// </summary>
ModuleConfig = 3,
/// <summary>
/// A selector resource.
/// </summary>
Selector = 4,
/// <summary>
/// A convention.
/// </summary>
Convention = 5,
/// <summary>
/// A suppression group.
/// </summary>
SuppressionGroup = 6
}
/// <summary>
/// Additional flags that indicate the status of the resource.
/// </summary>
[Flags]
public enum ResourceFlags
{
/// <summary>
/// No flags are set.
/// </summary>
None = 0,
/// <summary>
/// The resource is obsolete.
/// </summary>
Obsolete = 1
}
/// <summary>
/// A resource langange block.
/// </summary>
public interface IResource : ILanguageBlock
{
/// <summary>
/// The type of resource.
/// </summary>
ResourceKind Kind { get; }
/// <summary>
/// The API version of the resource.
/// </summary>
string ApiVersion { get; }
/// <summary>
/// The name of the resource.
/// </summary>
string Name { get; }
/// <summary>
/// An optional reference identifer for the resource.
/// </summary>
ResourceId? Ref { get; }
/// <summary>
/// Any additional aliases for the resource.
/// </summary>
ResourceId[] Alias { get; }
/// <summary>
/// Any resource tags.
/// </summary>
ResourceTags Tags { get; }
/// <summary>
/// Any taxonomy references.
/// </summary>
ResourceLabels Labels { get; }
/// <summary>
/// Flags for the resource.
/// </summary>
ResourceFlags Flags { get; }
/// <summary>
/// The source location of the resource.
/// </summary>
ISourceExtent Extent { get; }
/// <summary>
/// Additional information about the resource.
/// </summary>
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;
}
}
/// <summary>
/// A base resource annotation.
/// </summary>
internal abstract class ResourceAnnotation
{
}
/// <summary>
/// Annotation used to flag validation issues.
/// </summary>
internal sealed class ValidateResourceAnnotation : ResourceAnnotation
{
}
/// <summary>
/// A resource object.
/// </summary>
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<ILanguageBlock> _Output;
private readonly IDeserializer _Deserializer;
internal ResourceBuilder()
{
_Output = new List<ILanguageBlock>();
_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<ObjectNodeDeserializer>())
.Build();
}
internal void FromFile(SourceFile file)
{
using var reader = new StreamReader(file.Path);
var parser = new YamlDotNet.Core.Parser(reader);
parser.TryConsume<StreamStart>(out _);
while (parser.Current is DocumentStart)
{
var item = _Deserializer.Deserialize<ResourceObject>(parser: parser);
if (item == null || item.Block == null)
continue;
_Output.Add(item.Block);
}
}
internal IEnumerable<ILanguageBlock> Build()
{
return _Output.Count == 0 ? Array.Empty<ILanguageBlock>() : _Output.ToArray();
}
}
/// <summary>
/// Additional resource annotations.
/// </summary>
public sealed class ResourceAnnotations : Dictionary<string, object>
{
}
/// <summary>
/// Additional resource taxonomy references.
/// </summary>
public sealed class ResourceLabels : Dictionary<string, string[]>
{
/// <summary>
/// Create an empty set of resource labels.
/// </summary>
public ResourceLabels() : base(StringComparer.OrdinalIgnoreCase) { }
/// <summary>
/// Convert from a hashtable to resource labels.
/// </summary>
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;
}
}
/// <summary>
/// Additional resource tags.
/// </summary>
public sealed class ResourceTags : Dictionary<string, string>
{
private Hashtable _Hashtable;
/// <summary>
/// Create an empty set of resource tags.
/// </summary>
public ResourceTags() : base(StringComparer.OrdinalIgnoreCase) { }
/// <summary>
/// Convert from a hashtable to resource tags.
/// </summary>
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;
}
/// <summary>
/// Convert from a dictionary of string pairs to resource tags.
/// </summary>
internal static ResourceTags FromDictionary(Dictionary<string, string> dictionary)
{
if (dictionary == null)
return null;
var tags = new ResourceTags();
foreach (var kv in dictionary)
tags[kv.Key] = kv.Value;
return tags;
}
/// <summary>
/// Convert resource tags to a hashtable.
/// </summary>
public Hashtable ToHashtable()
{
_Hashtable ??= new ReadOnlyHashtable(this, StringComparer.OrdinalIgnoreCase);
return _Hashtable;
}
/// <summary>
/// Check if a specific resource tag exists.
/// </summary>
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<object> oValues)
{
var result = new List<string>();
foreach (var obj in oValues)
result.Add(obj.ToString());
values = result.ToArray();
return true;
}
return false;
}
/// <summary>
/// Convert the resourecs tags to a display string for PowerShell views.
/// </summary>
/// <returns></returns>
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();
}
}
/// <summary>
/// Additional resource metadata.
/// </summary>
public sealed class ResourceMetadata
{
/// <summary>
/// Create an empty set of metadata.
/// </summary>
public ResourceMetadata()
{
Annotations = new ResourceAnnotations();
Tags = new ResourceTags();
Labels = new ResourceLabels();
}
/// <summary>
/// The name of the resource.
/// </summary>
public string Name { get; set; }
/// <summary>
/// A non-localized display name for the resource.
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// A non-localized description of the resource.
/// </summary>
public string Description { get; set; }
/// <summary>
/// A opaque reference for the resource.
/// </summary>
public string Ref { get; set; }
/// <summary>
/// Additional aliases for the resource.
/// </summary>
public string[] Alias { get; set; }
/// <summary>
/// Any resource annotations.
/// </summary>
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public ResourceAnnotations Annotations { get; set; }
/// <summary>
/// Any resource tags.
/// </summary>
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public ResourceTags Tags { get; set; }
/// <summary>
/// Any taxonomy references.
/// </summary>
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public ResourceLabels Labels { get; set; }
/// <summary>
/// A URL to documentation for the resource.
/// </summary>
public string Link { get; set; }
}
/// <summary>
/// The source location of the resource.
/// </summary>
public sealed class ResourceExtent
{
/// <summary>
/// The file where the resource is located.
/// </summary>
public string File { get; set; }
/// <summary>
/// The name of the module if the resource is contained within a module.
/// </summary>
public string Module { get; set; }
}
/// <summary> /// <summary>
/// A base class for resources. /// A base class for resources.
/// </summary> /// </summary>
@ -550,132 +85,3 @@ public abstract class Resource<TSpec> where TSpec : Spec, new()
/// </summary> /// </summary>
public ISourceExtent Extent { get; } public ISourceExtent Extent { get; }
} }
/// <summary>
/// A base class for built-in resource types.
/// </summary>
/// <typeparam name="TSpec">The type of the related <seealso cref="Spec"/> for the resource.</typeparam>
public abstract class InternalResource<TSpec> : Resource<TSpec>, IResource, IAnnotated<ResourceAnnotation> where TSpec : Spec, new()
{
private readonly Dictionary<Type, ResourceAnnotation> _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<Type, ResourceAnnotation>();
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<ResourceAnnotation>.GetAnnotation<TAnnotation>()
{
return _Annotations.TryGetValue(typeof(TAnnotation), out var annotation) ? (TAnnotation)annotation : null;
}
void IAnnotated<ResourceAnnotation>.SetAnnotation<TAnnotation>(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);
}
/// <summary>
/// Checks each resource name and converts each into a full qualified <seealso cref="ResourceId"/>.
/// </summary>
/// <param name="defaultScope">The default scope to use if the resource name if not fully qualified.</param>
/// <param name="name">An array of names. Qualified names (RuleIds) supplied are left intact.</param>
/// <param name="kind">The <seealso cref="ResourceIdKind"/> of the <seealso cref="ResourceId"/>.</param>
/// <returns>An array of RuleIds.</returns>
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;
}
}

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

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions;
/// <summary>
/// A base resource annotation.
/// </summary>
internal abstract class ResourceAnnotation
{
}

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

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions;
/// <summary>
/// Additional resource annotations.
/// </summary>
public sealed class ResourceAnnotations : Dictionary<string, object>
{
}

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

@ -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<ILanguageBlock> _Output;
private readonly IDeserializer _Deserializer;
internal ResourceBuilder()
{
_Output = new List<ILanguageBlock>();
_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<ObjectNodeDeserializer>())
.Build();
}
internal void FromFile(SourceFile file)
{
using var reader = new StreamReader(file.Path);
var parser = new YamlDotNet.Core.Parser(reader);
parser.TryConsume<StreamStart>(out _);
while (parser.Current is DocumentStart)
{
var item = _Deserializer.Deserialize<ResourceObject>(parser: parser);
if (item == null || item.Block == null)
continue;
_Output.Add(item.Block);
}
}
internal IEnumerable<ILanguageBlock> Build()
{
return _Output.Count == 0 ? Array.Empty<ILanguageBlock>() : _Output.ToArray();
}
}

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

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions;
/// <summary>
/// The source location of the resource.
/// </summary>
public sealed class ResourceExtent
{
/// <summary>
/// The file where the resource is located.
/// </summary>
public string File { get; set; }
/// <summary>
/// The name of the module if the resource is contained within a module.
/// </summary>
public string Module { get; set; }
}

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

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions;
/// <summary>
/// Additional flags that indicate the status of the resource.
/// </summary>
[Flags]
public enum ResourceFlags
{
/// <summary>
/// No flags are set.
/// </summary>
None = 0,
/// <summary>
/// The resource is obsolete.
/// </summary>
Obsolete = 1
}

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

@ -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);
}
/// <summary>
/// Checks each resource name and converts each into a full qualified <seealso cref="ResourceId"/>.
/// </summary>
/// <param name="defaultScope">The default scope to use if the resource name if not fully qualified.</param>
/// <param name="name">An array of names. Qualified names (RuleIds) supplied are left intact.</param>
/// <param name="kind">The <seealso cref="ResourceIdKind"/> of the <seealso cref="ResourceId"/>.</param>
/// <returns>An array of RuleIds.</returns>
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;
}
}

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

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions;
/// <summary>
/// The type of resource.
/// </summary>
public enum ResourceKind
{
/// <summary>
/// Unknown or empty.
/// </summary>
None = 0,
/// <summary>
/// A rule resource.
/// </summary>
Rule = 1,
/// <summary>
/// A baseline resource.
/// </summary>
Baseline = 2,
/// <summary>
/// A module configuration resource.
/// </summary>
ModuleConfig = 3,
/// <summary>
/// A selector resource.
/// </summary>
Selector = 4,
/// <summary>
/// A convention.
/// </summary>
Convention = 5,
/// <summary>
/// A suppression group.
/// </summary>
SuppressionGroup = 6
}

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

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
namespace PSRule.Definitions;
/// <summary>
/// Additional resource taxonomy references.
/// </summary>
public sealed class ResourceLabels : Dictionary<string, string[]>
{
/// <summary>
/// Create an empty set of resource labels.
/// </summary>
public ResourceLabels() : base(StringComparer.OrdinalIgnoreCase) { }
/// <summary>
/// Convert from a hashtable to resource labels.
/// </summary>
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;
}
}

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

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using YamlDotNet.Serialization;
namespace PSRule.Definitions;
/// <summary>
/// Additional resource metadata.
/// </summary>
public sealed class ResourceMetadata
{
/// <summary>
/// Create an empty set of metadata.
/// </summary>
public ResourceMetadata()
{
Annotations = new ResourceAnnotations();
Tags = new ResourceTags();
Labels = new ResourceLabels();
}
/// <summary>
/// The name of the resource.
/// </summary>
public string Name { get; set; }
/// <summary>
/// A non-localized display name for the resource.
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// A non-localized description of the resource.
/// </summary>
public string Description { get; set; }
/// <summary>
/// A opaque reference for the resource.
/// </summary>
public string Ref { get; set; }
/// <summary>
/// Additional aliases for the resource.
/// </summary>
public string[] Alias { get; set; }
/// <summary>
/// Any resource annotations.
/// </summary>
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public ResourceAnnotations Annotations { get; set; }
/// <summary>
/// Any resource tags.
/// </summary>
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public ResourceTags Tags { get; set; }
/// <summary>
/// Any taxonomy references.
/// </summary>
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public ResourceLabels Labels { get; set; }
/// <summary>
/// A URL to documentation for the resource.
/// </summary>
public string Link { get; set; }
}

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

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions;
/// <summary>
/// A resource object.
/// </summary>
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);
}
}

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

@ -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;
}
}

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

@ -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;
/// <summary>
/// Additional resource tags.
/// </summary>
public sealed class ResourceTags : Dictionary<string, string>
{
private Hashtable _Hashtable;
/// <summary>
/// Create an empty set of resource tags.
/// </summary>
[DebuggerStepThrough]
public ResourceTags() : base(StringComparer.OrdinalIgnoreCase) { }
/// <summary>
/// Convert from a hashtable to resource tags.
/// </summary>
[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;
}
/// <summary>
/// Convert from a dictionary of string pairs to resource tags.
/// </summary>
[DebuggerStepThrough]
internal static ResourceTags FromDictionary(Dictionary<string, string> dictionary)
{
if (dictionary == null)
return null;
var tags = new ResourceTags();
foreach (var kv in dictionary)
tags[kv.Key] = kv.Value;
return tags;
}
/// <summary>
/// Convert resource tags to a hashtable.
/// </summary>
[DebuggerStepThrough]
public Hashtable ToHashtable()
{
_Hashtable ??= new ReadOnlyHashtable(this, StringComparer.OrdinalIgnoreCase);
return _Hashtable;
}
/// <summary>
/// Check if a specific resource tag exists.
/// </summary>
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<object> oValues)
{
var result = new List<string>();
foreach (var obj in oValues)
result.Add(obj.ToString());
values = result.ToArray();
return true;
}
return false;
}
/// <summary>
/// Convert the resourecs tags to a display string for PowerShell views.
/// </summary>
/// <returns></returns>
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();
}
}

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

@ -48,12 +48,6 @@ internal sealed class RuleFilter : IResourceFilter
ResourceKind IResourceFilter.Kind => ResourceKind.Rule; 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);
}
/// <summary> /// <summary>
/// Matches if the RuleId is contained or any tag is matched /// Matches if the RuleId is contained or any tag is matched
/// </summary> /// </summary>

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

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions;
/// <summary>
/// Annotation used to flag validation issues.
/// </summary>
internal sealed class ValidateResourceAnnotation : ResourceAnnotation
{
}

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

@ -3,13 +3,6 @@
namespace PSRule.Help; namespace PSRule.Help;
internal enum MarkdownReaderMode
{
None,
List
}
/// <summary> /// <summary>
/// Stateful markdown reader. /// Stateful markdown reader.
/// </summary> /// </summary>

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

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Help;
internal enum MarkdownReaderMode
{
None,
List
}

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

@ -7,49 +7,6 @@ namespace PSRule.Help;
internal delegate bool CharacterMatchDelegate(char c); 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}")] [DebuggerDisplay("Position = {Position}, Current = {Current}")]
internal sealed class MarkdownStream internal sealed class MarkdownStream
{ {

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

@ -5,70 +5,6 @@ using System.Diagnostics;
namespace PSRule.Help; 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}")] [DebuggerDisplay("Type = {Type}, Text = {Text}")]
internal sealed class MarkdownToken internal sealed class MarkdownToken
{ {

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

@ -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);
}
}

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

@ -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
}

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

@ -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
}

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

@ -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;
}
}
}

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

@ -6,193 +6,6 @@ using System.Diagnostics;
namespace PSRule.Help; namespace PSRule.Help;
internal static class TokenStreamExtensions
{
/// <summary>
/// Add a header.
/// </summary>
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
});
}
/// <summary>
/// Add a code fence.
/// </summary>
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
});
}
/// <summary>
/// Add a line break.
/// </summary>
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 });
}
/// <summary>
/// Add a marker for the start of a paragraph.
/// </summary>
public static void ParagraphStart(this TokenStream stream)
{
stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphStart });
}
/// <summary>
/// Add a marker for the end of a paragraph.
/// </summary>
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<MarkdownToken> GetSection(this TokenStream stream, string header)
{
return stream.Count == 0
? Enumerable.Empty<MarkdownToken>()
: 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<MarkdownToken> GetSections(this TokenStream stream)
{
return stream.Count == 0
? Enumerable.Empty<MarkdownToken>()
: 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}")] [DebuggerDisplay("Current = {Current?.Text}")]
internal sealed class TokenStream : IEnumerable<MarkdownToken> internal sealed class TokenStream : IEnumerable<MarkdownToken>
{ {

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

@ -0,0 +1,191 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Help;
internal static class TokenStreamExtensions
{
/// <summary>
/// Add a header.
/// </summary>
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
});
}
/// <summary>
/// Add a code fence.
/// </summary>
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
});
}
/// <summary>
/// Add a line break.
/// </summary>
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 });
}
/// <summary>
/// Add a marker for the start of a paragraph.
/// </summary>
public static void ParagraphStart(this TokenStream stream)
{
stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphStart });
}
/// <summary>
/// Add a marker for the end of a paragraph.
/// </summary>
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<MarkdownToken> GetSection(this TokenStream stream, string header)
{
return stream.Count == 0
? Enumerable.Empty<MarkdownToken>()
: 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<MarkdownToken> GetSections(this TokenStream stream)
{
return stream.Count == 0
? Enumerable.Empty<MarkdownToken>()
: 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);
}
}

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

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
using PSRule.Runtime;
namespace PSRule.Host;
/// <summary>
/// An assertion helper variable $Assert used during Rule execution.
/// </summary>
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;
}

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

@ -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;
}

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

@ -4,111 +4,9 @@
using System.Management.Automation; using System.Management.Automation;
using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces;
using PSRule.Commands; using PSRule.Commands;
using PSRule.Runtime;
namespace PSRule.Host; namespace PSRule.Host;
/// <summary>
/// A dynamic variable $PSRule used during Rule execution.
/// </summary>
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;
}
/// <summary>
/// A dynamic variable $Rule used during Rule execution.
/// </summary>
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;
}
/// <summary>
/// A dynamic variable $LocalizedData used during Rule execution.
/// </summary>
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;
}
/// <summary>
/// An assertion helper variable $Assert used during Rule execution.
/// </summary>
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;
}
/// <summary>
/// A dynamic variable used during Rule execution.
/// </summary>
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 internal static class HostState
{ {
/// <summary> /// <summary>

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

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
using PSRule.Runtime;
namespace PSRule.Host;
/// <summary>
/// A dynamic variable $LocalizedData used during Rule execution.
/// </summary>
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;
}

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

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
using PSRule.Runtime;
namespace PSRule.Host;
/// <summary>
/// A dynamic variable $PSRule used during Rule execution.
/// </summary>
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;
}

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

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
using PSRule.Runtime;
namespace PSRule.Host;
/// <summary>
/// A dynamic variable $Rule used during Rule execution.
/// </summary>
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;
}

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

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
using PSRule.Runtime;
namespace PSRule.Host;
/// <summary>
/// A dynamic variable used during Rule execution.
/// </summary>
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;
}

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

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Newtonsoft.Json;
using PSRule.Data;
namespace PSRule.Pipeline.Dependencies;
/// <summary>
/// An entry within the lock file.
/// </summary>
public sealed class LockEntry
{
/// <summary>
/// The version to use.
/// </summary>
[JsonProperty("version", NullValueHandling = NullValueHandling.Include)]
public SemanticVersion.Version Version { get; set; }
}

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

@ -3,22 +3,9 @@
using System.Text; using System.Text;
using Newtonsoft.Json; using Newtonsoft.Json;
using PSRule.Data;
namespace PSRule.Pipeline.Dependencies; namespace PSRule.Pipeline.Dependencies;
/// <summary>
/// An entry within the lock file.
/// </summary>
public sealed class LockEntry
{
/// <summary>
/// The version to use.
/// </summary>
[JsonProperty("version", NullValueHandling = NullValueHandling.Include)]
public SemanticVersion.Version Version { get; set; }
}
/// <summary> /// <summary>
/// Define the structure for the PSRule lock file. /// Define the structure for the PSRule lock file.
/// By default, this file is <c>ps-rule.lock.json</c>. /// By default, this file is <c>ps-rule.lock.json</c>.

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

@ -3,10 +3,9 @@
using PSRule.Emitters; using PSRule.Emitters;
namespace PSRule.Pipeline.Emitters namespace PSRule.Pipeline.Emitters;
{
/// <summary> /// <summary>
/// A chain of emitters. /// A chain of emitters.
/// </summary> /// </summary>
internal delegate bool EmitterChain(IEmitterContext context, object o, Type type); internal delegate bool EmitterChain(IEmitterContext context, object o, Type type);
}

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

@ -4,51 +4,50 @@
using System.Diagnostics; using System.Diagnostics;
using PSRule.Data; using PSRule.Data;
namespace PSRule.Pipeline.Emitters namespace PSRule.Pipeline.Emitters;
[DebuggerDisplay("{Path}")]
internal sealed class InternalFileInfo : IFileInfo, IDisposable
{ {
[DebuggerDisplay("{Path}")] private FileStream _Stream;
internal sealed class InternalFileInfo : IFileInfo, IDisposable private bool _Disposed;
public InternalFileInfo(string path, string extension)
{ {
private FileStream _Stream; Path = path;
private bool _Disposed; Extension = extension;
}
public InternalFileInfo(string path, string extension) /// <inheritdoc/>
public string Path { get; }
/// <inheritdoc/>
public string Extension { get; }
/// <inheritdoc/>
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)
{
if (!_Disposed)
{ {
Path = path; if (disposing)
Extension = extension;
}
/// <inheritdoc/>
public string Path { get; }
/// <inheritdoc/>
public string Extension { get; }
/// <inheritdoc/>
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)
{
if (!_Disposed)
{ {
if (disposing) _Stream.Dispose();
{
_Stream.Dispose();
}
_Disposed = true;
} }
} _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);
}
} }

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

@ -4,52 +4,51 @@
using System.Text; using System.Text;
using PSRule.Data; 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 bool _Disposed;
internal InternalFileStream(IFileInfo info, Stream stream)
{ {
private readonly Stream _Stream; Info = info;
_Stream = stream;
}
private bool _Disposed; /// <inheritdoc/>
public IFileInfo Info { get; }
internal InternalFileStream(IFileInfo info, Stream stream) /// <inheritdoc/>
public TextReader AsTextReader()
{
return new StreamReader(
stream: _Stream,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true
);
}
private void Dispose(bool disposing)
{
if (!_Disposed)
{ {
Info = info; if (disposing)
_Stream = stream;
}
/// <inheritdoc/>
public IFileInfo Info { get; }
/// <inheritdoc/>
public TextReader AsTextReader()
{
return new StreamReader(
stream: _Stream,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true
);
}
private void Dispose(bool disposing)
{
if (!_Disposed)
{ {
if (disposing) _Stream?.Dispose();
{
_Stream?.Dispose();
}
_Disposed = true;
} }
} _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);
}
} }

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

@ -24,10 +24,9 @@ internal sealed class JsonEmitter : FileEmitter
public JsonEmitter() public JsonEmitter()
{ {
_Settings = new JsonSerializerSettings _Settings = new JsonSerializerSettings
{ {
}; };
_Deserializer = JsonSerializer.CreateDefault(_Settings); // Think about caching this. _Deserializer = JsonSerializer.CreateDefault(_Settings); // Think about caching this.
_Deserializer.Converters.Add(new PSObjectArrayJsonConverter(null)); _Deserializer.Converters.Add(new PSObjectArrayJsonConverter(null));
@ -88,13 +87,13 @@ internal sealed class JsonEmitter : FileEmitter
VisitItems(context, value, null); VisitItems(context, value, null);
return true; return true;
} }
catch (Exception ex) catch (Exception)
{ {
throw; throw;
} }
finally finally
{ {
} }
} }

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

@ -99,7 +99,7 @@ internal sealed class YamlEmitter : FileEmitter
} }
return true; return true;
} }
catch (Exception ex) catch (Exception)
{ {
throw; throw;
} }

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

@ -9,115 +9,6 @@ using PSRule.Rules;
namespace PSRule.Pipeline.Formatters; 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);
}
/// <summary>
/// Configures formatted output.
/// </summary>
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; }
}
/// <summary> /// <summary>
/// A base class for a formatter. /// A base class for a formatter.
/// </summary> /// </summary>

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

@ -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);
}

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

@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Resources;
namespace PSRule.Pipeline.Formatters;
/// <summary>
/// Configures formatted output.
/// </summary>
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; }
}

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

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
using PSRule.Definitions;
namespace PSRule.Rules;
/// <summary>
/// A rule help information structure.
/// </summary>
public interface IRuleHelpInfoV2 : IResourceHelpInfo
{
/// <summary>
/// The rule recommendation.
/// </summary>
InfoString Recommendation { get; }
/// <summary>
/// Additional annotations, which are string key/ value pairs.
/// </summary>
Hashtable Annotations { get; }
/// <summary>
/// The name of the module where the rule was loaded from.
/// </summary>
string ModuleName { get; }
/// <summary>
/// Additional online links to reference information for the rule.
/// </summary>
Link[] Links { get; }
}

26
src/PSRule/Rules/Link.cs Normal file
Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Rules;
/// <summary>
/// An URL link to reference information.
/// </summary>
public sealed class Link
{
internal Link(string name, string uri)
{
Name = name;
Uri = uri;
}
/// <summary>
/// The display name of the link.
/// </summary>
public string Name { get; }
/// <summary>
/// The URL to the information, or the target link.
/// </summary>
public string Uri { get; }
}

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

@ -9,108 +9,6 @@ using YamlDotNet.Serialization;
namespace PSRule.Rules; namespace PSRule.Rules;
/// <summary>
/// A rule help information structure.
/// </summary>
public interface IRuleHelpInfoV2 : IResourceHelpInfo
{
/// <summary>
/// The rule recommendation.
/// </summary>
InfoString Recommendation { get; }
/// <summary>
/// Additional annotations, which are string key/ value pairs.
/// </summary>
Hashtable Annotations { get; }
/// <summary>
/// The name of the module where the rule was loaded from.
/// </summary>
string ModuleName { get; }
/// <summary>
/// Additional online links to reference information for the rule.
/// </summary>
Link[] Links { get; }
}
/// <summary>
/// Extension methods for rule help information.
/// </summary>
public static class RuleHelpInfoExtensions
{
private const string ONLINE_HELP_LINK_ANNOTATION = "online version";
/// <summary>
/// Get the URI for the online version of the documentation.
/// </summary>
/// <returns>Returns the URI when a valid link is set, otherwise null is returned.</returns>
public static Uri GetOnlineHelpUri(this IRuleHelpInfoV2 info)
{
var link = GetOnlineHelpUrl(info);
return link == null ||
!Uri.TryCreate(link, UriKind.Absolute, out var result) ?
null : result;
}
/// <summary>
/// Get the URL for the online version of the documentation.
/// </summary>
/// <returns>Returns the URL when set, otherwise null is returned.</returns>
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();
}
/// <summary>
/// Determines if the online help link is set.
/// </summary>
/// <returns>Returns <c>true</c> when the online help link is set. Otherwise this method returns <c>false</c>.</returns>
internal static bool HasOnlineHelp(this IRuleHelpInfoV2 info)
{
return info != null &&
info.Annotations != null &&
info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION);
}
/// <summary>
/// Set the online help link from the <paramref name="url"/> parameter.
/// </summary>
/// <param name="info">The info object.</param>
/// <param name="url">A URL to the online help location.</param>
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;
}
}
/// <summary>
/// An URL link to reference information.
/// </summary>
public sealed class Link
{
internal Link(string name, string uri)
{
Name = name;
Uri = uri;
}
/// <summary>
/// The display name of the link.
/// </summary>
public string Name { get; }
/// <summary>
/// The URL to the information, or the target link.
/// </summary>
public string Uri { get; }
}
/// <summary> /// <summary>
/// Output view helper class for rule help. /// Output view helper class for rule help.
/// </summary> /// </summary>

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

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Rules;
/// <summary>
/// Extension methods for rule help information.
/// </summary>
public static class RuleHelpInfoExtensions
{
private const string ONLINE_HELP_LINK_ANNOTATION = "online version";
/// <summary>
/// Get the URI for the online version of the documentation.
/// </summary>
/// <returns>Returns the URI when a valid link is set, otherwise null is returned.</returns>
public static Uri GetOnlineHelpUri(this IRuleHelpInfoV2 info)
{
var link = GetOnlineHelpUrl(info);
return link == null ||
!Uri.TryCreate(link, UriKind.Absolute, out var result) ?
null : result;
}
/// <summary>
/// Get the URL for the online version of the documentation.
/// </summary>
/// <returns>Returns the URL when set, otherwise null is returned.</returns>
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();
}
/// <summary>
/// Determines if the online help link is set.
/// </summary>
/// <returns>Returns <c>true</c> when the online help link is set. Otherwise this method returns <c>false</c>.</returns>
internal static bool HasOnlineHelp(this IRuleHelpInfoV2 info)
{
return info != null &&
info.Annotations != null &&
info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION);
}
/// <summary>
/// Set the online help link from the <paramref name="url"/> parameter.
/// </summary>
/// <param name="info">The info object.</param>
/// <param name="url">A URL to the online help location.</param>
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;
}
}

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

@ -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;
/// <summary>
/// A named scope for language elements.
/// </summary>
internal interface ILanguageScope : IDisposable
{
/// <summary>
/// The name of the scope.
/// </summary>
string Name { get; }
BindingOption Binding { get; }
/// <summary>
/// Get an ordered culture preference list which will be tries for finding help.
/// </summary>
string[] Culture { get; }
void Configure(OptionContext context);
/// <summary>
/// Try to get a specific configuration value by name.
/// </summary>
bool TryConfigurationValue(string key, out object value);
void WithFilter(IResourceFilter resourceFilter);
/// <summary>
/// Get a filter for a specific resource kind.
/// </summary>
IResourceFilter GetFilter(ResourceKind kind);
/// <summary>
/// Add a service to the scope.
/// </summary>
void AddService(string name, object service);
/// <summary>
/// Get a previously added service.
/// </summary>
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);
}

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

@ -3,25 +3,6 @@
namespace PSRule.Runtime; namespace PSRule.Runtime;
/// <summary>
/// A set of log levels which indicate different types of diagnostic messages.
/// </summary>
[Flags]
internal enum LogLevel
{
None = 0,
Error = 1,
Warning = 2,
Info = 4,
Verbose = 8,
Debug = 16,
}
/// <summary> /// <summary>
/// A generic interface for diagnostic logging within PSRule. /// A generic interface for diagnostic logging within PSRule.
/// </summary> /// </summary>
@ -45,6 +26,6 @@ internal interface ILogger
/// Write an error from an exception. /// Write an error from an exception.
/// </summary> /// </summary>
/// <param name="exception">The exception to write.</param> /// <param name="exception">The exception to write.</param>
/// <param name="errorId">A string identififer for the error.</param> /// <param name="errorId">A string identifier for the error.</param>
void Error(Exception exception, string errorId = null); void Error(Exception exception, string errorId = null);
} }

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

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime;
/// <summary>
/// An operand that is compared with PSRule expressions.
/// </summary>
public interface IOperand
{
/// <summary>
/// The value of the operand.
/// </summary>
object Value { get; }
/// <summary>
/// The type of operand.
/// </summary>
OperandKind Kind { get; }
/// <summary>
/// The object path to the operand.
/// </summary>
string Path { get; }
/// <summary>
/// A logical prefix to add to the object path.
/// </summary>
string Prefix { get; set; }
}

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

@ -8,54 +8,6 @@ using PSRule.Pipeline;
namespace PSRule.Runtime; namespace PSRule.Runtime;
/// <summary>
/// A named scope for language elements.
/// </summary>
internal interface ILanguageScope : IDisposable
{
/// <summary>
/// The name of the scope.
/// </summary>
string Name { get; }
BindingOption Binding { get; }
/// <summary>
/// Get an ordered culture preference list which will be tries for finding help.
/// </summary>
string[] Culture { get; }
void Configure(OptionContext context);
/// <summary>
/// Try to get a specific configuration value by name.
/// </summary>
bool TryConfigurationValue(string key, out object value);
void WithFilter(IResourceFilter resourceFilter);
/// <summary>
/// Get a filter for a specific resource kind.
/// </summary>
IResourceFilter GetFilter(ResourceKind kind);
/// <summary>
/// Add a service to the scope.
/// </summary>
void AddService(string name, object service);
/// <summary>
/// Get a previously added service.
/// </summary>
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}")] [DebuggerDisplay("{Name}")]
internal sealed class LanguageScope : ILanguageScope internal sealed class LanguageScope : ILanguageScope
{ {
@ -225,102 +177,3 @@ internal sealed class LanguageScope : ILanguageScope
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }
/// <summary>
/// A collection of <see cref="ILanguageScope"/>.
/// </summary>
internal sealed class LanguageScopeSet : IDisposable
{
private readonly RunspaceContext _Context;
private readonly Dictionary<string, ILanguageScope> _Scopes;
private ILanguageScope _Current;
private bool _Disposed;
public LanguageScopeSet(RunspaceContext context)
{
_Context = context;
_Scopes = new Dictionary<string, ILanguageScope>(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<ILanguageScope> Get()
{
return _Scopes.Values;
}
/// <summary>
/// Switch to a specific language scope by name.
/// </summary>
/// <param name="name">The name of the language scope to switch to.</param>
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);
}
}

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

@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime;
/// <summary>
/// A collection of <see cref="ILanguageScope"/>.
/// </summary>
internal sealed class LanguageScopeSet : IDisposable
{
private readonly RunspaceContext _Context;
private readonly Dictionary<string, ILanguageScope> _Scopes;
private ILanguageScope _Current;
private bool _Disposed;
public LanguageScopeSet(RunspaceContext context)
{
_Context = context;
_Scopes = new Dictionary<string, ILanguageScope>(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<ILanguageScope> Get()
{
return _Scopes.Values;
}
/// <summary>
/// Switch to a specific language scope by name.
/// </summary>
/// <param name="name">The name of the language scope to switch to.</param>
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);
}
}

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

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime;
/// <summary>
/// A set of log levels which indicate different types of diagnostic messages.
/// </summary>
[Flags]
internal enum LogLevel
{
None = 0,
Error = 1,
Warning = 2,
Info = 4,
Verbose = 8,
Debug = 16,
}

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

@ -5,27 +5,6 @@ using System.Diagnostics;
namespace PSRule.Runtime; namespace PSRule.Runtime;
/// <summary>
/// The type of NameToken.
/// </summary>
internal enum NameTokenType
{
/// <summary>
/// The token represents a field/ property of an object.
/// </summary>
Field = 0,
/// <summary>
/// The token is an index in an object.
/// </summary>
Index = 1,
/// <summary>
/// The token is a reference to the parent object. Can only be the first token.
/// </summary>
Self = 2
}
/// <summary> /// <summary>
/// A token for expressing a path through a tree of fields. /// A token for expressing a path through a tree of fields.
/// </summary> /// </summary>

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

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime;
/// <summary>
/// The type of NameToken.
/// </summary>
internal enum NameTokenType
{
/// <summary>
/// The token represents a field/ property of an object.
/// </summary>
Field = 0,
/// <summary>
/// The token is an index in an object.
/// </summary>
Index = 1,
/// <summary>
/// The token is a reference to the parent object. Can only be the first token.
/// </summary>
Self = 2
}

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

@ -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,
}

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

@ -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<T>();
}

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

@ -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);
}

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

@ -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);
}

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

@ -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<T>()
{
return Arg is T result ? result : default;
}
}

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

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime.ObjectPath;
internal enum PathTokenOption
{
None = 0,
CaseSensitive
}

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

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime.ObjectPath;
internal enum PathTokenType
{
None = 0,
/// <summary>
/// Token: $
/// </summary>
RootRef,
/// <summary>
/// Token: @
/// </summary>
CurrentRef,
/// <summary>
/// Token: .Name
/// </summary>
DotSelector,
/// <summary>
/// Token: [index]
/// </summary>
IndexSelector,
/// <summary>
/// Token: [*]
/// </summary>
IndexWildSelector,
StartFilter,
ComparisonOperator,
Boolean,
EndFilter,
String,
Integer,
LogicalOperator,
StartGroup,
EndGroup,
/// <summary>
/// Token: !
/// </summary>
NotOperator,
/// <summary>
/// Token: ..
/// </summary>
DescendantSelector,
/// <summary>
/// Token: .*
/// </summary>
DotWildSelector,
ArraySliceSelector,
UnionIndexSelector,
UnionQuotedMemberSelector,
}

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

@ -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;
}
}

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

@ -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,
/// <summary>
/// Token: $
/// </summary>
RootRef,
/// <summary>
/// Token: @
/// </summary>
CurrentRef,
/// <summary>
/// Token: .Name
/// </summary>
DotSelector,
/// <summary>
/// Token: [index]
/// </summary>
IndexSelector,
/// <summary>
/// Token: [*]
/// </summary>
IndexWildSelector,
StartFilter,
ComparisonOperator,
Boolean,
EndFilter,
String,
Integer,
LogicalOperator,
StartGroup,
EndGroup,
/// <summary>
/// Token: !
/// </summary>
NotOperator,
/// <summary>
/// Token: ..
/// </summary>
DescendantSelector,
/// <summary>
/// Token: .*
/// </summary>
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<T>();
}
[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<T>()
{
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;
}
}

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

@ -3,78 +3,6 @@
namespace PSRule.Runtime; namespace PSRule.Runtime;
/// <summary>
/// The type of operand that is compared with the expression.
/// </summary>
public enum OperandKind
{
/// <summary>
/// Unknown.
/// </summary>
None = 0,
/// <summary>
/// An object path.
/// </summary>
Path = 1,
/// <summary>
/// The object target type.
/// </summary>
Type = 2,
/// <summary>
/// The object target name.
/// </summary>
Name = 3,
/// <summary>
/// The object source information.
/// </summary>
Source = 4,
/// <summary>
/// The target object itself.
/// </summary>
Target = 5,
/// <summary>
/// A literal value or function.
/// </summary>
Value = 6,
/// <summary>
/// The object scope.
/// </summary>
Scope = 7
}
/// <summary>
/// An operand that is compared with PSRule expressions.
/// </summary>
public interface IOperand
{
/// <summary>
/// The value of the operand.
/// </summary>
object Value { get; }
/// <summary>
/// The type of operand.
/// </summary>
OperandKind Kind { get; }
/// <summary>
/// The object path to the operand.
/// </summary>
string Path { get; }
/// <summary>
/// A logical prefix to add to the object path.
/// </summary>
string Prefix { get; set; }
}
internal sealed class Operand : IOperand internal sealed class Operand : IOperand
{ {
private const string Dot = "."; private const string Dot = ".";

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

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime;
/// <summary>
/// The type of operand that is compared with the expression.
/// </summary>
public enum OperandKind
{
/// <summary>
/// Unknown.
/// </summary>
None = 0,
/// <summary>
/// An object path.
/// </summary>
Path = 1,
/// <summary>
/// The object target type.
/// </summary>
Type = 2,
/// <summary>
/// The object target name.
/// </summary>
Name = 3,
/// <summary>
/// The object source information.
/// </summary>
Source = 4,
/// <summary>
/// The target object itself.
/// </summary>
Target = 5,
/// <summary>
/// A literal value or function.
/// </summary>
Value = 6,
/// <summary>
/// The object scope.
/// </summary>
Scope = 7
}

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

@ -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<object> 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;
}
}

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

@ -5,64 +5,6 @@ using PSRule.Definitions;
namespace PSRule.Runtime; namespace PSRule.Runtime;
internal static class RuleConditionHelper
{
private static readonly RuleConditionResult Empty = new(pass: 0, count: 0, hadErrors: false);
internal static RuleConditionResult Create(IEnumerable<object> 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 sealed class RuleConditionResult : IConditionResult
{ {
internal RuleConditionResult(int pass, int count, bool hadErrors) internal RuleConditionResult(int pass, int count, bool hadErrors)

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

@ -14,42 +14,6 @@ using static PSRule.Pipeline.PipelineContext;
namespace PSRule.Runtime; namespace PSRule.Runtime;
/// <summary>
/// The available language scope types.
/// </summary>
[Flags]
public enum RunspaceScope
{
None = 0,
Source = 1,
/// <summary>
/// Executing a rule.
/// </summary>
Rule = 2,
/// <summary>
/// Executing a rule precondition.
/// </summary>
Precondition = 4,
/// <summary>
/// Execution is currently parsing YAML objects.
/// </summary>
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,
}
/// <summary> /// <summary>
/// A context for a PSRule runspace. /// A context for a PSRule runspace.
/// </summary> /// </summary>

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

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime;
/// <summary>
/// The available language scope types.
/// </summary>
[Flags]
public enum RunspaceScope
{
None = 0,
Source = 1,
/// <summary>
/// Executing a rule.
/// </summary>
Rule = 2,
/// <summary>
/// Executing a rule precondition.
/// </summary>
Precondition = 4,
/// <summary>
/// Execution is currently parsing YAML objects.
/// </summary>
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,
}

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

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
using System;
using System.Collections; using System.Collections;
using PSRule.Definitions; using PSRule.Definitions;
using PSRule.Definitions.Rules; using PSRule.Definitions.Rules;
@ -10,24 +11,24 @@ namespace PSRule;
/// <summary> /// <summary>
/// Define tests to validate <see cref="RuleFilter"/>. /// Define tests to validate <see cref="RuleFilter"/>.
/// </summary> /// </summary>
public sealed class RuleFilterTests public sealed partial class RuleFilterTests
{ {
[Fact] [Fact]
public void MatchInclude() public void MatchInclude()
{ {
var filter = new RuleFilter(new string[] { "rule1", "rule2" }, null, null, null, null); var filter = new RuleFilter(["rule1", "rule2"], null, null, null, null);
Assert.True(filter.Match("rule1", null, null)); Assert.True(filter.Match(GetResource("rule1")));
Assert.True(filter.Match("Rule2", null, null)); Assert.True(filter.Match(GetResource("Rule2")));
Assert.False(filter.Match("rule3", null, null)); Assert.False(filter.Match(GetResource("rule3")));
} }
[Fact] [Fact]
public void MatchExclude() public void MatchExclude()
{ {
var filter = new RuleFilter(null, null, new string[] { "rule3" }, null, null); var filter = new RuleFilter(null, null, ["rule3"], null, null);
Assert.True(filter.Match("rule1", null, null)); Assert.True(filter.Match(GetResource("rule1")));
Assert.True(filter.Match("rule2", null, null)); Assert.True(filter.Match(GetResource("rule2")));
Assert.False(filter.Match("Rule3", null, null)); Assert.False(filter.Match(GetResource("Rule3")));
} }
[Fact] [Fact]
@ -45,12 +46,23 @@ public sealed class RuleFilterTests
// Check basic match // Check basic match
resourceTags["category"] = "group2"; resourceTags["category"] = "group2";
Assert.True(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); Assert.True(filter.Match(GetResource("rule", ResourceTags.FromHashtable(resourceTags))));
resourceTags["category"] = "group1"; resourceTags["category"] = "group1";
Assert.True(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); Assert.True(filter.Match(GetResource("rule", ResourceTags.FromHashtable(resourceTags))));
resourceTags["category"] = "group3"; resourceTags["category"] = "group3";
Assert.False(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); Assert.False(filter.Match(GetResource("rule", ResourceTags.FromHashtable(resourceTags))));
Assert.False(filter.Match("rule", null, null)); 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] [Fact]
@ -62,19 +74,28 @@ public sealed class RuleFilterTests
// Create a filter // Create a filter
var labels = new ResourceLabels 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); var filter = new RuleFilter(null, null, null, null, labels);
resourceLabels["framework.v1/control"] = new string[] { "c-2", "c-1" }; 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" }; 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" }; 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" }; resourceLabels["framework.v1/control"] = new string[] { "c-3", "c-4" };
Assert.False(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); Assert.False(filter.Match(GetResource("rule", null, ResourceLabels.FromHashtable(resourceLabels))));
resourceLabels["framework.v1/control"] = System.Array.Empty<string>(); resourceLabels["framework.v1/control"] = Array.Empty<string>();
Assert.False(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); 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
} }

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

@ -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<ResourceId>();
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();
}

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

@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
global using Moq;
global using Xunit; global using Xunit;
global using Assert = Xunit.Assert; global using Assert = Xunit.Assert;
global using Moq;