mirror of
https://github.com/actions/runner.git
synced 2025-12-13 00:36:29 +00:00
GitHub Actions Runner
This commit is contained in:
433
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs
Normal file
433
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs
Normal file
@@ -0,0 +1,433 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Expands expression tokens where the allowed context is available now. The allowed context is defined
|
||||
/// within the schema. The available context is based on the ExpressionValues registered in the TemplateContext.
|
||||
/// </summary>
|
||||
internal partial class TemplateEvaluator
|
||||
{
|
||||
private TemplateEvaluator(
|
||||
TemplateContext context,
|
||||
TemplateToken template,
|
||||
Int32 removeBytes)
|
||||
{
|
||||
m_context = context;
|
||||
m_schema = context.Schema;
|
||||
m_unraveler = new TemplateUnraveler(context, template, removeBytes);
|
||||
}
|
||||
|
||||
internal static TemplateToken Evaluate(
|
||||
TemplateContext context,
|
||||
String type,
|
||||
TemplateToken template,
|
||||
Int32 removeBytes,
|
||||
Int32? fileId,
|
||||
Boolean omitHeader = false)
|
||||
{
|
||||
TemplateToken result;
|
||||
|
||||
if (!omitHeader)
|
||||
{
|
||||
if (fileId != null)
|
||||
{
|
||||
context.TraceWriter.Info("{0}", $"Begin evaluating template '{context.GetFileName(fileId.Value)}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.TraceWriter.Info("{0}", "Begin evaluating template");
|
||||
}
|
||||
}
|
||||
|
||||
var evaluator = new TemplateEvaluator(context, template, removeBytes);
|
||||
try
|
||||
{
|
||||
var availableContext = new HashSet<String>(context.ExpressionValues.Keys);
|
||||
var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext);
|
||||
result = evaluator.Evaluate(definitionInfo);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
evaluator.m_unraveler.ReadEnd();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Error(fileId, null, null, ex);
|
||||
result = null;
|
||||
}
|
||||
|
||||
if (!omitHeader)
|
||||
{
|
||||
if (fileId != null)
|
||||
{
|
||||
context.TraceWriter.Info("{0}", $"Finished evaluating template '{context.GetFileName(fileId.Value)}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.TraceWriter.Info("{0}", "Finished evaluating template");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TemplateToken Evaluate(DefinitionInfo definition)
|
||||
{
|
||||
// Scalar
|
||||
if (m_unraveler.AllowScalar(definition.Expand, out ScalarToken scalar))
|
||||
{
|
||||
if (scalar is LiteralToken literal)
|
||||
{
|
||||
Validate(ref literal, definition);
|
||||
return literal;
|
||||
}
|
||||
else
|
||||
{
|
||||
return scalar;
|
||||
}
|
||||
}
|
||||
|
||||
// Sequence start
|
||||
if (m_unraveler.AllowSequenceStart(definition.Expand, out SequenceToken sequence))
|
||||
{
|
||||
var sequenceDefinition = definition.Get<SequenceDefinition>().FirstOrDefault();
|
||||
|
||||
// Legal
|
||||
if (sequenceDefinition != null)
|
||||
{
|
||||
var itemDefinition = new DefinitionInfo(definition, sequenceDefinition.ItemType);
|
||||
|
||||
// Add each item
|
||||
while (!m_unraveler.AllowSequenceEnd(definition.Expand))
|
||||
{
|
||||
var item = Evaluate(itemDefinition);
|
||||
sequence.Add(item);
|
||||
}
|
||||
}
|
||||
// Illegal
|
||||
else
|
||||
{
|
||||
// Error
|
||||
m_context.Error(sequence, TemplateStrings.UnexpectedSequenceStart());
|
||||
|
||||
// Skip each item
|
||||
while (!m_unraveler.AllowSequenceEnd(expand: false))
|
||||
{
|
||||
m_unraveler.SkipSequenceItem();
|
||||
}
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
// Mapping
|
||||
if (m_unraveler.AllowMappingStart(definition.Expand, out MappingToken mapping))
|
||||
{
|
||||
var mappingDefinitions = definition.Get<MappingDefinition>().ToList();
|
||||
|
||||
// Legal
|
||||
if (mappingDefinitions.Count > 0)
|
||||
{
|
||||
if (mappingDefinitions.Count > 1 ||
|
||||
m_schema.HasProperties(mappingDefinitions[0]) ||
|
||||
String.IsNullOrEmpty(mappingDefinitions[0].LooseKeyType))
|
||||
{
|
||||
HandleMappingWithWellKnownProperties(definition, mappingDefinitions, mapping);
|
||||
}
|
||||
else
|
||||
{
|
||||
var keyDefinition = new DefinitionInfo(definition, mappingDefinitions[0].LooseKeyType);
|
||||
var valueDefinition = new DefinitionInfo(definition, mappingDefinitions[0].LooseValueType);
|
||||
HandleMappingWithAllLooseProperties(definition, keyDefinition, valueDefinition, mapping);
|
||||
}
|
||||
}
|
||||
// Illegal
|
||||
else
|
||||
{
|
||||
m_context.Error(mapping, TemplateStrings.UnexpectedMappingStart());
|
||||
|
||||
while (!m_unraveler.AllowMappingEnd(expand: false))
|
||||
{
|
||||
m_unraveler.SkipMappingKey();
|
||||
m_unraveler.SkipMappingValue();
|
||||
}
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
throw new ArgumentException(TemplateStrings.ExpectedScalarSequenceOrMapping());
|
||||
}
|
||||
|
||||
private void HandleMappingWithWellKnownProperties(
|
||||
DefinitionInfo definition,
|
||||
List<MappingDefinition> mappingDefinitions,
|
||||
MappingToken mapping)
|
||||
{
|
||||
// Check if loose properties are allowed
|
||||
String looseKeyType = null;
|
||||
String looseValueType = null;
|
||||
DefinitionInfo? looseKeyDefinition = null;
|
||||
DefinitionInfo? looseValueDefinition = null;
|
||||
if (!String.IsNullOrEmpty(mappingDefinitions[0].LooseKeyType))
|
||||
{
|
||||
looseKeyType = mappingDefinitions[0].LooseKeyType;
|
||||
looseValueType = mappingDefinitions[0].LooseValueType;
|
||||
}
|
||||
|
||||
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
while (m_unraveler.AllowScalar(definition.Expand, out ScalarToken nextKeyScalar))
|
||||
{
|
||||
// Expression
|
||||
if (nextKeyScalar is ExpressionToken)
|
||||
{
|
||||
var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any);
|
||||
mapping.Add(nextKeyScalar, Evaluate(anyDefinition));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not a string, convert
|
||||
if (!(nextKeyScalar is StringToken nextKey))
|
||||
{
|
||||
nextKey = new StringToken(nextKeyScalar.FileId, nextKeyScalar.Line, nextKeyScalar.Column, nextKeyScalar.ToString());
|
||||
}
|
||||
|
||||
// Duplicate
|
||||
if (!keys.Add(nextKey.Value))
|
||||
{
|
||||
m_context.Error(nextKey, TemplateStrings.ValueAlreadyDefined(nextKey.Value));
|
||||
m_unraveler.SkipMappingValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Well known
|
||||
if (m_schema.TryMatchKey(mappingDefinitions, nextKey.Value, out String nextValueType))
|
||||
{
|
||||
var nextValueDefinition = new DefinitionInfo(definition, nextValueType);
|
||||
var nextValue = Evaluate(nextValueDefinition);
|
||||
mapping.Add(nextKey, nextValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Loose
|
||||
if (looseKeyType != null)
|
||||
{
|
||||
if (looseKeyDefinition == null)
|
||||
{
|
||||
looseKeyDefinition = new DefinitionInfo(definition, looseKeyType);
|
||||
looseValueDefinition = new DefinitionInfo(definition, looseValueType);
|
||||
}
|
||||
|
||||
Validate(nextKey, looseKeyDefinition.Value);
|
||||
var nextValue = Evaluate(looseValueDefinition.Value);
|
||||
mapping.Add(nextKey, nextValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Error
|
||||
m_context.Error(nextKey, TemplateStrings.UnexpectedValue(nextKey.Value));
|
||||
m_unraveler.SkipMappingValue();
|
||||
}
|
||||
|
||||
// Only one
|
||||
if (mappingDefinitions.Count > 1)
|
||||
{
|
||||
var hitCount = new Dictionary<String, Int32>();
|
||||
foreach (MappingDefinition mapdef in mappingDefinitions)
|
||||
{
|
||||
foreach (String key in mapdef.Properties.Keys)
|
||||
{
|
||||
if (!hitCount.TryGetValue(key, out Int32 value))
|
||||
{
|
||||
hitCount.Add(key, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
hitCount[key] = value + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<String> nonDuplicates = new List<String>();
|
||||
foreach (String key in hitCount.Keys)
|
||||
{
|
||||
if (hitCount[key] == 1)
|
||||
{
|
||||
nonDuplicates.Add(key);
|
||||
}
|
||||
}
|
||||
nonDuplicates.Sort();
|
||||
|
||||
String listToDeDuplicate = String.Join(", ", nonDuplicates);
|
||||
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
|
||||
}
|
||||
|
||||
m_unraveler.ReadMappingEnd();
|
||||
}
|
||||
|
||||
private void HandleMappingWithAllLooseProperties(
|
||||
DefinitionInfo mappingDefinition,
|
||||
DefinitionInfo keyDefinition,
|
||||
DefinitionInfo valueDefinition,
|
||||
MappingToken mapping)
|
||||
{
|
||||
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
while (m_unraveler.AllowScalar(mappingDefinition.Expand, out ScalarToken nextKeyScalar))
|
||||
{
|
||||
// Expression
|
||||
if (nextKeyScalar is ExpressionToken)
|
||||
{
|
||||
if (nextKeyScalar is BasicExpressionToken)
|
||||
{
|
||||
mapping.Add(nextKeyScalar, Evaluate(valueDefinition));
|
||||
}
|
||||
else
|
||||
{
|
||||
var anyDefinition = new DefinitionInfo(mappingDefinition, TemplateConstants.Any);
|
||||
mapping.Add(nextKeyScalar, Evaluate(anyDefinition));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not a string
|
||||
if (!(nextKeyScalar is StringToken nextKey))
|
||||
{
|
||||
nextKey = new StringToken(nextKeyScalar.FileId, nextKeyScalar.Line, nextKeyScalar.Column, nextKeyScalar.ToString());
|
||||
}
|
||||
|
||||
// Duplicate
|
||||
if (!keys.Add(nextKey.Value))
|
||||
{
|
||||
m_context.Error(nextKey, TemplateStrings.ValueAlreadyDefined(nextKey.Value));
|
||||
m_unraveler.SkipMappingValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate
|
||||
Validate(nextKey, keyDefinition);
|
||||
|
||||
// Add the pair
|
||||
var nextValue = Evaluate(valueDefinition);
|
||||
mapping.Add(nextKey, nextValue);
|
||||
}
|
||||
|
||||
m_unraveler.ReadMappingEnd();
|
||||
}
|
||||
|
||||
private void Validate(
|
||||
StringToken stringToken,
|
||||
DefinitionInfo definition)
|
||||
{
|
||||
var literal = stringToken as LiteralToken;
|
||||
Validate(ref literal, definition);
|
||||
}
|
||||
|
||||
private void Validate(
|
||||
ref LiteralToken literal,
|
||||
DefinitionInfo definition)
|
||||
{
|
||||
// Legal
|
||||
var literal2 = literal;
|
||||
if (definition.Get<ScalarDefinition>().Any(x => x.IsMatch(literal2)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a string, convert
|
||||
if (literal.Type != TokenType.String)
|
||||
{
|
||||
var stringToken = new StringToken(literal.FileId, literal.Line, literal.Column, literal.ToString());
|
||||
|
||||
// Legal
|
||||
if (definition.Get<StringDefinition>().Any(x => x.IsMatch(stringToken)))
|
||||
{
|
||||
literal = stringToken;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Illegal
|
||||
m_context.Error(literal, TemplateStrings.UnexpectedValue(literal));
|
||||
}
|
||||
|
||||
private void ValidateEnd()
|
||||
{
|
||||
m_unraveler.ReadEnd();
|
||||
}
|
||||
|
||||
private struct DefinitionInfo
|
||||
{
|
||||
public DefinitionInfo(
|
||||
TemplateSchema schema,
|
||||
String name,
|
||||
HashSet<String> availableContext)
|
||||
{
|
||||
m_schema = schema;
|
||||
m_availableContext = availableContext;
|
||||
|
||||
// Lookup the definition
|
||||
Definition = m_schema.GetDefinition(name);
|
||||
|
||||
// Determine whether to expand
|
||||
if (Definition.Context.Length > 0)
|
||||
{
|
||||
m_allowedContext = Definition.Context;
|
||||
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_allowedContext = new String[0];
|
||||
Expand = false;
|
||||
}
|
||||
}
|
||||
|
||||
public DefinitionInfo(
|
||||
DefinitionInfo parent,
|
||||
String name)
|
||||
{
|
||||
m_schema = parent.m_schema;
|
||||
m_availableContext = parent.m_availableContext;
|
||||
|
||||
// Lookup the definition
|
||||
Definition = m_schema.GetDefinition(name);
|
||||
|
||||
// Determine whether to expand
|
||||
if (Definition.Context.Length > 0)
|
||||
{
|
||||
m_allowedContext = new HashSet<String>(parent.m_allowedContext.Concat(Definition.Context)).ToArray();
|
||||
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_allowedContext = parent.m_allowedContext;
|
||||
Expand = parent.Expand;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> Get<T>()
|
||||
where T : Definition
|
||||
{
|
||||
return m_schema.Get<T>(Definition);
|
||||
}
|
||||
|
||||
private HashSet<String> m_availableContext;
|
||||
private String[] m_allowedContext;
|
||||
private TemplateSchema m_schema;
|
||||
public Definition Definition;
|
||||
public Boolean Expand;
|
||||
}
|
||||
|
||||
private readonly TemplateContext m_context;
|
||||
private readonly TemplateSchema m_schema;
|
||||
private readonly TemplateUnraveler m_unraveler;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user