switch hashFiles to extension function (#362)

This commit is contained in:
eric sciple
2020-03-18 12:08:51 -04:00
committed by GitHub
parent b0a71481f0
commit dfaf6e06ee
36 changed files with 826 additions and 659 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
@@ -22,10 +23,27 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
{
var context = definition[i].Value.AssertSequence($"{TemplateConstants.Context}");
definition.RemoveAt(i);
Context = context
.Select(x => x.AssertString($"{TemplateConstants.Context} item").Value)
.Distinct()
.ToArray();
var readerContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
var evaluatorContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
foreach (TemplateToken item in context)
{
var itemStr = item.AssertString($"{TemplateConstants.Context} item").Value;
readerContext.Add(itemStr);
// Remove min/max parameter info
var paramIndex = itemStr.IndexOf('(');
if (paramIndex > 0)
{
evaluatorContext.Add(String.Concat(itemStr.Substring(0, paramIndex + 1), ")"));
}
else
{
evaluatorContext.Add(itemStr);
}
}
ReaderContext = readerContext.ToArray();
EvaluatorContext = evaluatorContext.ToArray();
}
else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal))
{
@@ -40,7 +58,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
internal abstract DefinitionType DefinitionType { get; }
internal String[] Context { get; private set; } = new String[0];
/// <summary>
/// Used by the template reader to determine allowed expression values and functions.
/// Also used by the template reader to validate function min/max parameters.
/// </summary>
internal String[] ReaderContext { get; private set; } = new String[0];
/// <summary>
/// Used by the template evaluator to determine allowed expression values and functions.
/// The min/max parameter info is omitted.
/// </summary>
internal String[] EvaluatorContext { get; private set; } = new String[0];
internal abstract void Validate(
TemplateSchema schema,

View File

@@ -108,7 +108,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
{
var inherited = schema.GetDefinition(Inherits);
if (inherited.Context.Length > 0)
if (inherited.ReaderContext.Length > 0)
{
throw new NotSupportedException($"Property '{TemplateConstants.Context}' is not supported on inhertied definitions");
}

View File

@@ -62,7 +62,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
{
var nestedDefinition = schema.GetDefinition(nestedType);
if (nestedDefinition.Context.Length > 0)
if (nestedDefinition.ReaderContext.Length > 0)
{
throw new ArgumentException($"'{name}' is a one-of definition and references another definition that defines context. This is currently not supported.");
}

View File

@@ -47,7 +47,16 @@ namespace GitHub.DistributedTask.ObjectTemplating
var evaluator = new TemplateEvaluator(context, template, removeBytes);
try
{
var availableContext = new HashSet<String>(context.ExpressionValues.Keys.Concat(context.ExpressionFunctions.Select(x => $"{x.Name}({x.MinParameters},{x.MaxParameters})")));
var availableContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
foreach (var key in context.ExpressionValues.Keys)
{
availableContext.Add(key);
}
foreach (var function in context.ExpressionFunctions)
{
availableContext.Add($"{function.Name}()");
}
var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext);
result = evaluator.Evaluate(definitionInfo);
@@ -393,14 +402,13 @@ namespace GitHub.DistributedTask.ObjectTemplating
Definition = m_schema.GetDefinition(name);
// Determine whether to expand
if (Definition.Context.Length > 0)
m_allowedContext = Definition.EvaluatorContext;
if (Definition.EvaluatorContext.Length > 0)
{
m_allowedContext = Definition.Context;
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
}
else
{
m_allowedContext = new String[0];
Expand = false;
}
}
@@ -416,9 +424,9 @@ namespace GitHub.DistributedTask.ObjectTemplating
Definition = m_schema.GetDefinition(name);
// Determine whether to expand
if (Definition.Context.Length > 0)
if (Definition.EvaluatorContext.Length > 0)
{
m_allowedContext = new HashSet<String>(parent.m_allowedContext.Concat(Definition.Context)).ToArray();
m_allowedContext = new HashSet<String>(parent.m_allowedContext.Concat(Definition.EvaluatorContext), StringComparer.OrdinalIgnoreCase).ToArray();
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
}
else

View File

@@ -49,6 +49,14 @@ namespace GitHub.DistributedTask.ObjectTemplating
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
}
public TemplateValidationException(
String message,
IEnumerable<TemplateValidationError> errors)
: this(message)
{
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
}
public TemplateValidationException(String message)
: base(message)
{

View File

@@ -780,15 +780,8 @@ namespace GitHub.DistributedTask.ObjectTemplating
// Lookup the definition
Definition = m_schema.GetDefinition(name);
// Determine whether to expand
if (Definition.Context.Length > 0)
{
AllowedContext = Definition.Context;
}
else
{
AllowedContext = new String[0];
}
// Record allowed context
AllowedContext = Definition.ReaderContext;
}
public DefinitionInfo(
@@ -800,10 +793,10 @@ namespace GitHub.DistributedTask.ObjectTemplating
// Lookup the definition
Definition = m_schema.GetDefinition(name);
// Determine whether to expand
if (Definition.Context.Length > 0)
// Record allowed context
if (Definition.ReaderContext.Length > 0)
{
AllowedContext = new HashSet<String>(parent.AllowedContext.Concat(Definition.Context)).ToArray();
AllowedContext = new HashSet<String>(parent.AllowedContext.Concat(Definition.ReaderContext), StringComparer.OrdinalIgnoreCase).ToArray();
}
else
{

View File

@@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
namespace GitHub.DistributedTask.ObjectTemplating
@@ -41,7 +42,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
{
for (int i = 0; i < 50; i++)
{
String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.Message;
String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.ToString();
Add(new TemplateValidationError(message));
if (ex.InnerException == null)
{
@@ -88,6 +89,23 @@ namespace GitHub.DistributedTask.ObjectTemplating
}
}
/// <summary>
/// Throws <c ref="TemplateValidationException" /> if any errors.
/// <param name="prefix">The error message prefix</param>
/// </summary>
public void Check(String prefix)
{
if (String.IsNullOrEmpty(prefix))
{
this.Check();
}
else if (m_errors.Count > 0)
{
var message = $"{prefix.Trim()} {String.Join(",", m_errors.Select(e => e.Message))}";
throw new TemplateValidationException(message, m_errors);
}
}
public void Clear()
{
m_errors.Clear();

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Globalization;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk;
using GitHub.Services.WebApi.Internal;
@@ -35,11 +37,29 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
String[] allowedContext,
out Exception ex)
{
// Create dummy allowed contexts
INamedValueInfo[] namedValues = null;
// Create dummy named values and functions
var namedValues = new List<INamedValueInfo>();
var functions = new List<IFunctionInfo>();
if (allowedContext?.Length > 0)
{
namedValues = allowedContext.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
foreach (var contextItem in allowedContext)
{
var match = s_function.Match(contextItem);
if (match.Success)
{
var functionName = match.Groups[1].Value;
var minParameters = Int32.Parse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture);
var maxParametersRaw = match.Groups[3].Value;
var maxParameters = String.Equals(maxParametersRaw, TemplateConstants.MaxConstant, StringComparison.Ordinal)
? Int32.MaxValue
: Int32.Parse(maxParametersRaw, NumberStyles.None, CultureInfo.InvariantCulture);
functions.Add(new FunctionInfo<DummyFunction>(functionName, minParameters, maxParameters));
}
else
{
namedValues.Add(new NamedValueInfo<ContextValueNode>(contextItem));
}
}
}
// Parse
@@ -47,7 +67,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
ExpressionNode root = null;
try
{
root = new ExpressionParser().CreateTree(expression, null, namedValues, null) as ExpressionNode;
root = new ExpressionParser().CreateTree(expression, null, namedValues, functions) as ExpressionNode;
result = true;
ex = null;
@@ -60,5 +80,18 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
return result;
}
private sealed class DummyFunction : Function
{
protected override Object EvaluateCore(
EvaluationContext context,
out ResultMemory resultMemory)
{
resultMemory = null;
return null;
}
}
private static readonly Regex s_function = new Regex(@"^([a-zA-Z0-9_]+)\(([0-9]+),([0-9]+|MAX)\)$", RegexOptions.Compiled);
}
}

View File

@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Expressions2.Sdk;
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
{
@@ -106,6 +109,43 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
throw new ArgumentException($"Error while reading '{objectDescription}'. Unexpected value '{literal.ToString()}'");
}
/// <summary>
/// Traverses the token and checks whether all required expression values
/// and functions are provided.
/// </summary>
public static bool CheckHasRequiredContext(
this TemplateToken token,
IReadOnlyObject expressionValues,
IList<IFunctionInfo> expressionFunctions)
{
var expressionTokens = token.Traverse()
.OfType<BasicExpressionToken>()
.ToArray();
var parser = new ExpressionParser();
foreach (var expressionToken in expressionTokens)
{
var tree = parser.ValidateSyntax(expressionToken.Expression, null);
foreach (var node in tree.Traverse())
{
if (node is NamedValue namedValue)
{
if (expressionValues?.Keys.Any(x => string.Equals(x, namedValue.Name, StringComparison.OrdinalIgnoreCase)) != true)
{
return false;
}
}
else if (node is Function function &&
!ExpressionConstants.WellKnownFunctions.ContainsKey(function.Name) &&
expressionFunctions?.Any(x => string.Equals(x.Name, function.Name, StringComparison.OrdinalIgnoreCase)) != true)
{
return false;
}
}
}
return true;
}
/// <summary>
/// Returns all tokens (depth first)
/// </summary>