mirror of
https://github.com/actions/runner.git
synced 2025-12-16 06:57:25 +00:00
GitHub Actions Runner
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// This expression node retrieves a user-defined named-value. This is used during expression evaluation.
|
||||
/// </summary>
|
||||
internal sealed class ContextValueNode : NamedValue
|
||||
{
|
||||
protected override Object EvaluateCore(
|
||||
EvaluationContext context,
|
||||
out ResultMemory resultMemory)
|
||||
{
|
||||
resultMemory = null;
|
||||
return (context.State as TemplateContext).ExpressionValues[Name];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class EmptyTraceWriter : ITraceWriter
|
||||
{
|
||||
public void Error(
|
||||
String format,
|
||||
params Object[] args)
|
||||
{
|
||||
}
|
||||
|
||||
public void Info(
|
||||
String format,
|
||||
params Object[] args)
|
||||
{
|
||||
}
|
||||
|
||||
public void Verbose(
|
||||
String format,
|
||||
params Object[] args)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps an ITraceWriter so it can be passed for expression evaluation.
|
||||
/// </summary>
|
||||
internal sealed class ExpressionTraceWriter : DistributedTask.Expressions2.ITraceWriter
|
||||
{
|
||||
public ExpressionTraceWriter(ITraceWriter trace)
|
||||
{
|
||||
m_trace = trace;
|
||||
}
|
||||
|
||||
public void Info(String message)
|
||||
{
|
||||
m_trace.Info("{0}", message);
|
||||
}
|
||||
|
||||
public void Verbose(String message)
|
||||
{
|
||||
m_trace.Verbose("{0}", message);
|
||||
}
|
||||
|
||||
private readonly ITraceWriter m_trace;
|
||||
}
|
||||
}
|
||||
26
src/Sdk/DTObjectTemplating/ObjectTemplating/IObjectReader.cs
Normal file
26
src/Sdk/DTObjectTemplating/ObjectTemplating/IObjectReader.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for reading a source object (or file).
|
||||
/// This interface is used by TemplateReader to build a TemplateToken DOM.
|
||||
/// </summary>
|
||||
internal interface IObjectReader
|
||||
{
|
||||
Boolean AllowLiteral(out LiteralToken token);
|
||||
|
||||
Boolean AllowSequenceStart(out SequenceToken token);
|
||||
|
||||
Boolean AllowSequenceEnd();
|
||||
|
||||
Boolean AllowMappingStart(out MappingToken token);
|
||||
|
||||
Boolean AllowMappingEnd();
|
||||
|
||||
void ValidateStart();
|
||||
|
||||
void ValidateEnd();
|
||||
}
|
||||
}
|
||||
31
src/Sdk/DTObjectTemplating/ObjectTemplating/IObjectWriter.cs
Normal file
31
src/Sdk/DTObjectTemplating/ObjectTemplating/IObjectWriter.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for building an object. This interface is used by
|
||||
/// TemplateWriter to convert a TemplateToken DOM to another format.
|
||||
/// </summary>
|
||||
internal interface IObjectWriter
|
||||
{
|
||||
void WriteNull();
|
||||
|
||||
void WriteBoolean(Boolean value);
|
||||
|
||||
void WriteNumber(Double value);
|
||||
|
||||
void WriteString(String value);
|
||||
|
||||
void WriteSequenceStart();
|
||||
|
||||
void WriteSequenceEnd();
|
||||
|
||||
void WriteMappingStart();
|
||||
|
||||
void WriteMappingEnd();
|
||||
|
||||
void WriteStart();
|
||||
|
||||
void WriteEnd();
|
||||
}
|
||||
}
|
||||
21
src/Sdk/DTObjectTemplating/ObjectTemplating/ITraceWriter.cs
Normal file
21
src/Sdk/DTObjectTemplating/ObjectTemplating/ITraceWriter.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public interface ITraceWriter
|
||||
{
|
||||
void Error(
|
||||
String format,
|
||||
params Object[] args);
|
||||
|
||||
void Info(
|
||||
String format,
|
||||
params Object[] args);
|
||||
|
||||
void Verbose(
|
||||
String format,
|
||||
params Object[] args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
internal static class ITraceWriterExtensions
|
||||
{
|
||||
internal static DistributedTask.Expressions2.ITraceWriter ToExpressionTraceWriter(this ITraceWriter trace)
|
||||
{
|
||||
return new ExpressionTraceWriter(trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class BooleanDefinition : ScalarDefinition
|
||||
{
|
||||
internal BooleanDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal BooleanDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Boolean:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Boolean}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Boolean} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.Boolean} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Boolean;
|
||||
|
||||
internal override Boolean IsMatch(LiteralToken literal)
|
||||
{
|
||||
return literal is BooleanToken;
|
||||
}
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the allowable schema for a user defined type
|
||||
/// </summary>
|
||||
internal abstract class Definition
|
||||
{
|
||||
protected Definition()
|
||||
{
|
||||
}
|
||||
|
||||
protected Definition(MappingToken definition)
|
||||
{
|
||||
for (var i = 0; i < definition.Count; )
|
||||
{
|
||||
var definitionKey = definition[i].Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
if (String.Equals(definitionKey.Value, TemplateConstants.Context, StringComparison.Ordinal))
|
||||
{
|
||||
var context = definition[i].Value.AssertSequence($"{TemplateConstants.Context}");
|
||||
definition.RemoveAt(i);
|
||||
Context = context
|
||||
.Select(x => x.AssertString($"{TemplateConstants.Context} item").Value)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
}
|
||||
else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal))
|
||||
{
|
||||
definition.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract DefinitionType DefinitionType { get; }
|
||||
|
||||
internal String[] Context { get; private set; } = new String[0];
|
||||
|
||||
internal abstract void Validate(
|
||||
TemplateSchema schema,
|
||||
String name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
internal enum DefinitionType
|
||||
{
|
||||
Null,
|
||||
Boolean,
|
||||
Number,
|
||||
String,
|
||||
Sequence,
|
||||
Mapping,
|
||||
OneOf,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class MappingDefinition : Definition
|
||||
{
|
||||
internal MappingDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal MappingDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Mapping:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Mapping}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
case TemplateConstants.Properties:
|
||||
var properties = mappingPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties}");
|
||||
foreach (var propertiesPair in properties)
|
||||
{
|
||||
var propertyName = propertiesPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} key");
|
||||
var propertyValue = propertiesPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} value");
|
||||
Properties.Add(propertyName.Value, new PropertyValue(propertyValue.Value));
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateConstants.LooseKeyType:
|
||||
var looseKeyType = mappingPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.LooseKeyType}");
|
||||
LooseKeyType = looseKeyType.Value;
|
||||
break;
|
||||
|
||||
case TemplateConstants.LooseValueType:
|
||||
var looseValueType = mappingPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.LooseValueType}");
|
||||
LooseValueType = looseValueType.Value;
|
||||
break;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Mapping;
|
||||
|
||||
internal String Inherits { get; set; }
|
||||
|
||||
internal String LooseKeyType { get; set; }
|
||||
|
||||
internal String LooseValueType { get; set; }
|
||||
|
||||
internal Dictionary<String, PropertyValue> Properties { get; } = new Dictionary<String, PropertyValue>(StringComparer.Ordinal);
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
// Lookup loose key type
|
||||
if (!String.IsNullOrEmpty(LooseKeyType))
|
||||
{
|
||||
schema.GetDefinition(LooseKeyType);
|
||||
|
||||
// Lookup loose value type
|
||||
if (!String.IsNullOrEmpty(LooseValueType))
|
||||
{
|
||||
schema.GetDefinition(LooseValueType);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Property '{TemplateConstants.LooseKeyType}' is defined but '{TemplateConstants.LooseValueType}' is not defined");
|
||||
}
|
||||
}
|
||||
// Otherwise validate loose value type not be defined
|
||||
else if (!String.IsNullOrEmpty(LooseValueType))
|
||||
{
|
||||
throw new ArgumentException($"Property '{TemplateConstants.LooseValueType}' is defined but '{TemplateConstants.LooseKeyType}' is not defined");
|
||||
}
|
||||
|
||||
// Lookup each property
|
||||
foreach (var property in Properties.Values)
|
||||
{
|
||||
schema.GetDefinition(property.Type);
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(Inherits))
|
||||
{
|
||||
var inherited = schema.GetDefinition(Inherits);
|
||||
|
||||
if (inherited.Context.Length > 0)
|
||||
{
|
||||
throw new NotSupportedException($"Property '{TemplateConstants.Context}' is not supported on inhertied definitions");
|
||||
}
|
||||
|
||||
if (inherited.DefinitionType != DefinitionType.Mapping)
|
||||
{
|
||||
throw new NotSupportedException($"Expected structure of inherited definition to match. Actual '{inherited.DefinitionType}'");
|
||||
}
|
||||
|
||||
var inheritedMapping = inherited as MappingDefinition;
|
||||
|
||||
if (!String.IsNullOrEmpty(inheritedMapping.Inherits))
|
||||
{
|
||||
throw new NotSupportedException($"Property '{TemplateConstants.Inherits}' is not supported on inherited definition");
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(inheritedMapping.LooseKeyType))
|
||||
{
|
||||
throw new NotSupportedException($"Property '{TemplateConstants.LooseKeyType}' is not supported on inherited definition");
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(inheritedMapping.LooseValueType))
|
||||
{
|
||||
throw new NotSupportedException($"Property '{TemplateConstants.LooseValueType}' is not supported on inherited definition");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class NullDefinition : ScalarDefinition
|
||||
{
|
||||
internal NullDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal NullDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Null:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Null}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Null} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.Null} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Null;
|
||||
|
||||
internal override Boolean IsMatch(LiteralToken literal)
|
||||
{
|
||||
return literal is NullToken;
|
||||
}
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class NumberDefinition : ScalarDefinition
|
||||
{
|
||||
internal NumberDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal NumberDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Number:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Number}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Number} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.Number} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Number;
|
||||
|
||||
internal override Boolean IsMatch(LiteralToken literal)
|
||||
{
|
||||
return literal is NumberToken;
|
||||
}
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
/// <summary>
|
||||
/// Must resolve to exactly one of the referenced definitions
|
||||
/// </summary>
|
||||
internal sealed class OneOfDefinition : Definition
|
||||
{
|
||||
internal OneOfDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal OneOfDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.OneOf:
|
||||
var oneOf = definitionPair.Value.AssertSequence(TemplateConstants.OneOf);
|
||||
foreach (var oneOfItem in oneOf)
|
||||
{
|
||||
var reference = oneOfItem.AssertString(TemplateConstants.OneOf);
|
||||
OneOf.Add(reference.Value);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Mapping;
|
||||
|
||||
internal List<String> OneOf { get; } = new List<String>();
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
if (OneOf.Count == 0)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' does not contain any references");
|
||||
}
|
||||
|
||||
var foundLooseKeyType = false;
|
||||
var mappingDefinitions = default(List<MappingDefinition>);
|
||||
var sequenceDefinition = default(SequenceDefinition);
|
||||
var nullDefinition = default(NullDefinition);
|
||||
var booleanDefinition = default(BooleanDefinition);
|
||||
var numberDefinition = default(NumberDefinition);
|
||||
var stringDefinitions = default(List<StringDefinition>);
|
||||
|
||||
foreach (var nestedType in OneOf)
|
||||
{
|
||||
var nestedDefinition = schema.GetDefinition(nestedType);
|
||||
|
||||
if (nestedDefinition.Context.Length > 0)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' is a one-of definition and references another definition that defines context. This is currently not supported.");
|
||||
}
|
||||
|
||||
if (nestedDefinition is MappingDefinition mappingDefinition)
|
||||
{
|
||||
if (mappingDefinitions == null)
|
||||
{
|
||||
mappingDefinitions = new List<MappingDefinition>();
|
||||
}
|
||||
|
||||
mappingDefinitions.Add(mappingDefinition);
|
||||
|
||||
if (!String.IsNullOrEmpty(mappingDefinition.LooseKeyType))
|
||||
{
|
||||
foundLooseKeyType = true;
|
||||
}
|
||||
}
|
||||
else if (nestedDefinition is SequenceDefinition s)
|
||||
{
|
||||
// Multiple sequence definitions not allowed
|
||||
if (sequenceDefinition != null)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Sequence}'");
|
||||
}
|
||||
|
||||
sequenceDefinition = s;
|
||||
}
|
||||
else if (nestedDefinition is NullDefinition n)
|
||||
{
|
||||
// Multiple sequence definitions not allowed
|
||||
if (nullDefinition != null)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Null}'");
|
||||
}
|
||||
|
||||
nullDefinition = n;
|
||||
}
|
||||
else if (nestedDefinition is BooleanDefinition b)
|
||||
{
|
||||
// Multiple boolean definitions not allowed
|
||||
if (booleanDefinition != null)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Boolean}'");
|
||||
}
|
||||
|
||||
booleanDefinition = b;
|
||||
}
|
||||
else if (nestedDefinition is NumberDefinition num)
|
||||
{
|
||||
// Multiple number definitions not allowed
|
||||
if (numberDefinition != null)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Number}'");
|
||||
}
|
||||
|
||||
numberDefinition = num;
|
||||
}
|
||||
else if (nestedDefinition is StringDefinition stringDefinition)
|
||||
{
|
||||
// First string definition
|
||||
if (stringDefinitions == null)
|
||||
{
|
||||
stringDefinitions = new List<StringDefinition>();
|
||||
}
|
||||
// Multiple string definitions, all must be 'Constant'
|
||||
else if ((stringDefinitions.Count == 1 && String.IsNullOrEmpty(stringDefinitions[0].Constant))
|
||||
|| String.IsNullOrEmpty(stringDefinition.Constant))
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to more than one '{TemplateConstants.Scalar}', but some do not set '{TemplateConstants.Constant}'");
|
||||
}
|
||||
|
||||
stringDefinitions.Add(stringDefinition);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to a '{nestedDefinition.DefinitionType}' definition");
|
||||
}
|
||||
}
|
||||
|
||||
if (mappingDefinitions?.Count > 1)
|
||||
{
|
||||
if (foundLooseKeyType)
|
||||
{
|
||||
throw new ArgumentException($"'{name}' refers to two mappings that both set '{TemplateConstants.LooseKeyType}'");
|
||||
}
|
||||
|
||||
var seenProperties = new Dictionary<String, PropertyValue>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var mappingDefinition in mappingDefinitions)
|
||||
{
|
||||
foreach (var newProperty in GetMergedProperties(schema, mappingDefinition))
|
||||
{
|
||||
// Already seen
|
||||
if (seenProperties.TryGetValue(newProperty.Key, out PropertyValue existingProperty))
|
||||
{
|
||||
// Types match
|
||||
if (String.Equals(existingProperty.Type, newProperty.Value.Type, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collision
|
||||
throw new ArgumentException($"'{name}' contains two mappings with the same property, but each refers to a different type. All matching properties must refer to the same type.");
|
||||
}
|
||||
// New
|
||||
else
|
||||
{
|
||||
seenProperties.Add(newProperty.Key, newProperty.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<KeyValuePair<String, PropertyValue>> GetMergedProperties(
|
||||
TemplateSchema schema,
|
||||
MappingDefinition mapping)
|
||||
{
|
||||
foreach (var property in mapping.Properties)
|
||||
{
|
||||
yield return property;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(mapping.Inherits))
|
||||
{
|
||||
var inherited = schema.GetDefinition(mapping.Inherits) as MappingDefinition;
|
||||
|
||||
if (!String.IsNullOrEmpty(inherited.Inherits))
|
||||
{
|
||||
throw new NotSupportedException("Multiple levels of inheritance is not supported");
|
||||
}
|
||||
|
||||
foreach (var property in inherited.Properties)
|
||||
{
|
||||
if (!mapping.Properties.ContainsKey(property.Key))
|
||||
{
|
||||
yield return property;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class PropertyValue
|
||||
{
|
||||
internal PropertyValue()
|
||||
{
|
||||
}
|
||||
|
||||
internal PropertyValue(String type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
|
||||
internal String Type { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
internal abstract class ScalarDefinition : Definition
|
||||
{
|
||||
internal ScalarDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal ScalarDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
}
|
||||
|
||||
internal abstract Boolean IsMatch(LiteralToken literal);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class SequenceDefinition : Definition
|
||||
{
|
||||
internal SequenceDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal SequenceDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Sequence:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.Sequence}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Sequence} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
case TemplateConstants.ItemType:
|
||||
var itemType = mappingPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Sequence} {TemplateConstants.ItemType}");
|
||||
ItemType = itemType.Value;
|
||||
break;
|
||||
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.Sequence} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.Sequence;
|
||||
|
||||
internal String ItemType { get; set; }
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
if (String.IsNullOrEmpty(ItemType))
|
||||
{
|
||||
throw new ArgumentException($"'{name}' does not define '{TemplateConstants.ItemType}'");
|
||||
}
|
||||
|
||||
// Lookup item type
|
||||
schema.GetDefinition(ItemType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
internal sealed class StringDefinition : ScalarDefinition
|
||||
{
|
||||
internal StringDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal StringDefinition(MappingToken definition)
|
||||
: base(definition)
|
||||
{
|
||||
foreach (var definitionPair in definition)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.String:
|
||||
var mapping = definitionPair.Value.AssertMapping($"{TemplateConstants.Definition} {TemplateConstants.String}");
|
||||
foreach (var mappingPair in mapping)
|
||||
{
|
||||
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.String} key");
|
||||
switch (mappingKey.Value)
|
||||
{
|
||||
case TemplateConstants.Constant:
|
||||
var constantStringToken = mappingPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.String} {TemplateConstants.Constant}");
|
||||
Constant = constantStringToken.Value;
|
||||
break;
|
||||
|
||||
case TemplateConstants.IgnoreCase:
|
||||
var ignoreCaseBooleanToken = mappingPair.Value.AssertBoolean($"{TemplateConstants.Definition} {TemplateConstants.String} {TemplateConstants.IgnoreCase}");
|
||||
IgnoreCase = ignoreCaseBooleanToken.Value;
|
||||
break;
|
||||
|
||||
case TemplateConstants.RequireNonEmpty:
|
||||
var requireNonEmptyBooleanToken = mappingPair.Value.AssertBoolean($"{TemplateConstants.Definition} {TemplateConstants.String} {TemplateConstants.RequireNonEmpty}");
|
||||
RequireNonEmpty = requireNonEmptyBooleanToken.Value;
|
||||
break;
|
||||
|
||||
default:
|
||||
mappingKey.AssertUnexpectedValue($"{TemplateConstants.Definition} {TemplateConstants.String} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue($"{TemplateConstants.Definition} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override DefinitionType DefinitionType => DefinitionType.String;
|
||||
|
||||
internal String Constant { get; set; }
|
||||
|
||||
internal Boolean IgnoreCase { get; set; }
|
||||
|
||||
internal Boolean RequireNonEmpty { get; set; }
|
||||
|
||||
internal override Boolean IsMatch(LiteralToken literal)
|
||||
{
|
||||
if (literal is StringToken str)
|
||||
{
|
||||
var value = str.Value;
|
||||
if (!String.IsNullOrEmpty(Constant))
|
||||
{
|
||||
var comparison = IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
|
||||
|
||||
if (String.Equals(Constant, value, comparison))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (RequireNonEmpty)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override void Validate(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(Constant) && RequireNonEmpty)
|
||||
{
|
||||
throw new ArgumentException($"Properties '{TemplateConstants.Constant}' and '{TemplateConstants.RequireNonEmpty}' cannot both be set");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||
{
|
||||
/// <summary>
|
||||
/// This models the root schema object and contains definitions
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class TemplateSchema
|
||||
{
|
||||
internal TemplateSchema()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
private TemplateSchema(MappingToken mapping)
|
||||
{
|
||||
// Add built-in type: null
|
||||
var nullDefinition = new NullDefinition();
|
||||
Definitions.Add(TemplateConstants.Null, nullDefinition);
|
||||
|
||||
// Add built-in type: boolean
|
||||
var booleanDefinition = new BooleanDefinition();
|
||||
Definitions.Add(TemplateConstants.Boolean, booleanDefinition);
|
||||
|
||||
// Add built-in type: number
|
||||
var numberDefinition = new NumberDefinition();
|
||||
Definitions.Add(TemplateConstants.Number, numberDefinition);
|
||||
|
||||
// Add built-in type: string
|
||||
var stringDefinition = new StringDefinition();
|
||||
Definitions.Add(TemplateConstants.String, stringDefinition);
|
||||
|
||||
// Add built-in type: sequence
|
||||
var sequenceDefinition = new SequenceDefinition { ItemType = TemplateConstants.Any };
|
||||
Definitions.Add(TemplateConstants.Sequence, sequenceDefinition);
|
||||
|
||||
// Add built-in type: mapping
|
||||
var mappingDefinition = new MappingDefinition { LooseKeyType = TemplateConstants.String, LooseValueType = TemplateConstants.Any };
|
||||
Definitions.Add(TemplateConstants.Mapping, mappingDefinition);
|
||||
|
||||
// Add built-in type: any
|
||||
var anyDefinition = new OneOfDefinition();
|
||||
anyDefinition.OneOf.Add(TemplateConstants.Null);
|
||||
anyDefinition.OneOf.Add(TemplateConstants.Boolean);
|
||||
anyDefinition.OneOf.Add(TemplateConstants.Number);
|
||||
anyDefinition.OneOf.Add(TemplateConstants.String);
|
||||
anyDefinition.OneOf.Add(TemplateConstants.Sequence);
|
||||
anyDefinition.OneOf.Add(TemplateConstants.Mapping);
|
||||
Definitions.Add(TemplateConstants.Any, anyDefinition);
|
||||
|
||||
if (mapping != null)
|
||||
{
|
||||
foreach (var pair in mapping)
|
||||
{
|
||||
var key = pair.Key.AssertString($"{TemplateConstants.TemplateSchema} key");
|
||||
switch (key.Value)
|
||||
{
|
||||
case TemplateConstants.Version:
|
||||
var version = pair.Value.AssertString(TemplateConstants.Version);
|
||||
Version = version.Value;
|
||||
break;
|
||||
|
||||
case TemplateConstants.Definitions:
|
||||
var definitions = pair.Value.AssertMapping(TemplateConstants.Definitions);
|
||||
foreach (var definitionsPair in definitions)
|
||||
{
|
||||
var definitionsKey = definitionsPair.Key.AssertString($"{TemplateConstants.Definitions} key");
|
||||
var definitionsValue = definitionsPair.Value.AssertMapping(TemplateConstants.Definition);
|
||||
var definition = default(Definition);
|
||||
foreach (var definitionPair in definitionsValue)
|
||||
{
|
||||
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
||||
switch (definitionKey.Value)
|
||||
{
|
||||
case TemplateConstants.Null:
|
||||
definition = new NullDefinition(definitionsValue);
|
||||
break;
|
||||
|
||||
case TemplateConstants.Boolean:
|
||||
definition = new BooleanDefinition(definitionsValue);
|
||||
break;
|
||||
|
||||
case TemplateConstants.Number:
|
||||
definition = new NumberDefinition(definitionsValue);
|
||||
break;
|
||||
|
||||
case TemplateConstants.String:
|
||||
definition = new StringDefinition(definitionsValue);
|
||||
break;
|
||||
|
||||
case TemplateConstants.Sequence:
|
||||
definition = new SequenceDefinition(definitionsValue);
|
||||
break;
|
||||
|
||||
case TemplateConstants.Mapping:
|
||||
definition = new MappingDefinition(definitionsValue);
|
||||
break;
|
||||
|
||||
case TemplateConstants.OneOf:
|
||||
definition = new OneOfDefinition(definitionsValue);
|
||||
break;
|
||||
|
||||
case TemplateConstants.Context:
|
||||
case TemplateConstants.Description:
|
||||
continue;
|
||||
|
||||
default:
|
||||
definitionKey.AssertUnexpectedValue("definition mapping key"); // throws
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (definition == null)
|
||||
{
|
||||
throw new ArgumentException($"Unable to determine definition details. Specify the '{TemplateConstants.Structure}' property");
|
||||
}
|
||||
|
||||
Definitions.Add(definitionsKey.Value, definition);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
key.AssertUnexpectedValue($"{TemplateConstants.TemplateSchema} key"); // throws
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Dictionary<String, Definition> Definitions { get; } = new Dictionary<String, Definition>(StringComparer.Ordinal);
|
||||
|
||||
internal String Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads a user's schema file
|
||||
/// </summary>
|
||||
internal static TemplateSchema Load(IObjectReader objectReader)
|
||||
{
|
||||
var context = new TemplateContext
|
||||
{
|
||||
CancellationToken = CancellationToken.None,
|
||||
Errors = new TemplateValidationErrors(maxErrors: 10, maxMessageLength: 500),
|
||||
Memory = new TemplateMemory(
|
||||
maxDepth: 50,
|
||||
maxEvents: 1000000, // 1 million
|
||||
maxBytes: 1024 * 1024), // 1 mb
|
||||
TraceWriter = new EmptyTraceWriter(),
|
||||
};
|
||||
|
||||
var value = TemplateReader.Read(context, TemplateConstants.TemplateSchema, objectReader, null, Schema, out _);
|
||||
|
||||
if (context.Errors.Count > 0)
|
||||
{
|
||||
throw new TemplateValidationException(context.Errors);
|
||||
}
|
||||
|
||||
var mapping = value.AssertMapping(TemplateConstants.TemplateSchema);
|
||||
var schema = new TemplateSchema(mapping);
|
||||
schema.Validate();
|
||||
return schema;
|
||||
}
|
||||
|
||||
internal IEnumerable<T> Get<T>(Definition definition)
|
||||
where T : Definition
|
||||
{
|
||||
if (definition is T match)
|
||||
{
|
||||
yield return match;
|
||||
}
|
||||
else if (definition is OneOfDefinition oneOf)
|
||||
{
|
||||
foreach (var reference in oneOf.OneOf)
|
||||
{
|
||||
var nestedDefinition = GetDefinition(reference);
|
||||
if (nestedDefinition is T match2)
|
||||
{
|
||||
yield return match2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Definition GetDefinition(String type)
|
||||
{
|
||||
if (Definitions.TryGetValue(type, out Definition value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Schema definition '{type}' not found");
|
||||
}
|
||||
|
||||
internal Boolean HasProperties(MappingDefinition definition)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
if (definition.Properties.Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(definition.Inherits))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
definition = GetDefinition(definition.Inherits) as MappingDefinition;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Inheritance depth exceeded 10");
|
||||
}
|
||||
|
||||
internal Boolean TryGetProperty(
|
||||
MappingDefinition definition,
|
||||
String name,
|
||||
out String type)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
if (definition.Properties.TryGetValue(name, out PropertyValue property))
|
||||
{
|
||||
type = property.Type;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(definition.Inherits))
|
||||
{
|
||||
type = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
definition = GetDefinition(definition.Inherits) as MappingDefinition;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Inheritance depth exceeded 10");
|
||||
}
|
||||
|
||||
internal Boolean TryMatchKey(
|
||||
List<MappingDefinition> definitions,
|
||||
String key,
|
||||
out String valueType)
|
||||
{
|
||||
valueType = null;
|
||||
|
||||
// Check for a matching well known property
|
||||
var notFoundInSome = false;
|
||||
for (var i = 0; i < definitions.Count; i++)
|
||||
{
|
||||
var definition = definitions[i];
|
||||
|
||||
if (TryGetProperty(definition, key, out String t))
|
||||
{
|
||||
if (valueType == null)
|
||||
{
|
||||
valueType = t;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
notFoundInSome = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if found
|
||||
if (valueType != null)
|
||||
{
|
||||
// Filter the matched definitions if needed
|
||||
if (notFoundInSome)
|
||||
{
|
||||
for (var i = 0; i < definitions.Count;)
|
||||
{
|
||||
if (TryGetProperty(definitions[i], key, out _))
|
||||
{
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
definitions.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The built-in schema for reading schema files
|
||||
/// </summary>
|
||||
private static TemplateSchema Schema
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_schema == null)
|
||||
{
|
||||
var schema = new TemplateSchema();
|
||||
|
||||
StringDefinition stringDefinition;
|
||||
SequenceDefinition sequenceDefinition;
|
||||
MappingDefinition mappingDefinition;
|
||||
OneOfDefinition oneOfDefinition;
|
||||
|
||||
// template-schema
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Version, new PropertyValue(TemplateConstants.NonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyValue(TemplateConstants.Definitions));
|
||||
schema.Definitions.Add(TemplateConstants.TemplateSchema, mappingDefinition);
|
||||
|
||||
// definitions
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
|
||||
mappingDefinition.LooseValueType = TemplateConstants.Definition;
|
||||
schema.Definitions.Add(TemplateConstants.Definitions, mappingDefinition);
|
||||
|
||||
// definition
|
||||
oneOfDefinition = new OneOfDefinition();
|
||||
oneOfDefinition.OneOf.Add(TemplateConstants.NullDefinition);
|
||||
oneOfDefinition.OneOf.Add(TemplateConstants.BooleanDefinition);
|
||||
oneOfDefinition.OneOf.Add(TemplateConstants.NumberDefinition);
|
||||
oneOfDefinition.OneOf.Add(TemplateConstants.StringDefinition);
|
||||
oneOfDefinition.OneOf.Add(TemplateConstants.SequenceDefinition);
|
||||
oneOfDefinition.OneOf.Add(TemplateConstants.MappingDefinition);
|
||||
oneOfDefinition.OneOf.Add(TemplateConstants.OneOfDefinition);
|
||||
schema.Definitions.Add(TemplateConstants.Definition, oneOfDefinition);
|
||||
|
||||
// null-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyValue(TemplateConstants.NullDefinitionProperties));
|
||||
schema.Definitions.Add(TemplateConstants.NullDefinition, mappingDefinition);
|
||||
|
||||
// null-definition-properties
|
||||
mappingDefinition = new MappingDefinition();
|
||||
schema.Definitions.Add(TemplateConstants.NullDefinitionProperties, mappingDefinition);
|
||||
|
||||
// boolean-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyValue(TemplateConstants.BooleanDefinitionProperties));
|
||||
schema.Definitions.Add(TemplateConstants.BooleanDefinition, mappingDefinition);
|
||||
|
||||
// boolean-definition-properties
|
||||
mappingDefinition = new MappingDefinition();
|
||||
schema.Definitions.Add(TemplateConstants.BooleanDefinitionProperties, mappingDefinition);
|
||||
|
||||
// number-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyValue(TemplateConstants.NumberDefinitionProperties));
|
||||
schema.Definitions.Add(TemplateConstants.NumberDefinition, mappingDefinition);
|
||||
|
||||
// number-definition-properties
|
||||
mappingDefinition = new MappingDefinition();
|
||||
schema.Definitions.Add(TemplateConstants.NumberDefinitionProperties, mappingDefinition);
|
||||
|
||||
// string-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyValue(TemplateConstants.StringDefinitionProperties));
|
||||
schema.Definitions.Add(TemplateConstants.StringDefinition, mappingDefinition);
|
||||
|
||||
// string-definition-properties
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Constant, new PropertyValue(TemplateConstants.NonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyValue(TemplateConstants.Boolean));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyValue(TemplateConstants.Boolean));
|
||||
schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition);
|
||||
|
||||
// sequence-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyValue(TemplateConstants.SequenceDefinitionProperties));
|
||||
schema.Definitions.Add(TemplateConstants.SequenceDefinition, mappingDefinition);
|
||||
|
||||
// sequence-definition-properties
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.ItemType, new PropertyValue(TemplateConstants.NonEmptyString));
|
||||
schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition);
|
||||
|
||||
// mapping-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyValue(TemplateConstants.MappingDefinitionProperties));
|
||||
schema.Definitions.Add(TemplateConstants.MappingDefinition, mappingDefinition);
|
||||
|
||||
// mapping-definition-properties
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Properties, new PropertyValue(TemplateConstants.Properties));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyValue(TemplateConstants.NonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyValue(TemplateConstants.NonEmptyString));
|
||||
schema.Definitions.Add(TemplateConstants.MappingDefinitionProperties, mappingDefinition);
|
||||
|
||||
// properties
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
|
||||
mappingDefinition.LooseValueType = TemplateConstants.NonEmptyString;
|
||||
schema.Definitions.Add(TemplateConstants.Properties, mappingDefinition);
|
||||
|
||||
// one-of-definition
|
||||
mappingDefinition = new MappingDefinition();
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
||||
mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
||||
schema.Definitions.Add(TemplateConstants.OneOfDefinition, mappingDefinition);
|
||||
|
||||
// non-empty-string
|
||||
stringDefinition = new StringDefinition();
|
||||
stringDefinition.RequireNonEmpty = true;
|
||||
schema.Definitions.Add(TemplateConstants.NonEmptyString, stringDefinition);
|
||||
|
||||
// sequence-of-non-empty-string
|
||||
sequenceDefinition = new SequenceDefinition();
|
||||
sequenceDefinition.ItemType = TemplateConstants.NonEmptyString;
|
||||
schema.Definitions.Add(TemplateConstants.SequenceOfNonEmptyString, sequenceDefinition);
|
||||
|
||||
schema.Validate();
|
||||
|
||||
Interlocked.CompareExchange(ref s_schema, schema, null);
|
||||
}
|
||||
|
||||
return s_schema;
|
||||
}
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
var oneOfPairs = new List<KeyValuePair<String, OneOfDefinition>>();
|
||||
|
||||
foreach (var pair in Definitions)
|
||||
{
|
||||
var name = pair.Key;
|
||||
|
||||
if (!s_definitionNameRegex.IsMatch(name ?? String.Empty))
|
||||
{
|
||||
throw new ArgumentException($"Invalid definition name '{name}'");
|
||||
}
|
||||
|
||||
var definition = pair.Value;
|
||||
|
||||
// Delay validation for 'one-of' definitions
|
||||
if (definition is OneOfDefinition oneOf)
|
||||
{
|
||||
oneOfPairs.Add(new KeyValuePair<String, OneOfDefinition>(name, oneOf));
|
||||
}
|
||||
// Otherwise validate now
|
||||
else
|
||||
{
|
||||
definition.Validate(this, name);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate 'one-of' definitions
|
||||
foreach (var pair in oneOfPairs)
|
||||
{
|
||||
var name = pair.Key;
|
||||
var oneOf = pair.Value;
|
||||
oneOf.Validate(this, name);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Regex s_definitionNameRegex = new Regex("^[a-zA-Z_][a-zA-Z0-9_-]*$", RegexOptions.Compiled);
|
||||
private static TemplateSchema s_schema;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
internal static class TemplateConstants
|
||||
{
|
||||
internal const String Any = "any";
|
||||
internal const String Boolean = "boolean";
|
||||
internal const String BooleanDefinition = "boolean-definition";
|
||||
internal const String BooleanDefinitionProperties = "boolean-definition-properties";
|
||||
internal const String CloseExpression = "}}";
|
||||
internal const String Constant = "constant";
|
||||
internal const String Context = "context";
|
||||
internal const String Definition = "definition";
|
||||
internal const String Definitions = "definitions";
|
||||
internal const String Description = "description";
|
||||
internal const String False = "false";
|
||||
internal const String FalseConstant = "false-constant";
|
||||
internal const String IgnoreCase = "ignore-case";
|
||||
internal const String Inherits = "inherits";
|
||||
internal const String InsertDirective = "insert";
|
||||
internal const String ItemType = "item-type";
|
||||
internal const String LooseKeyType = "loose-key-type";
|
||||
internal const String LooseValueType = "loose-value-type";
|
||||
internal const String Mapping = "mapping";
|
||||
internal const String MappingDefinition = "mapping-definition";
|
||||
internal const String MappingDefinitionProperties = "mapping-definition-properties";
|
||||
internal const String NonEmptyString = "non-empty-string";
|
||||
internal const String Null = "null";
|
||||
internal const String NullDefinition = "null-definition";
|
||||
internal const String NullDefinitionProperties = "null-definition-properties";
|
||||
internal const String Number = "number";
|
||||
internal const String NumberDefinition = "number-definition";
|
||||
internal const String NumberDefinitionProperties = "number-definition-properties";
|
||||
internal const String OneOf = "one-of";
|
||||
internal const String OneOfDefinition = "one-of-definition";
|
||||
internal const String OpenExpression = "${{";
|
||||
internal const String Properties = "properties";
|
||||
internal const String RequireNonEmpty = "require-non-empty";
|
||||
internal const String Scalar = "scalar";
|
||||
internal const String ScalarDefinition = "scalar-definition";
|
||||
internal const String ScalarDefinitionProperties = "scalar-definition-properties";
|
||||
internal const String Sequence = "sequence";
|
||||
internal const String SequenceDefinition = "sequence-definition";
|
||||
internal const String SequenceDefinitionProperties = "sequence-definition-properties";
|
||||
internal const String SequenceOfNonEmptyString = "sequence-of-non-empty-string";
|
||||
internal const String String = "string";
|
||||
internal const String StringDefinition = "string-definition";
|
||||
internal const String StringDefinitionProperties = "string-definition-properties";
|
||||
internal const String Structure = "structure";
|
||||
internal const String TemplateSchema = "template-schema";
|
||||
internal const String True = "true";
|
||||
internal const String TrueConstant = "true-constant";
|
||||
internal const String Version = "version";
|
||||
}
|
||||
}
|
||||
231
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateContext.cs
Normal file
231
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateContext.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Context object that is flowed through while loading and evaluating object templates
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class TemplateContext
|
||||
{
|
||||
internal CancellationToken CancellationToken { get; set; }
|
||||
|
||||
internal TemplateValidationErrors Errors
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_errors == null)
|
||||
{
|
||||
m_errors = new TemplateValidationErrors();
|
||||
}
|
||||
|
||||
return m_errors;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_errors = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Available functions within expression contexts
|
||||
/// </summary>
|
||||
internal IList<IFunctionInfo> ExpressionFunctions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_expressionFunctions == null)
|
||||
{
|
||||
m_expressionFunctions = new List<IFunctionInfo>();
|
||||
}
|
||||
|
||||
return m_expressionFunctions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Available values within expression contexts
|
||||
/// </summary>
|
||||
internal IDictionary<String, Object> ExpressionValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_expressionValues == null)
|
||||
{
|
||||
m_expressionValues = new Dictionary<String, Object>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return m_expressionValues;
|
||||
}
|
||||
}
|
||||
|
||||
internal TemplateMemory Memory { get; set; }
|
||||
|
||||
internal TemplateSchema Schema { get; set; }
|
||||
|
||||
internal IDictionary<String, Object> State
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_state == null)
|
||||
{
|
||||
m_state = new Dictionary<String, Object>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return m_state;
|
||||
}
|
||||
}
|
||||
|
||||
internal ITraceWriter TraceWriter { get; set; }
|
||||
|
||||
private IDictionary<String, Int32> FileIds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_fileIds == null)
|
||||
{
|
||||
m_fileIds = new Dictionary<String, Int32>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return m_fileIds;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_fileIds = value;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> FileNames
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_fileNames == null)
|
||||
{
|
||||
m_fileNames = new List<String>();
|
||||
}
|
||||
|
||||
return m_fileNames;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_fileNames = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Error(TemplateValidationError error)
|
||||
{
|
||||
Errors.Add(error);
|
||||
TraceWriter.Error(error.Message);
|
||||
}
|
||||
|
||||
internal void Error(
|
||||
TemplateToken value,
|
||||
Exception ex)
|
||||
{
|
||||
Error(value?.FileId, value?.Line, value?.Column, ex);
|
||||
}
|
||||
|
||||
internal void Error(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
Exception ex)
|
||||
{
|
||||
var prefix = GetErrorPrefix(fileId, line, column);
|
||||
Errors.Add(prefix, ex);
|
||||
TraceWriter.Error(prefix, ex);
|
||||
}
|
||||
|
||||
internal void Error(
|
||||
TemplateToken value,
|
||||
String message)
|
||||
{
|
||||
Error(value?.FileId, value?.Line, value?.Column, message);
|
||||
}
|
||||
|
||||
internal void Error(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
String message)
|
||||
{
|
||||
var prefix = GetErrorPrefix(fileId, line, column);
|
||||
if (!String.IsNullOrEmpty(prefix))
|
||||
{
|
||||
message = $"{prefix} {message}";
|
||||
}
|
||||
|
||||
Errors.Add(message);
|
||||
TraceWriter.Error(message);
|
||||
}
|
||||
|
||||
internal INamedValueInfo[] GetExpressionNamedValues()
|
||||
{
|
||||
if (m_expressionValues?.Count > 0)
|
||||
{
|
||||
return m_expressionValues.Keys.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal Int32 GetFileId(String file)
|
||||
{
|
||||
if (!FileIds.TryGetValue(file, out Int32 id))
|
||||
{
|
||||
id = FileIds.Count + 1;
|
||||
FileIds.Add(file, id);
|
||||
FileNames.Add(file);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
internal String GetFileName(Int32 fileId)
|
||||
{
|
||||
return FileNames[fileId - 1];
|
||||
}
|
||||
|
||||
private String GetErrorPrefix(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
{
|
||||
if (fileId != null)
|
||||
{
|
||||
var fileName = GetFileName(fileId.Value);
|
||||
if (line != null && column != null)
|
||||
{
|
||||
return $"{fileName} {TemplateStrings.LineColumn(line, column)}:";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{fileName}:";
|
||||
}
|
||||
}
|
||||
else if (line != null && column != null)
|
||||
{
|
||||
return $"{TemplateStrings.LineColumn(line, column)}:";
|
||||
}
|
||||
else
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private TemplateValidationErrors m_errors;
|
||||
private IList<IFunctionInfo> m_expressionFunctions;
|
||||
private IDictionary<String, Object> m_expressionValues;
|
||||
private IDictionary<String, Int32> m_fileIds;
|
||||
private List<String> m_fileNames;
|
||||
private IDictionary<String, Object> m_state;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class TemplateException : VssServiceException
|
||||
{
|
||||
public TemplateException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TemplateException(
|
||||
String message,
|
||||
Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception from serialized data
|
||||
/// </summary>
|
||||
/// <param name="info">object holding the serialized data</param>
|
||||
/// <param name="context">context info about the source or destination</param>
|
||||
protected TemplateException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class TemplateValidationException : TemplateException
|
||||
{
|
||||
public TemplateValidationException()
|
||||
: this(TemplateStrings.TemplateNotValid())
|
||||
{
|
||||
}
|
||||
|
||||
public TemplateValidationException(IEnumerable<TemplateValidationError> errors)
|
||||
: this(TemplateStrings.TemplateNotValidWithErrors(string.Join(",", (errors ?? Enumerable.Empty<TemplateValidationError>()).Select(e => e.Message))))
|
||||
{
|
||||
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
|
||||
}
|
||||
|
||||
public TemplateValidationException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TemplateValidationException(
|
||||
String message,
|
||||
Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public IList<TemplateValidationError> Errors
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_errors == null)
|
||||
{
|
||||
m_errors = new List<TemplateValidationError>();
|
||||
}
|
||||
return m_errors;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an exception from serialized data
|
||||
/// </summary>
|
||||
/// <param name="info">object holding the serialized data</param>
|
||||
/// <param name="context">context info about the source or destination</param>
|
||||
protected TemplateValidationException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
|
||||
private List<TemplateValidationError> m_errors;
|
||||
}
|
||||
}
|
||||
302
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateMemory.cs
Normal file
302
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateMemory.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracks characteristics about the current memory usage (CPU, stack, size)
|
||||
/// </summary>
|
||||
internal sealed class TemplateMemory
|
||||
{
|
||||
internal TemplateMemory(
|
||||
Int32 maxDepth,
|
||||
Int32 maxEvents,
|
||||
Int32 maxBytes)
|
||||
{
|
||||
m_maxDepth = maxDepth;
|
||||
m_maxEvents = maxEvents;
|
||||
m_maxBytes = maxBytes;
|
||||
}
|
||||
|
||||
public Int32 CurrentBytes => m_currentBytes;
|
||||
|
||||
public Int32 MaxBytes => m_maxBytes;
|
||||
|
||||
internal void AddBytes(Int32 bytes)
|
||||
{
|
||||
checked
|
||||
{
|
||||
m_currentBytes += bytes;
|
||||
}
|
||||
|
||||
if (m_currentBytes > m_maxBytes)
|
||||
{
|
||||
throw new InvalidOperationException(TemplateStrings.MaxObjectSizeExceeded());
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddBytes(String value)
|
||||
{
|
||||
var bytes = CalculateBytes(value);
|
||||
AddBytes(bytes);
|
||||
}
|
||||
|
||||
internal void AddBytes(
|
||||
JToken value,
|
||||
Boolean traverse)
|
||||
{
|
||||
var bytes = CalculateBytes(value, traverse);
|
||||
AddBytes(bytes);
|
||||
}
|
||||
|
||||
internal void AddBytes(
|
||||
TemplateToken value,
|
||||
Boolean traverse = false)
|
||||
{
|
||||
var bytes = CalculateBytes(value, traverse);
|
||||
AddBytes(bytes);
|
||||
}
|
||||
|
||||
internal void AddBytes(LiteralToken literal)
|
||||
{
|
||||
var bytes = CalculateBytes(literal);
|
||||
AddBytes(bytes);
|
||||
}
|
||||
|
||||
internal void AddBytes(SequenceToken sequence)
|
||||
{
|
||||
var bytes = CalculateBytes(sequence);
|
||||
AddBytes(bytes);
|
||||
}
|
||||
|
||||
internal void AddBytes(MappingToken mapping)
|
||||
{
|
||||
var bytes = CalculateBytes(mapping);
|
||||
AddBytes(bytes);
|
||||
}
|
||||
|
||||
internal void AddBytes(BasicExpressionToken basicExpression)
|
||||
{
|
||||
var bytes = CalculateBytes(basicExpression);
|
||||
AddBytes(bytes);
|
||||
}
|
||||
|
||||
internal void AddBytes(InsertExpressionToken insertExpression)
|
||||
{
|
||||
var bytes = CalculateBytes(insertExpression);
|
||||
AddBytes(bytes);
|
||||
}
|
||||
|
||||
internal Int32 CalculateBytes(String value)
|
||||
{
|
||||
// This measurement doesn't have to be perfect
|
||||
// https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
|
||||
|
||||
checked
|
||||
{
|
||||
return StringBaseOverhead + ((value?.Length ?? 0) * sizeof(Char));
|
||||
}
|
||||
}
|
||||
|
||||
internal Int32 CalculateBytes(
|
||||
JToken value,
|
||||
Boolean traverse)
|
||||
{
|
||||
// This measurement doesn't have to be perfect
|
||||
// https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
return MinObjectSize;
|
||||
}
|
||||
|
||||
if (!traverse)
|
||||
{
|
||||
switch (value.Type)
|
||||
{
|
||||
case JTokenType.String:
|
||||
checked
|
||||
{
|
||||
return StringBaseOverhead + (value.ToObject<String>().Length * sizeof(Char));
|
||||
}
|
||||
|
||||
case JTokenType.Property:
|
||||
var property = value as JProperty;
|
||||
checked
|
||||
{
|
||||
return StringBaseOverhead + ((property.Name?.Length ?? 0) * sizeof(Char));
|
||||
}
|
||||
|
||||
case JTokenType.Array:
|
||||
case JTokenType.Boolean:
|
||||
case JTokenType.Float:
|
||||
case JTokenType.Integer:
|
||||
case JTokenType.Null:
|
||||
case JTokenType.Object:
|
||||
return MinObjectSize;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unexpected JToken type '{value.Type}' when traversing object");
|
||||
}
|
||||
}
|
||||
|
||||
var result = 0;
|
||||
do
|
||||
{
|
||||
// Descend as much as possible
|
||||
while (true)
|
||||
{
|
||||
// Add bytes
|
||||
var bytes = CalculateBytes(value, false);
|
||||
checked
|
||||
{
|
||||
result += bytes;
|
||||
}
|
||||
|
||||
// Descend
|
||||
if (value.HasValues)
|
||||
{
|
||||
value = value.First;
|
||||
}
|
||||
// No more descendants
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Next sibling or ancestor sibling
|
||||
do
|
||||
{
|
||||
var sibling = value.Next;
|
||||
|
||||
// Sibling found
|
||||
if (sibling != null)
|
||||
{
|
||||
value = sibling;
|
||||
break;
|
||||
}
|
||||
|
||||
// Ascend
|
||||
value = value.Parent;
|
||||
|
||||
} while (value != null);
|
||||
|
||||
} while (value != null);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal Int32 CalculateBytes(
|
||||
TemplateToken value,
|
||||
Boolean traverse = false)
|
||||
{
|
||||
var enumerable = traverse ? value.Traverse() : new[] { value };
|
||||
var result = 0;
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
// This measurement doesn't have to be perfect
|
||||
// https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
|
||||
switch (item.Type)
|
||||
{
|
||||
case TokenType.Null:
|
||||
case TokenType.Boolean:
|
||||
case TokenType.Number:
|
||||
checked
|
||||
{
|
||||
result += MinObjectSize;
|
||||
}
|
||||
break;
|
||||
|
||||
case TokenType.String:
|
||||
var stringToken = item as StringToken;
|
||||
checked
|
||||
{
|
||||
result += MinObjectSize + StringBaseOverhead + ((stringToken.Value?.Length ?? 0) * sizeof(Char));
|
||||
}
|
||||
break;
|
||||
|
||||
case TokenType.Sequence:
|
||||
case TokenType.Mapping:
|
||||
case TokenType.InsertExpression:
|
||||
// Min object size is good enough. Allows for base + a few fields.
|
||||
checked
|
||||
{
|
||||
result += MinObjectSize;
|
||||
}
|
||||
break;
|
||||
|
||||
case TokenType.BasicExpression:
|
||||
var basicExpression = item as BasicExpressionToken;
|
||||
checked
|
||||
{
|
||||
result += MinObjectSize + StringBaseOverhead + ((basicExpression.Expression?.Length ?? 0) * sizeof(Char));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unexpected template type '{item.Type}'");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal void SubtractBytes(Int32 bytes)
|
||||
{
|
||||
if (bytes > m_currentBytes)
|
||||
{
|
||||
throw new InvalidOperationException("Bytes to subtract exceeds total bytes");
|
||||
}
|
||||
|
||||
m_currentBytes -= bytes;
|
||||
}
|
||||
|
||||
internal void SubtractBytes(
|
||||
JToken value,
|
||||
Boolean traverse)
|
||||
{
|
||||
var bytes = CalculateBytes(value, traverse);
|
||||
SubtractBytes(bytes);
|
||||
}
|
||||
|
||||
internal void SubtractBytes(
|
||||
TemplateToken value,
|
||||
Boolean traverse = false)
|
||||
{
|
||||
var bytes = CalculateBytes(value, traverse);
|
||||
SubtractBytes(bytes);
|
||||
}
|
||||
|
||||
internal void IncrementDepth()
|
||||
{
|
||||
if (m_depth++ >= m_maxDepth)
|
||||
{
|
||||
throw new InvalidOperationException(TemplateStrings.MaxObjectDepthExceeded());
|
||||
}
|
||||
}
|
||||
|
||||
internal void DecrementDepth()
|
||||
{
|
||||
m_depth--;
|
||||
}
|
||||
|
||||
internal void IncrementEvents()
|
||||
{
|
||||
if (m_events++ >= m_maxEvents)
|
||||
{
|
||||
throw new InvalidOperationException(TemplateStrings.MaxTemplateEventsExceeded());
|
||||
}
|
||||
}
|
||||
|
||||
internal const Int32 MinObjectSize = 24;
|
||||
internal const Int32 StringBaseOverhead = 26;
|
||||
private readonly Int32 m_maxDepth;
|
||||
private readonly Int32 m_maxEvents;
|
||||
private readonly Int32 m_maxBytes;
|
||||
private Int32 m_depth;
|
||||
private Int32 m_events;
|
||||
private Int32 m_currentBytes;
|
||||
}
|
||||
}
|
||||
818
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs
Normal file
818
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs
Normal file
@@ -0,0 +1,818 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a source object format into a TemplateToken
|
||||
/// </summary>
|
||||
internal sealed class TemplateReader
|
||||
{
|
||||
private TemplateReader(
|
||||
TemplateContext context,
|
||||
TemplateSchema schema,
|
||||
IObjectReader objectReader,
|
||||
Int32? fileId)
|
||||
{
|
||||
m_context = context;
|
||||
m_schema = schema;
|
||||
m_memory = context.Memory;
|
||||
m_objectReader = objectReader;
|
||||
m_fileId = fileId;
|
||||
}
|
||||
|
||||
internal static TemplateToken Read(
|
||||
TemplateContext context,
|
||||
String type,
|
||||
IObjectReader objectReader,
|
||||
Int32? fileId,
|
||||
out Int32 bytes)
|
||||
{
|
||||
return Read(context, type, objectReader, fileId, context.Schema, out bytes);
|
||||
}
|
||||
|
||||
internal static TemplateToken Read(
|
||||
TemplateContext context,
|
||||
String type,
|
||||
IObjectReader objectReader,
|
||||
Int32? fileId,
|
||||
TemplateSchema schema,
|
||||
out Int32 bytes)
|
||||
{
|
||||
TemplateToken result = null;
|
||||
|
||||
var reader = new TemplateReader(context, schema, objectReader, fileId);
|
||||
var originalBytes = context.Memory.CurrentBytes;
|
||||
try
|
||||
{
|
||||
objectReader.ValidateStart();
|
||||
var definition = new DefinitionInfo(schema, type);
|
||||
result = reader.ReadValue(definition);
|
||||
objectReader.ValidateEnd();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Error(fileId, null, null, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bytes = context.Memory.CurrentBytes - originalBytes;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TemplateToken ReadValue(DefinitionInfo definition)
|
||||
{
|
||||
m_memory.IncrementEvents();
|
||||
|
||||
// Scalar
|
||||
if (m_objectReader.AllowLiteral(out LiteralToken literal))
|
||||
{
|
||||
var scalar = ParseScalar(literal, definition.AllowedContext);
|
||||
Validate(ref scalar, definition);
|
||||
m_memory.AddBytes(scalar);
|
||||
return scalar;
|
||||
}
|
||||
|
||||
// Sequence
|
||||
if (m_objectReader.AllowSequenceStart(out SequenceToken sequence))
|
||||
{
|
||||
m_memory.IncrementDepth();
|
||||
m_memory.AddBytes(sequence);
|
||||
|
||||
var sequenceDefinition = definition.Get<SequenceDefinition>().FirstOrDefault();
|
||||
|
||||
// Legal
|
||||
if (sequenceDefinition != null)
|
||||
{
|
||||
var itemDefinition = new DefinitionInfo(definition, sequenceDefinition.ItemType);
|
||||
|
||||
// Add each item
|
||||
while (!m_objectReader.AllowSequenceEnd())
|
||||
{
|
||||
var item = ReadValue(itemDefinition);
|
||||
sequence.Add(item);
|
||||
}
|
||||
}
|
||||
// Illegal
|
||||
else
|
||||
{
|
||||
// Error
|
||||
m_context.Error(sequence, TemplateStrings.UnexpectedSequenceStart());
|
||||
|
||||
// Skip each item
|
||||
while (!m_objectReader.AllowSequenceEnd())
|
||||
{
|
||||
SkipValue();
|
||||
}
|
||||
}
|
||||
|
||||
m_memory.DecrementDepth();
|
||||
return sequence;
|
||||
}
|
||||
|
||||
// Mapping
|
||||
if (m_objectReader.AllowMappingStart(out MappingToken mapping))
|
||||
{
|
||||
m_memory.IncrementDepth();
|
||||
m_memory.AddBytes(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_objectReader.AllowMappingEnd())
|
||||
{
|
||||
SkipValue();
|
||||
SkipValue();
|
||||
}
|
||||
}
|
||||
|
||||
m_memory.DecrementDepth();
|
||||
return mapping;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(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_objectReader.AllowLiteral(out LiteralToken rawLiteral))
|
||||
{
|
||||
var nextKeyScalar = ParseScalar(rawLiteral, definition.AllowedContext);
|
||||
|
||||
// Expression
|
||||
if (nextKeyScalar is ExpressionToken)
|
||||
{
|
||||
// Legal
|
||||
if (definition.AllowedContext.Length > 0)
|
||||
{
|
||||
m_memory.AddBytes(nextKeyScalar);
|
||||
var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any);
|
||||
mapping.Add(nextKeyScalar, ReadValue(anyDefinition));
|
||||
}
|
||||
// Illegal
|
||||
else
|
||||
{
|
||||
m_context.Error(nextKeyScalar, TemplateStrings.ExpressionNotAllowed());
|
||||
SkipValue();
|
||||
}
|
||||
|
||||
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));
|
||||
SkipValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Well known
|
||||
if (m_schema.TryMatchKey(mappingDefinitions, nextKey.Value, out String nextValueType))
|
||||
{
|
||||
m_memory.AddBytes(nextKey);
|
||||
var nextValueDefinition = new DefinitionInfo(definition, nextValueType);
|
||||
var nextValue = ReadValue(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);
|
||||
m_memory.AddBytes(nextKey);
|
||||
var nextValue = ReadValue(looseValueDefinition.Value);
|
||||
mapping.Add(nextKey, nextValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Error
|
||||
m_context.Error(nextKey, TemplateStrings.UnexpectedValue(nextKey.Value));
|
||||
SkipValue();
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
ExpectMappingEnd();
|
||||
}
|
||||
|
||||
private void HandleMappingWithAllLooseProperties(
|
||||
DefinitionInfo mappingDefinition,
|
||||
DefinitionInfo keyDefinition,
|
||||
DefinitionInfo valueDefinition,
|
||||
MappingToken mapping)
|
||||
{
|
||||
TemplateToken nextValue;
|
||||
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
while (m_objectReader.AllowLiteral(out LiteralToken rawLiteral))
|
||||
{
|
||||
var nextKeyScalar = ParseScalar(rawLiteral, mappingDefinition.AllowedContext);
|
||||
|
||||
// Expression
|
||||
if (nextKeyScalar is ExpressionToken)
|
||||
{
|
||||
// Legal
|
||||
if (mappingDefinition.AllowedContext.Length > 0)
|
||||
{
|
||||
m_memory.AddBytes(nextKeyScalar);
|
||||
nextValue = ReadValue(valueDefinition);
|
||||
mapping.Add(nextKeyScalar, nextValue);
|
||||
}
|
||||
// Illegal
|
||||
else
|
||||
{
|
||||
m_context.Error(nextKeyScalar, TemplateStrings.ExpressionNotAllowed());
|
||||
SkipValue();
|
||||
}
|
||||
|
||||
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));
|
||||
SkipValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate
|
||||
Validate(nextKey, keyDefinition);
|
||||
m_memory.AddBytes(nextKey);
|
||||
|
||||
// Add the pair
|
||||
nextValue = ReadValue(valueDefinition);
|
||||
mapping.Add(nextKey, nextValue);
|
||||
}
|
||||
|
||||
ExpectMappingEnd();
|
||||
}
|
||||
|
||||
private void ExpectMappingEnd()
|
||||
{
|
||||
if (!m_objectReader.AllowMappingEnd())
|
||||
{
|
||||
throw new Exception("Expected mapping end"); // Should never happen
|
||||
}
|
||||
}
|
||||
|
||||
private void SkipValue(Boolean error = false)
|
||||
{
|
||||
m_memory.IncrementEvents();
|
||||
|
||||
// Scalar
|
||||
if (m_objectReader.AllowLiteral(out LiteralToken literal))
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
m_context.Error(literal, TemplateStrings.UnexpectedValue(literal));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Sequence
|
||||
if (m_objectReader.AllowSequenceStart(out SequenceToken sequence))
|
||||
{
|
||||
m_memory.IncrementDepth();
|
||||
|
||||
if (error)
|
||||
{
|
||||
m_context.Error(sequence, TemplateStrings.UnexpectedSequenceStart());
|
||||
}
|
||||
|
||||
while (!m_objectReader.AllowSequenceEnd())
|
||||
{
|
||||
SkipValue();
|
||||
}
|
||||
|
||||
m_memory.DecrementDepth();
|
||||
return;
|
||||
}
|
||||
|
||||
// Mapping
|
||||
if (m_objectReader.AllowMappingStart(out MappingToken mapping))
|
||||
{
|
||||
m_memory.IncrementDepth();
|
||||
|
||||
if (error)
|
||||
{
|
||||
m_context.Error(mapping, TemplateStrings.UnexpectedMappingStart());
|
||||
}
|
||||
|
||||
while (!m_objectReader.AllowMappingEnd())
|
||||
{
|
||||
SkipValue();
|
||||
SkipValue();
|
||||
}
|
||||
|
||||
m_memory.DecrementDepth();
|
||||
return;
|
||||
}
|
||||
|
||||
// Unexpected
|
||||
throw new InvalidOperationException(TemplateStrings.ExpectedScalarSequenceOrMapping());
|
||||
}
|
||||
|
||||
private void Validate(
|
||||
StringToken stringToken,
|
||||
DefinitionInfo definition)
|
||||
{
|
||||
var scalar = stringToken as ScalarToken;
|
||||
Validate(ref scalar, definition);
|
||||
}
|
||||
|
||||
private void Validate(
|
||||
ref ScalarToken scalar,
|
||||
DefinitionInfo definition)
|
||||
{
|
||||
switch (scalar.Type)
|
||||
{
|
||||
case TokenType.Null:
|
||||
case TokenType.Boolean:
|
||||
case TokenType.Number:
|
||||
case TokenType.String:
|
||||
var literal = scalar as LiteralToken;
|
||||
|
||||
// Legal
|
||||
if (definition.Get<ScalarDefinition>().Any(x => x.IsMatch(literal)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a string, convert
|
||||
if (literal.Type != TokenType.String)
|
||||
{
|
||||
literal = new StringToken(literal.FileId, literal.Line, literal.Column, literal.ToString());
|
||||
|
||||
// Legal
|
||||
if (definition.Get<StringDefinition>().Any(x => x.IsMatch(literal)))
|
||||
{
|
||||
scalar = literal;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Illegal
|
||||
m_context.Error(literal, TemplateStrings.UnexpectedValue(literal));
|
||||
break;
|
||||
|
||||
case TokenType.BasicExpression:
|
||||
|
||||
// Illegal
|
||||
if (definition.AllowedContext.Length == 0)
|
||||
{
|
||||
m_context.Error(scalar, TemplateStrings.ExpressionNotAllowed());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
m_context.Error(scalar, TemplateStrings.UnexpectedValue(scalar));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private ScalarToken ParseScalar(
|
||||
LiteralToken token,
|
||||
String[] allowedContext)
|
||||
{
|
||||
// Not a string
|
||||
if (token.Type != TokenType.String)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
|
||||
// Check if the value is definitely a literal
|
||||
var raw = token.ToString();
|
||||
Int32 startExpression;
|
||||
if (String.IsNullOrEmpty(raw) ||
|
||||
(startExpression = raw.IndexOf(TemplateConstants.OpenExpression)) < 0) // Doesn't contain ${{
|
||||
{
|
||||
return token;
|
||||
}
|
||||
|
||||
// Break the value into segments of LiteralToken and ExpressionToken
|
||||
var segments = new List<ScalarToken>();
|
||||
var i = 0;
|
||||
while (i < raw.Length)
|
||||
{
|
||||
// An expression starts here:
|
||||
if (i == startExpression)
|
||||
{
|
||||
// Find the end of the expression - i.e. }}
|
||||
startExpression = i;
|
||||
var endExpression = -1;
|
||||
var inString = false;
|
||||
for (i += TemplateConstants.OpenExpression.Length; i < raw.Length; i++)
|
||||
{
|
||||
if (raw[i] == '\'')
|
||||
{
|
||||
inString = !inString; // Note, this handles escaped single quotes gracefully. Ex. 'foo''bar'
|
||||
}
|
||||
else if (!inString && raw[i] == '}' && raw[i - 1] == '}')
|
||||
{
|
||||
endExpression = i;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if not closed
|
||||
if (endExpression < startExpression)
|
||||
{
|
||||
m_context.Error(token, TemplateStrings.ExpressionNotClosed());
|
||||
return token;
|
||||
}
|
||||
|
||||
// Parse the expression
|
||||
var rawExpression = raw.Substring(
|
||||
startExpression + TemplateConstants.OpenExpression.Length,
|
||||
endExpression - startExpression + 1 - TemplateConstants.OpenExpression.Length - TemplateConstants.CloseExpression.Length);
|
||||
var expression = ParseExpression(token.Line, token.Column, rawExpression, allowedContext, out Exception ex);
|
||||
|
||||
// Check for error
|
||||
if (ex != null)
|
||||
{
|
||||
m_context.Error(token, ex);
|
||||
return token;
|
||||
}
|
||||
|
||||
// Check if a directive was used when not allowed
|
||||
if (!String.IsNullOrEmpty(expression.Directive) &&
|
||||
((startExpression != 0) || (i < raw.Length)))
|
||||
{
|
||||
m_context.Error(token, TemplateStrings.DirectiveNotAllowedInline(expression.Directive));
|
||||
return token;
|
||||
}
|
||||
|
||||
// Add the segment
|
||||
segments.Add(expression);
|
||||
|
||||
// Look for the next expression
|
||||
startExpression = raw.IndexOf(TemplateConstants.OpenExpression, i);
|
||||
}
|
||||
// The next expression is further ahead:
|
||||
else if (i < startExpression)
|
||||
{
|
||||
// Append the segment
|
||||
AddString(segments, token.Line, token.Column, raw.Substring(i, startExpression - i));
|
||||
|
||||
// Adjust the position
|
||||
i = startExpression;
|
||||
}
|
||||
// No remaining expressions:
|
||||
else
|
||||
{
|
||||
AddString(segments, token.Line, token.Column, raw.Substring(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if can convert to a literal
|
||||
// For example, the escaped expression: ${{ '{{ this is a literal }}' }}
|
||||
if (segments.Count == 1 &&
|
||||
segments[0] is BasicExpressionToken basicExpression &&
|
||||
IsExpressionString(basicExpression.Expression, out String str))
|
||||
{
|
||||
return new StringToken(m_fileId, token.Line, token.Column, str);
|
||||
}
|
||||
|
||||
// Check if only ony segment
|
||||
if (segments.Count == 1)
|
||||
{
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
// Build the new expression, using the format function
|
||||
var format = new StringBuilder();
|
||||
var args = new StringBuilder();
|
||||
var argIndex = 0;
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (segment is StringToken literal)
|
||||
{
|
||||
var text = ExpressionUtility.StringEscape(literal.Value) // Escape quotes
|
||||
.Replace("{", "{{") // Escape braces
|
||||
.Replace("}", "}}");
|
||||
format.Append(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
format.Append("{" + argIndex.ToString(CultureInfo.InvariantCulture) + "}"); // Append formatter
|
||||
argIndex++;
|
||||
|
||||
var expression = segment as BasicExpressionToken;
|
||||
args.Append(", ");
|
||||
args.Append(expression.Expression);
|
||||
}
|
||||
}
|
||||
|
||||
return new BasicExpressionToken(m_fileId, token.Line, token.Column, $"format('{format}'{args})");
|
||||
}
|
||||
|
||||
private ExpressionToken ParseExpression(
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
String value,
|
||||
String[] allowedContext,
|
||||
out Exception ex)
|
||||
{
|
||||
var trimmed = value.Trim();
|
||||
|
||||
// Check if the value is empty
|
||||
if (String.IsNullOrEmpty(trimmed))
|
||||
{
|
||||
ex = new ArgumentException(TemplateStrings.ExpectedExpression());
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to find a matching directive
|
||||
List<String> parameters;
|
||||
if (MatchesDirective(trimmed, TemplateConstants.InsertDirective, 0, out parameters, out ex))
|
||||
{
|
||||
return new InsertExpressionToken(m_fileId, line, column);
|
||||
}
|
||||
else if (ex != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the value is an expression
|
||||
if (!ExpressionToken.IsValidExpression(trimmed, allowedContext, out ex))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return the expression
|
||||
return new BasicExpressionToken(m_fileId, line, column, trimmed);
|
||||
}
|
||||
|
||||
private void AddString(
|
||||
List<ScalarToken> segments,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
String value)
|
||||
{
|
||||
// If the last segment was a LiteralToken, then append to the last segment
|
||||
if (segments.Count > 0 && segments[segments.Count - 1] is StringToken lastSegment)
|
||||
{
|
||||
segments[segments.Count - 1] = new StringToken(m_fileId, line, column, lastSegment.Value + value);
|
||||
}
|
||||
// Otherwise add a new LiteralToken
|
||||
else
|
||||
{
|
||||
segments.Add(new StringToken(m_fileId, line, column, value));
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean MatchesDirective(
|
||||
String trimmed,
|
||||
String directive,
|
||||
Int32 expectedParameters,
|
||||
out List<String> parameters,
|
||||
out Exception ex)
|
||||
{
|
||||
if (trimmed.StartsWith(directive, StringComparison.Ordinal) &&
|
||||
(trimmed.Length == directive.Length || Char.IsWhiteSpace(trimmed[directive.Length])))
|
||||
{
|
||||
parameters = new List<String>();
|
||||
var startIndex = directive.Length;
|
||||
var inString = false;
|
||||
var parens = 0;
|
||||
for (var i = startIndex; i < trimmed.Length; i++)
|
||||
{
|
||||
var c = trimmed[i];
|
||||
if (Char.IsWhiteSpace(c) && !inString && parens == 0)
|
||||
{
|
||||
if (startIndex < i)
|
||||
{
|
||||
parameters.Add(trimmed.Substring(startIndex, i - startIndex));
|
||||
}
|
||||
|
||||
startIndex = i + 1;
|
||||
}
|
||||
else if (c == '\'')
|
||||
{
|
||||
inString = !inString;
|
||||
}
|
||||
else if (c == '(' && !inString)
|
||||
{
|
||||
parens++;
|
||||
}
|
||||
else if (c == ')' && !inString)
|
||||
{
|
||||
parens--;
|
||||
}
|
||||
}
|
||||
|
||||
if (startIndex < trimmed.Length)
|
||||
{
|
||||
parameters.Add(trimmed.Substring(startIndex));
|
||||
}
|
||||
|
||||
if (expectedParameters != parameters.Count)
|
||||
{
|
||||
ex = new ArgumentException(TemplateStrings.ExpectedNParametersFollowingDirective(expectedParameters, directive, parameters.Count));
|
||||
parameters = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
ex = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
ex = null;
|
||||
parameters = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Boolean IsExpressionString(
|
||||
String trimmed,
|
||||
out String str)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
var inString = false;
|
||||
for (var i = 0; i < trimmed.Length; i++)
|
||||
{
|
||||
var c = trimmed[i];
|
||||
if (c == '\'')
|
||||
{
|
||||
inString = !inString;
|
||||
|
||||
if (inString && i != 0)
|
||||
{
|
||||
builder.Append(c);
|
||||
}
|
||||
}
|
||||
else if (!inString)
|
||||
{
|
||||
str = default;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
str = builder.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
private struct DefinitionInfo
|
||||
{
|
||||
public DefinitionInfo(
|
||||
TemplateSchema schema,
|
||||
String name)
|
||||
{
|
||||
m_schema = schema;
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
|
||||
public DefinitionInfo(
|
||||
DefinitionInfo parent,
|
||||
String name)
|
||||
{
|
||||
m_schema = parent.m_schema;
|
||||
|
||||
// Lookup the definition
|
||||
Definition = m_schema.GetDefinition(name);
|
||||
|
||||
// Determine whether to expand
|
||||
if (Definition.Context.Length > 0)
|
||||
{
|
||||
AllowedContext = new HashSet<String>(parent.AllowedContext.Concat(Definition.Context)).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
AllowedContext = parent.AllowedContext;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> Get<T>()
|
||||
where T : Definition
|
||||
{
|
||||
return m_schema.Get<T>(Definition);
|
||||
}
|
||||
|
||||
private TemplateSchema m_schema;
|
||||
public Definition Definition;
|
||||
public String[] AllowedContext;
|
||||
}
|
||||
|
||||
private readonly TemplateContext m_context;
|
||||
private readonly Int32? m_fileId;
|
||||
private readonly TemplateMemory m_memory;
|
||||
private readonly IObjectReader m_objectReader;
|
||||
private readonly TemplateSchema m_schema;
|
||||
}
|
||||
}
|
||||
1210
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateUnraveler.cs
Normal file
1210
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateUnraveler.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about an error which occurred during validation.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class TemplateValidationError
|
||||
{
|
||||
public TemplateValidationError()
|
||||
{
|
||||
}
|
||||
|
||||
public TemplateValidationError(String message)
|
||||
: this(null, message)
|
||||
{
|
||||
}
|
||||
|
||||
public TemplateValidationError(
|
||||
String code,
|
||||
String message)
|
||||
{
|
||||
Code = code;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Code
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Message
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static IEnumerable<TemplateValidationError> Create(Exception exception)
|
||||
{
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
yield return new TemplateValidationError(exception.Message);
|
||||
if (exception.InnerException == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
exception = exception.InnerException;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about an error which occurred during validation.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class TemplateValidationErrors : IEnumerable<TemplateValidationError>
|
||||
{
|
||||
public TemplateValidationErrors()
|
||||
{
|
||||
}
|
||||
|
||||
public TemplateValidationErrors(
|
||||
Int32 maxErrors,
|
||||
Int32 maxMessageLength)
|
||||
{
|
||||
m_maxErrors = maxErrors;
|
||||
m_maxMessageLength = maxMessageLength;
|
||||
}
|
||||
|
||||
public Int32 Count => m_errors.Count;
|
||||
|
||||
public void Add(String message)
|
||||
{
|
||||
Add(new TemplateValidationError(message));
|
||||
}
|
||||
|
||||
public void Add(Exception ex)
|
||||
{
|
||||
Add(null, ex);
|
||||
}
|
||||
|
||||
public void Add(String messagePrefix, Exception ex)
|
||||
{
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.Message;
|
||||
Add(new TemplateValidationError(message));
|
||||
if (ex.InnerException == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ex = ex.InnerException;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(IEnumerable<TemplateValidationError> errors)
|
||||
{
|
||||
foreach (var error in errors)
|
||||
{
|
||||
Add(error);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TemplateValidationError error)
|
||||
{
|
||||
// Check max errors
|
||||
if (m_maxErrors <= 0 ||
|
||||
m_errors.Count < m_maxErrors)
|
||||
{
|
||||
// Check max message length
|
||||
if (m_maxMessageLength > 0 &&
|
||||
error.Message?.Length > m_maxMessageLength)
|
||||
{
|
||||
error = new TemplateValidationError(error.Code, error.Message.Substring(0, m_maxMessageLength) + "[...]");
|
||||
}
|
||||
|
||||
m_errors.Add(error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws <c ref="TemplateValidationException" /> if any errors.
|
||||
/// </summary>
|
||||
public void Check()
|
||||
{
|
||||
if (m_errors.Count > 0)
|
||||
{
|
||||
throw new TemplateValidationException(m_errors);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_errors.Clear();
|
||||
}
|
||||
|
||||
public IEnumerator<TemplateValidationError> GetEnumerator()
|
||||
{
|
||||
return (m_errors as IEnumerable<TemplateValidationError>).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return (m_errors as IEnumerable).GetEnumerator();
|
||||
}
|
||||
|
||||
private readonly List<TemplateValidationError> m_errors = new List<TemplateValidationError>();
|
||||
private readonly Int32 m_maxErrors;
|
||||
private readonly Int32 m_maxMessageLength;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts from a TemplateToken into another object format
|
||||
/// </summary>
|
||||
internal sealed class TemplateWriter
|
||||
{
|
||||
internal static void Write(
|
||||
IObjectWriter objectWriter,
|
||||
TemplateToken value)
|
||||
{
|
||||
objectWriter.WriteStart();
|
||||
WriteValue(objectWriter, value);
|
||||
objectWriter.WriteEnd();
|
||||
}
|
||||
|
||||
private static void WriteValue(
|
||||
IObjectWriter objectWriter,
|
||||
TemplateToken value)
|
||||
{
|
||||
switch (value?.Type ?? TokenType.Null)
|
||||
{
|
||||
case TokenType.Null:
|
||||
objectWriter.WriteNull();
|
||||
break;
|
||||
|
||||
case TokenType.Boolean:
|
||||
var booleanToken = value as BooleanToken;
|
||||
objectWriter.WriteBoolean(booleanToken.Value);
|
||||
break;
|
||||
|
||||
case TokenType.Number:
|
||||
var numberToken = value as NumberToken;
|
||||
objectWriter.WriteNumber(numberToken.Value);
|
||||
break;
|
||||
|
||||
case TokenType.String:
|
||||
case TokenType.BasicExpression:
|
||||
case TokenType.InsertExpression:
|
||||
objectWriter.WriteString(value.ToString());
|
||||
break;
|
||||
|
||||
case TokenType.Mapping:
|
||||
var mappingToken = value as MappingToken;
|
||||
objectWriter.WriteMappingStart();
|
||||
foreach (var pair in mappingToken)
|
||||
{
|
||||
WriteValue(objectWriter, pair.Key);
|
||||
WriteValue(objectWriter, pair.Value);
|
||||
}
|
||||
objectWriter.WriteMappingEnd();
|
||||
break;
|
||||
|
||||
case TokenType.Sequence:
|
||||
var sequenceToken = value as SequenceToken;
|
||||
objectWriter.WriteSequenceStart();
|
||||
foreach (var item in sequenceToken)
|
||||
{
|
||||
WriteValue(objectWriter, item);
|
||||
}
|
||||
objectWriter.WriteSequenceEnd();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unexpected type '{value.GetType()}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk.Functions;
|
||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
using Container = GitHub.DistributedTask.Expressions2.Sdk.Container;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class BasicExpressionToken : ExpressionToken
|
||||
{
|
||||
internal BasicExpressionToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
String expression)
|
||||
: base(TokenType.BasicExpression, fileId, line, column, null)
|
||||
{
|
||||
m_expression = expression;
|
||||
}
|
||||
|
||||
internal String Expression
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_expression == null)
|
||||
{
|
||||
m_expression = String.Empty;
|
||||
}
|
||||
|
||||
return m_expression;
|
||||
}
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new BasicExpressionToken(null, null, null, m_expression) : new BasicExpressionToken(FileId, Line, Column, m_expression);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return $"{TemplateConstants.OpenExpression} {m_expression} {TemplateConstants.CloseExpression}";
|
||||
}
|
||||
|
||||
public override String ToDisplayString()
|
||||
{
|
||||
var expressionParser = new ExpressionParser();
|
||||
var expressionNode = expressionParser.ValidateSyntax(Expression, null);
|
||||
if (expressionNode is Format formatNode)
|
||||
{
|
||||
// Make sure our first item is indeed a literal string so we can format it.
|
||||
if (formatNode.Parameters.Count > 1 &&
|
||||
formatNode.Parameters.First() is Literal literalValueNode &&
|
||||
literalValueNode.Kind == ValueKind.String)
|
||||
{
|
||||
// Get all other Parameters san the formatted string to pass into the formatter
|
||||
var formatParameters = formatNode.Parameters.Skip(1).Select(x => this.ConvertFormatParameterToExpression(x)).ToArray();
|
||||
if (formatParameters.Length > 0)
|
||||
{
|
||||
String formattedString = String.Empty;
|
||||
try
|
||||
{
|
||||
formattedString = String.Format(CultureInfo.InvariantCulture, (formatNode.Parameters[0] as Literal).Value as String, formatParameters);
|
||||
}
|
||||
catch (FormatException) { }
|
||||
catch (ArgumentNullException) { } // If this operation fails, revert to default display name
|
||||
if (!String.IsNullOrEmpty(formattedString))
|
||||
{
|
||||
return TrimDisplayString(formattedString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return base.ToDisplayString();
|
||||
}
|
||||
|
||||
internal StringToken EvaluateStringToken(
|
||||
TemplateContext context,
|
||||
out Int32 bytes)
|
||||
{
|
||||
return EvaluateStringToken(context, Expression, out bytes);
|
||||
}
|
||||
|
||||
internal MappingToken EvaluateMappingToken(
|
||||
TemplateContext context,
|
||||
out Int32 bytes)
|
||||
{
|
||||
return EvaluateMappingToken(context, Expression, out bytes);
|
||||
}
|
||||
|
||||
internal SequenceToken EvaluateSequenceToken(
|
||||
TemplateContext context,
|
||||
out Int32 bytes)
|
||||
{
|
||||
return EvaluateSequenceToken(context, Expression, out bytes);
|
||||
}
|
||||
|
||||
internal TemplateToken EvaluateTemplateToken(
|
||||
TemplateContext context,
|
||||
out Int32 bytes)
|
||||
{
|
||||
return EvaluateTemplateToken(context, Expression, out bytes);
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
private void OnSerializing(StreamingContext context)
|
||||
{
|
||||
if (m_expression?.Length == 0)
|
||||
{
|
||||
m_expression = null;
|
||||
}
|
||||
}
|
||||
|
||||
private String ConvertFormatParameterToExpression(ExpressionNode node)
|
||||
{
|
||||
var nodeString = node.ConvertToExpression();
|
||||
|
||||
// If the node is a container, see if it starts with '(' and ends with ')' so we can simplify the string
|
||||
// Should only simplify if only one '(' or ')' exists in the string
|
||||
// We are trying to simplify the case (a || b) to a || b
|
||||
// But we should avoid simplifying ( a && b
|
||||
if (node is Container &&
|
||||
nodeString.Length > 2 &&
|
||||
nodeString[0] == ExpressionConstants.StartParameter &&
|
||||
nodeString[nodeString.Length - 1] == ExpressionConstants.EndParameter &&
|
||||
nodeString.Count(character => character == ExpressionConstants.StartParameter) == 1 &&
|
||||
nodeString.Count(character => character == ExpressionConstants.EndParameter) == 1)
|
||||
{
|
||||
nodeString = nodeString = nodeString.Substring(1, nodeString.Length - 2);
|
||||
}
|
||||
return String.Concat(TemplateConstants.OpenExpression, " ", nodeString, " ", TemplateConstants.CloseExpression);
|
||||
}
|
||||
|
||||
[DataMember(Name = "expr", EmitDefaultValue = false)]
|
||||
private String m_expression;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class BooleanToken : LiteralToken, IBoolean
|
||||
{
|
||||
public BooleanToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
Boolean value)
|
||||
: base(TokenType.Boolean, fileId, line, column)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public Boolean Value => m_value;
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new BooleanToken(null, null, null, m_value) : new BooleanToken(FileId, Line, Column, m_value);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return m_value ? "true" : "false";
|
||||
}
|
||||
|
||||
Boolean IBoolean.GetBoolean()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
[DataMember(Name = "bool", EmitDefaultValue = false)]
|
||||
private Boolean m_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all template expression tokens
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class ExpressionToken : ScalarToken
|
||||
{
|
||||
internal ExpressionToken(
|
||||
Int32 templateType,
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
String directive)
|
||||
: base(templateType, fileId, line, column)
|
||||
{
|
||||
Directive = directive;
|
||||
}
|
||||
|
||||
[DataMember(Name = "directive", EmitDefaultValue = false)]
|
||||
internal String Directive { get; }
|
||||
|
||||
internal static Boolean IsValidExpression(
|
||||
String expression,
|
||||
String[] allowedContext,
|
||||
out Exception ex)
|
||||
{
|
||||
// Create dummy allowed contexts
|
||||
INamedValueInfo[] namedValues = null;
|
||||
if (allowedContext?.Length > 0)
|
||||
{
|
||||
namedValues = allowedContext.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
|
||||
}
|
||||
|
||||
// Parse
|
||||
Boolean result;
|
||||
ExpressionNode root = null;
|
||||
try
|
||||
{
|
||||
root = new ExpressionParser().CreateTree(expression, null, namedValues, null) as ExpressionNode;
|
||||
|
||||
result = true;
|
||||
ex = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
result = false;
|
||||
ex = exception;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class InsertExpressionToken : ExpressionToken
|
||||
{
|
||||
internal InsertExpressionToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(TokenType.InsertExpression, fileId, line, column, TemplateConstants.InsertDirective)
|
||||
{
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new InsertExpressionToken(null, null, null) : new InsertExpressionToken(FileId, Line, Column);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return $"{TemplateConstants.OpenExpression} insert {TemplateConstants.CloseExpression}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class LiteralToken : ScalarToken
|
||||
{
|
||||
public LiteralToken(
|
||||
Int32 tokenType,
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(tokenType, fileId, line, column)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class MappingToken : TemplateToken, IEnumerable<KeyValuePair<ScalarToken, TemplateToken>>, IReadOnlyObject
|
||||
{
|
||||
public MappingToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(TokenType.Mapping, fileId, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
internal Int32 Count => m_items?.Count ?? 0;
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
Int32 IReadOnlyObject.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDictionary();
|
||||
return m_dictionary.Count;
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
IEnumerable<String> IReadOnlyObject.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDictionary();
|
||||
foreach (var key in m_dictionary.Keys)
|
||||
{
|
||||
yield return key as String;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
IEnumerable<Object> IReadOnlyObject.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDictionary();
|
||||
foreach (var value in m_dictionary.Values)
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public KeyValuePair<ScalarToken, TemplateToken> this[Int32 index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_items[index];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_items[index] = value;
|
||||
m_dictionary = null;
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
Object IReadOnlyObject.this[String key]
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDictionary();
|
||||
return m_dictionary[key];
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(IEnumerable<KeyValuePair<ScalarToken, TemplateToken>> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<ScalarToken, TemplateToken> item)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<KeyValuePair<ScalarToken, TemplateToken>>();
|
||||
}
|
||||
|
||||
m_items.Add(item);
|
||||
m_dictionary = null;
|
||||
}
|
||||
|
||||
public void Add(
|
||||
ScalarToken key,
|
||||
TemplateToken value)
|
||||
{
|
||||
Add(new KeyValuePair<ScalarToken, TemplateToken>(key, value));
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
var result = omitSource ? new MappingToken(null, null, null) : new MappingToken(FileId, Line, Column);
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
foreach (var pair in m_items)
|
||||
{
|
||||
result.Add(pair.Key?.Clone() as ScalarToken, pair.Value?.Clone());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<ScalarToken, TemplateToken>> GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new List<KeyValuePair<ScalarToken, TemplateToken>>(0)).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new KeyValuePair<ScalarToken, TemplateToken>[0]).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(
|
||||
Int32 index,
|
||||
KeyValuePair<ScalarToken, TemplateToken> item)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<KeyValuePair<ScalarToken, TemplateToken>>();
|
||||
}
|
||||
|
||||
m_items.Insert(index, item);
|
||||
m_dictionary = null;
|
||||
}
|
||||
|
||||
public void Insert(
|
||||
Int32 index,
|
||||
ScalarToken key,
|
||||
TemplateToken value)
|
||||
{
|
||||
Insert(index, new KeyValuePair<ScalarToken, TemplateToken>(key, value));
|
||||
}
|
||||
|
||||
public void RemoveAt(Int32 index)
|
||||
{
|
||||
m_items.RemoveAt(index);
|
||||
m_dictionary = null;
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
Boolean IReadOnlyObject.ContainsKey(String key)
|
||||
{
|
||||
InitializeDictionary();
|
||||
return m_dictionary.Contains(key);
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
IEnumerator IReadOnlyObject.GetEnumerator()
|
||||
{
|
||||
InitializeDictionary();
|
||||
return m_dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
Boolean IReadOnlyObject.TryGetValue(
|
||||
String key,
|
||||
out Object value)
|
||||
{
|
||||
InitializeDictionary();
|
||||
if (!m_dictionary.Contains(key))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = m_dictionary[key];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the dictionary used for the expressions IReadOnlyObject interface
|
||||
/// </summary>
|
||||
private void InitializeDictionary()
|
||||
{
|
||||
if (m_dictionary == null)
|
||||
{
|
||||
m_dictionary = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
foreach (var pair in m_items)
|
||||
{
|
||||
if (pair.Key is StringToken stringToken &&
|
||||
!m_dictionary.Contains(stringToken.Value))
|
||||
{
|
||||
m_dictionary.Add(stringToken.Value, pair.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
private void OnSerializing(StreamingContext context)
|
||||
{
|
||||
if (m_items?.Count == 0)
|
||||
{
|
||||
m_items = null;
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember(Name = "map", EmitDefaultValue = false)]
|
||||
private List<KeyValuePair<ScalarToken, TemplateToken>> m_items;
|
||||
|
||||
private IDictionary m_dictionary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class NullToken : LiteralToken, INull
|
||||
{
|
||||
public NullToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(TokenType.Null, fileId, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new NullToken(null, null, null) : new NullToken(FileId, Line, Column);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class NumberToken : LiteralToken, INumber
|
||||
{
|
||||
public NumberToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
Double value)
|
||||
: base(TokenType.Number, fileId, line, column)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public Double Value => m_value;
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new NumberToken(null, null, null, m_value) : new NumberToken(FileId, Line, Column, m_value);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return m_value.ToString("G15", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
Double INumber.GetNumber()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
[DataMember(Name = "num", EmitDefaultValue = false)]
|
||||
private Double m_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class ScalarToken : TemplateToken
|
||||
{
|
||||
protected ScalarToken(
|
||||
Int32 type,
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(type, fileId, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual String ToDisplayString()
|
||||
{
|
||||
return TrimDisplayString(ToString());
|
||||
}
|
||||
|
||||
protected String TrimDisplayString(String displayString)
|
||||
{
|
||||
var firstLine = displayString.TrimStart(' ', '\t', '\r', '\n');
|
||||
var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' });
|
||||
if (firstNewLine >= 0)
|
||||
{
|
||||
firstLine = firstLine.Substring(0, firstNewLine);
|
||||
}
|
||||
return firstLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class SequenceToken : TemplateToken, IEnumerable<TemplateToken>, IReadOnlyArray
|
||||
{
|
||||
public SequenceToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(TokenType.Sequence, fileId, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public Int32 Count => m_items?.Count ?? 0;
|
||||
|
||||
public TemplateToken this[Int32 index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_items[index];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_items[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyArray (for expressions)
|
||||
Object IReadOnlyArray.this[Int32 index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_items[index];
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TemplateToken value)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<TemplateToken>();
|
||||
}
|
||||
|
||||
m_items.Add(value);
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
var result = omitSource ? new SequenceToken(null, null, null) : new SequenceToken(FileId, Line, Column);
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
foreach (var item in m_items)
|
||||
{
|
||||
result.Add(item?.Clone());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerator<TemplateToken> GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new TemplateToken[0] as IEnumerable<TemplateToken>).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new TemplateToken[0] as IEnumerable<TemplateToken>).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyArray (for expressions)
|
||||
IEnumerator IReadOnlyArray.GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new TemplateToken[0] as IEnumerable<TemplateToken>).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(
|
||||
Int32 index,
|
||||
TemplateToken item)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<TemplateToken>();
|
||||
}
|
||||
|
||||
m_items.Insert(index, item);
|
||||
}
|
||||
|
||||
public void InsertRange(
|
||||
Int32 index,
|
||||
IEnumerable<TemplateToken> items)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<TemplateToken>();
|
||||
}
|
||||
|
||||
m_items.InsertRange(index, items);
|
||||
}
|
||||
|
||||
public void RemoveAt(Int32 index)
|
||||
{
|
||||
m_items.RemoveAt(index);
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
private void OnSerializing(StreamingContext context)
|
||||
{
|
||||
if (m_items?.Count == 0)
|
||||
{
|
||||
m_items = null;
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember(Name = "seq", EmitDefaultValue = false)]
|
||||
private List<TemplateToken> m_items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class StringToken : LiteralToken, IString
|
||||
{
|
||||
public StringToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
String value)
|
||||
: base(TokenType.String, fileId, line, column)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public String Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_value == null)
|
||||
{
|
||||
m_value = String.Empty;
|
||||
}
|
||||
|
||||
return m_value;
|
||||
}
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new StringToken(null, null, null, m_value) : new StringToken(FileId, Line, Column, m_value);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return m_value ?? String.Empty;
|
||||
}
|
||||
|
||||
String IString.GetString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
private void OnSerializing(StreamingContext context)
|
||||
{
|
||||
if (m_value?.Length == 0)
|
||||
{
|
||||
m_value = null;
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember(Name = "lit", EmitDefaultValue = false)]
|
||||
private String m_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all template tokens
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonConverter(typeof(TemplateTokenJsonConverter))]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class TemplateToken
|
||||
{
|
||||
protected TemplateToken(
|
||||
Int32 type,
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
{
|
||||
Type = type;
|
||||
FileId = fileId;
|
||||
Line = line;
|
||||
Column = column;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
internal Int32? FileId { get; set; }
|
||||
|
||||
[DataMember(Name = "line", EmitDefaultValue = false)]
|
||||
internal Int32? Line { get; }
|
||||
|
||||
[DataMember(Name = "col", EmitDefaultValue = false)]
|
||||
internal Int32? Column { get; }
|
||||
|
||||
[DataMember(Name = "type", EmitDefaultValue = false)]
|
||||
internal Int32 Type { get; }
|
||||
|
||||
public TemplateToken Clone()
|
||||
{
|
||||
return Clone(false);
|
||||
}
|
||||
|
||||
public abstract TemplateToken Clone(Boolean omitSource);
|
||||
|
||||
protected StringToken EvaluateStringToken(
|
||||
TemplateContext context,
|
||||
String expression,
|
||||
out Int32 bytes)
|
||||
{
|
||||
var originalBytes = context.Memory.CurrentBytes;
|
||||
try
|
||||
{
|
||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||
var options = new EvaluationOptions
|
||||
{
|
||||
MaxMemory = context.Memory.MaxBytes,
|
||||
};
|
||||
var result = tree.Evaluate(context.TraceWriter.ToExpressionTraceWriter(), null, context, options);
|
||||
|
||||
if (result.Raw is LiteralToken literalToken)
|
||||
{
|
||||
var stringToken = new StringToken(FileId, Line, Column, literalToken.ToString());
|
||||
context.Memory.AddBytes(stringToken);
|
||||
return stringToken;
|
||||
}
|
||||
|
||||
if (!result.IsPrimitive)
|
||||
{
|
||||
context.Error(this, "Expected a string");
|
||||
return CreateStringToken(context, expression);
|
||||
}
|
||||
|
||||
var stringValue = result.Kind == ValueKind.Null ? String.Empty : result.ConvertToString();
|
||||
return CreateStringToken(context, stringValue);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bytes = context.Memory.CurrentBytes - originalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
protected SequenceToken EvaluateSequenceToken(
|
||||
TemplateContext context,
|
||||
String expression,
|
||||
out Int32 bytes)
|
||||
{
|
||||
var originalBytes = context.Memory.CurrentBytes;
|
||||
try
|
||||
{
|
||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||
var options = new EvaluationOptions
|
||||
{
|
||||
MaxMemory = context.Memory.MaxBytes,
|
||||
};
|
||||
var result = tree.Evaluate(context.TraceWriter.ToExpressionTraceWriter(), null, context, options);
|
||||
var templateToken = ConvertToTemplateToken(context, result);
|
||||
if (templateToken is SequenceToken sequence)
|
||||
{
|
||||
return sequence;
|
||||
}
|
||||
|
||||
context.Error(this, TemplateStrings.ExpectedSequence());
|
||||
return CreateSequenceToken(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bytes = context.Memory.CurrentBytes - originalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
protected MappingToken EvaluateMappingToken(
|
||||
TemplateContext context,
|
||||
String expression,
|
||||
out Int32 bytes)
|
||||
{
|
||||
var originalBytes = context.Memory.CurrentBytes;
|
||||
try
|
||||
{
|
||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||
var options = new EvaluationOptions
|
||||
{
|
||||
MaxMemory = context.Memory.MaxBytes,
|
||||
};
|
||||
var result = tree.Evaluate(context.TraceWriter.ToExpressionTraceWriter(), null, context, options);
|
||||
var templateToken = ConvertToTemplateToken(context, result);
|
||||
if (templateToken is MappingToken mapping)
|
||||
{
|
||||
return mapping;
|
||||
}
|
||||
|
||||
context.Error(this, TemplateStrings.ExpectedMapping());
|
||||
return CreateMappingToken(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bytes = context.Memory.CurrentBytes - originalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
protected TemplateToken EvaluateTemplateToken(
|
||||
TemplateContext context,
|
||||
String expression,
|
||||
out Int32 bytes)
|
||||
{
|
||||
var originalBytes = context.Memory.CurrentBytes;
|
||||
try
|
||||
{
|
||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||
var options = new EvaluationOptions
|
||||
{
|
||||
MaxMemory = context.Memory.MaxBytes,
|
||||
};
|
||||
var result = tree.Evaluate(context.TraceWriter.ToExpressionTraceWriter(), null, context, options);
|
||||
return ConvertToTemplateToken(context, result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bytes = context.Memory.CurrentBytes - originalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
private TemplateToken ConvertToTemplateToken(
|
||||
TemplateContext context,
|
||||
EvaluationResult result)
|
||||
{
|
||||
// Literal
|
||||
if (TryConvertToLiteralToken(context, result, out LiteralToken literal))
|
||||
{
|
||||
return literal;
|
||||
}
|
||||
// Known raw types
|
||||
else if (!Object.ReferenceEquals(result.Raw, null))
|
||||
{
|
||||
if (result.Raw is SequenceToken sequence)
|
||||
{
|
||||
context.Memory.AddBytes(sequence, true);
|
||||
return sequence;
|
||||
}
|
||||
else if (result.Raw is MappingToken mapping)
|
||||
{
|
||||
context.Memory.AddBytes(mapping, true);
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
// Leverage the expression SDK to traverse the object
|
||||
if (result.TryGetCollectionInterface(out Object collection))
|
||||
{
|
||||
if (collection is IReadOnlyObject dictionary)
|
||||
{
|
||||
var mapping = CreateMappingToken(context);
|
||||
|
||||
foreach (KeyValuePair<String, Object> pair in dictionary)
|
||||
{
|
||||
var keyToken = CreateStringToken(context, pair.Key);
|
||||
var valueResult = EvaluationResult.CreateIntermediateResult(null, pair.Value);
|
||||
var valueToken = ConvertToTemplateToken(context, valueResult);
|
||||
mapping.Add(keyToken, valueToken);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
else if (collection is IReadOnlyArray list)
|
||||
{
|
||||
var sequence = CreateSequenceToken(context);
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
var itemResult = EvaluationResult.CreateIntermediateResult(null, item);
|
||||
var itemToken = ConvertToTemplateToken(context, itemResult);
|
||||
sequence.Add(itemToken);
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException(TemplateStrings.UnableToConvertToTemplateToken(result.Value?.GetType().FullName));
|
||||
}
|
||||
|
||||
private Boolean TryConvertToLiteralToken(
|
||||
TemplateContext context,
|
||||
EvaluationResult result,
|
||||
out LiteralToken literal)
|
||||
{
|
||||
if (result.Raw is LiteralToken literal2)
|
||||
{
|
||||
context.Memory.AddBytes(literal2);
|
||||
literal = literal2;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (result.Kind)
|
||||
{
|
||||
case ValueKind.Null:
|
||||
literal = new NullToken(FileId, Line, Column);
|
||||
break;
|
||||
|
||||
case ValueKind.Boolean:
|
||||
literal = new BooleanToken(FileId, Line, Column, (Boolean)result.Value);
|
||||
break;
|
||||
|
||||
case ValueKind.Number:
|
||||
literal = new NumberToken(FileId, Line, Column, (Double)result.Value);
|
||||
break;
|
||||
|
||||
case ValueKind.String:
|
||||
literal = new StringToken(FileId, Line, Column, (String)result.Value);
|
||||
break;
|
||||
|
||||
default:
|
||||
literal = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
context.Memory.AddBytes(literal);
|
||||
return true;
|
||||
}
|
||||
|
||||
private StringToken CreateStringToken(
|
||||
TemplateContext context,
|
||||
String value)
|
||||
{
|
||||
var result = new StringToken(FileId, Line, Column, value);
|
||||
context.Memory.AddBytes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private SequenceToken CreateSequenceToken(TemplateContext context)
|
||||
{
|
||||
var result = new SequenceToken(FileId, Line, Column);
|
||||
context.Memory.AddBytes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private MappingToken CreateMappingToken(TemplateContext context)
|
||||
{
|
||||
var result = new MappingToken(FileId, Line, Column);
|
||||
context.Memory.AddBytes(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
public static class TemplateTokenExtensions
|
||||
{
|
||||
internal static BooleanToken AssertBoolean(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is BooleanToken booleanToken)
|
||||
{
|
||||
return booleanToken;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(BooleanToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static NullToken AssertNull(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is NullToken nullToken)
|
||||
{
|
||||
return nullToken;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(NullToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static NumberToken AssertNumber(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is NumberToken numberToken)
|
||||
{
|
||||
return numberToken;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(NumberToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static StringToken AssertString(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is StringToken stringToken)
|
||||
{
|
||||
return stringToken;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(StringToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static MappingToken AssertMapping(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is MappingToken mapping)
|
||||
{
|
||||
return mapping;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(MappingToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static void AssertNotEmpty(
|
||||
this MappingToken mapping,
|
||||
string objectDescription)
|
||||
{
|
||||
if (mapping.Count == 0)
|
||||
{
|
||||
throw new ArgumentException($"Unexpected empty mapping when reading '{objectDescription}'");
|
||||
}
|
||||
}
|
||||
|
||||
internal static ScalarToken AssertScalar(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is ScalarToken scalar)
|
||||
{
|
||||
return scalar;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(ScalarToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static SequenceToken AssertSequence(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is SequenceToken sequence)
|
||||
{
|
||||
return sequence;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(SequenceToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static void AssertUnexpectedValue(
|
||||
this LiteralToken literal,
|
||||
string objectDescription)
|
||||
{
|
||||
throw new ArgumentException($"Error while reading '{objectDescription}'. Unexpected value '{literal.ToString()}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tokens (depth first)
|
||||
/// </summary>
|
||||
public static IEnumerable<TemplateToken> Traverse(this TemplateToken token)
|
||||
{
|
||||
return Traverse(token, omitKeys: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tokens (depth first)
|
||||
/// </summary>
|
||||
public static IEnumerable<TemplateToken> Traverse(
|
||||
this TemplateToken token,
|
||||
bool omitKeys)
|
||||
{
|
||||
if (token != null)
|
||||
{
|
||||
yield return token;
|
||||
|
||||
if (token is SequenceToken || token is MappingToken)
|
||||
{
|
||||
var state = new TraversalState(null, token);
|
||||
while (state != null)
|
||||
{
|
||||
if (state.MoveNext(omitKeys))
|
||||
{
|
||||
token = state.Current;
|
||||
yield return token;
|
||||
|
||||
if (token is SequenceToken || token is MappingToken)
|
||||
{
|
||||
state = new TraversalState(state, token);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state = state.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TraversalState
|
||||
{
|
||||
public TraversalState(
|
||||
TraversalState parent,
|
||||
TemplateToken token)
|
||||
{
|
||||
Parent = parent;
|
||||
m_token = token;
|
||||
}
|
||||
|
||||
public bool MoveNext(bool omitKeys)
|
||||
{
|
||||
switch (m_token.Type)
|
||||
{
|
||||
case TokenType.Sequence:
|
||||
var sequence = m_token as SequenceToken;
|
||||
if (++m_index < sequence.Count)
|
||||
{
|
||||
Current = sequence[m_index];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
case TokenType.Mapping:
|
||||
var mapping = m_token as MappingToken;
|
||||
|
||||
// Return the value
|
||||
if (m_isKey)
|
||||
{
|
||||
m_isKey = false;
|
||||
Current = mapping[m_index].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (++m_index < mapping.Count)
|
||||
{
|
||||
// Skip the key, return the value
|
||||
if (omitKeys)
|
||||
{
|
||||
m_isKey = false;
|
||||
Current = mapping[m_index].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the key
|
||||
m_isKey = true;
|
||||
Current = mapping[m_index].Key;
|
||||
return true;
|
||||
}
|
||||
|
||||
Current = null;
|
||||
return false;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unexpected token type '{m_token.Type}'");
|
||||
}
|
||||
}
|
||||
|
||||
private TemplateToken m_token;
|
||||
private int m_index = -1;
|
||||
private bool m_isKey;
|
||||
public TemplateToken Current;
|
||||
public TraversalState Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON serializer for TemplateToken objects
|
||||
/// </summary>
|
||||
internal sealed class TemplateTokenJsonConverter : VssSecureJsonConverter
|
||||
{
|
||||
public override Boolean CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override Boolean CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(TemplateToken).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
|
||||
}
|
||||
|
||||
public override Object ReadJson(
|
||||
JsonReader reader,
|
||||
Type objectType,
|
||||
Object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.String:
|
||||
return new StringToken(null, null, null, reader.Value.ToString());
|
||||
case JsonToken.Boolean:
|
||||
return new BooleanToken(null, null, null, (Boolean)reader.Value);
|
||||
case JsonToken.Float:
|
||||
return new NumberToken(null, null, null, (Double)reader.Value);
|
||||
case JsonToken.Integer:
|
||||
return new NumberToken(null, null, null, (Double)(Int64)reader.Value);
|
||||
case JsonToken.Null:
|
||||
return new NullToken(null, null, null);
|
||||
case JsonToken.StartObject:
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
Int32? type = null;
|
||||
JObject value = JObject.Load(reader);
|
||||
if (!value.TryGetValue("type", StringComparison.OrdinalIgnoreCase, out JToken typeValue))
|
||||
{
|
||||
type = TokenType.String;
|
||||
}
|
||||
else if (typeValue.Type == JTokenType.Integer)
|
||||
{
|
||||
type = (Int32)typeValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return existingValue;
|
||||
}
|
||||
|
||||
Object newValue = null;
|
||||
switch (type)
|
||||
{
|
||||
case TokenType.Null:
|
||||
newValue = new NullToken(null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.Boolean:
|
||||
newValue = new BooleanToken(null, null, null, default(Boolean));
|
||||
break;
|
||||
|
||||
case TokenType.Number:
|
||||
newValue = new NumberToken(null, null, null, default(Double));
|
||||
break;
|
||||
|
||||
case TokenType.String:
|
||||
newValue = new StringToken(null, null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.BasicExpression:
|
||||
newValue = new BasicExpressionToken(null, null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.InsertExpression:
|
||||
newValue = new InsertExpressionToken(null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.Sequence:
|
||||
newValue = new SequenceToken(null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.Mapping:
|
||||
newValue = new MappingToken(null, null, null);
|
||||
break;
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
using (JsonReader objectReader = value.CreateReader())
|
||||
{
|
||||
serializer.Populate(objectReader, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public override void WriteJson(
|
||||
JsonWriter writer,
|
||||
Object value,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
base.WriteJson(writer, value, serializer);
|
||||
if (value is TemplateToken token)
|
||||
{
|
||||
switch (token.Type)
|
||||
{
|
||||
case TokenType.Null:
|
||||
if (token.Line == null && token.Column == null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
return;
|
||||
|
||||
case TokenType.Boolean:
|
||||
var booleanToken = token as BooleanToken;
|
||||
if (token.Line == null && token.Column == null)
|
||||
{
|
||||
writer.WriteValue(booleanToken.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WritePropertyName("bool");
|
||||
writer.WriteValue(booleanToken.Value);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
return;
|
||||
|
||||
case TokenType.Number:
|
||||
var numberToken = token as NumberToken;
|
||||
if (token.Line == null && token.Column == null)
|
||||
{
|
||||
writer.WriteValue(numberToken.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WritePropertyName("num");
|
||||
writer.WriteValue(numberToken.Value);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
return;
|
||||
|
||||
case TokenType.String:
|
||||
var stringToken = token as StringToken;
|
||||
if (token.Line == null && token.Column == null)
|
||||
{
|
||||
writer.WriteValue(stringToken.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WritePropertyName("lit");
|
||||
writer.WriteValue(stringToken.Value);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
return;
|
||||
|
||||
case TokenType.BasicExpression:
|
||||
var basicExpressionToken = token as BasicExpressionToken;
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
if (!String.IsNullOrEmpty(basicExpressionToken.Expression))
|
||||
{
|
||||
writer.WritePropertyName("expr");
|
||||
writer.WriteValue(basicExpressionToken.Expression);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
|
||||
case TokenType.InsertExpression:
|
||||
var insertExpressionToken = token as InsertExpressionToken;
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WritePropertyName("directive");
|
||||
writer.WriteValue(insertExpressionToken.Directive);
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
|
||||
case TokenType.Sequence:
|
||||
var sequenceToken = token as SequenceToken;
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
if (sequenceToken.Count > 0)
|
||||
{
|
||||
writer.WritePropertyName("seq");
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in sequenceToken)
|
||||
{
|
||||
serializer.Serialize(writer, item);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
|
||||
case TokenType.Mapping:
|
||||
var mappingToken = token as MappingToken;
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
if (mappingToken.Count > 0)
|
||||
{
|
||||
writer.WritePropertyName("map");
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in mappingToken)
|
||||
{
|
||||
serializer.Serialize(writer, item);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"Unexpected type '{value?.GetType().FullName}' when serializing template token");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
internal static class TokenType
|
||||
{
|
||||
internal const Int32 String = 0;
|
||||
|
||||
internal const Int32 Sequence = 1;
|
||||
|
||||
internal const Int32 Mapping = 2;
|
||||
|
||||
internal const Int32 BasicExpression = 3;
|
||||
|
||||
internal const Int32 InsertExpression = 4;
|
||||
|
||||
internal const Int32 Boolean = 5;
|
||||
|
||||
internal const Int32 Number = 6;
|
||||
|
||||
internal const Int32 Null = 7;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user