GitHub Actions Runner

This commit is contained in:
Tingluo Huang
2019-10-10 00:52:42 -04:00
commit c8afc84840
1255 changed files with 198670 additions and 0 deletions

View File

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

View File

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

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
namespace GitHub.DistributedTask.ObjectTemplating.Schema
{
internal enum DefinitionType
{
Null,
Boolean,
Number,
String,
Sequence,
Mapping,
OneOf,
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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