mirror of
https://github.com/actions/runner.git
synced 2025-12-17 07:54:19 +00:00
GitHub Actions Runner
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk.Functions;
|
||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
using Container = GitHub.DistributedTask.Expressions2.Sdk.Container;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class BasicExpressionToken : ExpressionToken
|
||||
{
|
||||
internal BasicExpressionToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
String expression)
|
||||
: base(TokenType.BasicExpression, fileId, line, column, null)
|
||||
{
|
||||
m_expression = expression;
|
||||
}
|
||||
|
||||
internal String Expression
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_expression == null)
|
||||
{
|
||||
m_expression = String.Empty;
|
||||
}
|
||||
|
||||
return m_expression;
|
||||
}
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new BasicExpressionToken(null, null, null, m_expression) : new BasicExpressionToken(FileId, Line, Column, m_expression);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return $"{TemplateConstants.OpenExpression} {m_expression} {TemplateConstants.CloseExpression}";
|
||||
}
|
||||
|
||||
public override String ToDisplayString()
|
||||
{
|
||||
var expressionParser = new ExpressionParser();
|
||||
var expressionNode = expressionParser.ValidateSyntax(Expression, null);
|
||||
if (expressionNode is Format formatNode)
|
||||
{
|
||||
// Make sure our first item is indeed a literal string so we can format it.
|
||||
if (formatNode.Parameters.Count > 1 &&
|
||||
formatNode.Parameters.First() is Literal literalValueNode &&
|
||||
literalValueNode.Kind == ValueKind.String)
|
||||
{
|
||||
// Get all other Parameters san the formatted string to pass into the formatter
|
||||
var formatParameters = formatNode.Parameters.Skip(1).Select(x => this.ConvertFormatParameterToExpression(x)).ToArray();
|
||||
if (formatParameters.Length > 0)
|
||||
{
|
||||
String formattedString = String.Empty;
|
||||
try
|
||||
{
|
||||
formattedString = String.Format(CultureInfo.InvariantCulture, (formatNode.Parameters[0] as Literal).Value as String, formatParameters);
|
||||
}
|
||||
catch (FormatException) { }
|
||||
catch (ArgumentNullException) { } // If this operation fails, revert to default display name
|
||||
if (!String.IsNullOrEmpty(formattedString))
|
||||
{
|
||||
return TrimDisplayString(formattedString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return base.ToDisplayString();
|
||||
}
|
||||
|
||||
internal StringToken EvaluateStringToken(
|
||||
TemplateContext context,
|
||||
out Int32 bytes)
|
||||
{
|
||||
return EvaluateStringToken(context, Expression, out bytes);
|
||||
}
|
||||
|
||||
internal MappingToken EvaluateMappingToken(
|
||||
TemplateContext context,
|
||||
out Int32 bytes)
|
||||
{
|
||||
return EvaluateMappingToken(context, Expression, out bytes);
|
||||
}
|
||||
|
||||
internal SequenceToken EvaluateSequenceToken(
|
||||
TemplateContext context,
|
||||
out Int32 bytes)
|
||||
{
|
||||
return EvaluateSequenceToken(context, Expression, out bytes);
|
||||
}
|
||||
|
||||
internal TemplateToken EvaluateTemplateToken(
|
||||
TemplateContext context,
|
||||
out Int32 bytes)
|
||||
{
|
||||
return EvaluateTemplateToken(context, Expression, out bytes);
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
private void OnSerializing(StreamingContext context)
|
||||
{
|
||||
if (m_expression?.Length == 0)
|
||||
{
|
||||
m_expression = null;
|
||||
}
|
||||
}
|
||||
|
||||
private String ConvertFormatParameterToExpression(ExpressionNode node)
|
||||
{
|
||||
var nodeString = node.ConvertToExpression();
|
||||
|
||||
// If the node is a container, see if it starts with '(' and ends with ')' so we can simplify the string
|
||||
// Should only simplify if only one '(' or ')' exists in the string
|
||||
// We are trying to simplify the case (a || b) to a || b
|
||||
// But we should avoid simplifying ( a && b
|
||||
if (node is Container &&
|
||||
nodeString.Length > 2 &&
|
||||
nodeString[0] == ExpressionConstants.StartParameter &&
|
||||
nodeString[nodeString.Length - 1] == ExpressionConstants.EndParameter &&
|
||||
nodeString.Count(character => character == ExpressionConstants.StartParameter) == 1 &&
|
||||
nodeString.Count(character => character == ExpressionConstants.EndParameter) == 1)
|
||||
{
|
||||
nodeString = nodeString = nodeString.Substring(1, nodeString.Length - 2);
|
||||
}
|
||||
return String.Concat(TemplateConstants.OpenExpression, " ", nodeString, " ", TemplateConstants.CloseExpression);
|
||||
}
|
||||
|
||||
[DataMember(Name = "expr", EmitDefaultValue = false)]
|
||||
private String m_expression;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class BooleanToken : LiteralToken, IBoolean
|
||||
{
|
||||
public BooleanToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
Boolean value)
|
||||
: base(TokenType.Boolean, fileId, line, column)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public Boolean Value => m_value;
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new BooleanToken(null, null, null, m_value) : new BooleanToken(FileId, Line, Column, m_value);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return m_value ? "true" : "false";
|
||||
}
|
||||
|
||||
Boolean IBoolean.GetBoolean()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
[DataMember(Name = "bool", EmitDefaultValue = false)]
|
||||
private Boolean m_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all template expression tokens
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class ExpressionToken : ScalarToken
|
||||
{
|
||||
internal ExpressionToken(
|
||||
Int32 templateType,
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
String directive)
|
||||
: base(templateType, fileId, line, column)
|
||||
{
|
||||
Directive = directive;
|
||||
}
|
||||
|
||||
[DataMember(Name = "directive", EmitDefaultValue = false)]
|
||||
internal String Directive { get; }
|
||||
|
||||
internal static Boolean IsValidExpression(
|
||||
String expression,
|
||||
String[] allowedContext,
|
||||
out Exception ex)
|
||||
{
|
||||
// Create dummy allowed contexts
|
||||
INamedValueInfo[] namedValues = null;
|
||||
if (allowedContext?.Length > 0)
|
||||
{
|
||||
namedValues = allowedContext.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
|
||||
}
|
||||
|
||||
// Parse
|
||||
Boolean result;
|
||||
ExpressionNode root = null;
|
||||
try
|
||||
{
|
||||
root = new ExpressionParser().CreateTree(expression, null, namedValues, null) as ExpressionNode;
|
||||
|
||||
result = true;
|
||||
ex = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
result = false;
|
||||
ex = exception;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class InsertExpressionToken : ExpressionToken
|
||||
{
|
||||
internal InsertExpressionToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(TokenType.InsertExpression, fileId, line, column, TemplateConstants.InsertDirective)
|
||||
{
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new InsertExpressionToken(null, null, null) : new InsertExpressionToken(FileId, Line, Column);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return $"{TemplateConstants.OpenExpression} insert {TemplateConstants.CloseExpression}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class LiteralToken : ScalarToken
|
||||
{
|
||||
public LiteralToken(
|
||||
Int32 tokenType,
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(tokenType, fileId, line, column)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class MappingToken : TemplateToken, IEnumerable<KeyValuePair<ScalarToken, TemplateToken>>, IReadOnlyObject
|
||||
{
|
||||
public MappingToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(TokenType.Mapping, fileId, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
internal Int32 Count => m_items?.Count ?? 0;
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
Int32 IReadOnlyObject.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDictionary();
|
||||
return m_dictionary.Count;
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
IEnumerable<String> IReadOnlyObject.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDictionary();
|
||||
foreach (var key in m_dictionary.Keys)
|
||||
{
|
||||
yield return key as String;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
IEnumerable<Object> IReadOnlyObject.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDictionary();
|
||||
foreach (var value in m_dictionary.Values)
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public KeyValuePair<ScalarToken, TemplateToken> this[Int32 index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_items[index];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_items[index] = value;
|
||||
m_dictionary = null;
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
Object IReadOnlyObject.this[String key]
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeDictionary();
|
||||
return m_dictionary[key];
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(IEnumerable<KeyValuePair<ScalarToken, TemplateToken>> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<ScalarToken, TemplateToken> item)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<KeyValuePair<ScalarToken, TemplateToken>>();
|
||||
}
|
||||
|
||||
m_items.Add(item);
|
||||
m_dictionary = null;
|
||||
}
|
||||
|
||||
public void Add(
|
||||
ScalarToken key,
|
||||
TemplateToken value)
|
||||
{
|
||||
Add(new KeyValuePair<ScalarToken, TemplateToken>(key, value));
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
var result = omitSource ? new MappingToken(null, null, null) : new MappingToken(FileId, Line, Column);
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
foreach (var pair in m_items)
|
||||
{
|
||||
result.Add(pair.Key?.Clone() as ScalarToken, pair.Value?.Clone());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<ScalarToken, TemplateToken>> GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new List<KeyValuePair<ScalarToken, TemplateToken>>(0)).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new KeyValuePair<ScalarToken, TemplateToken>[0]).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(
|
||||
Int32 index,
|
||||
KeyValuePair<ScalarToken, TemplateToken> item)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<KeyValuePair<ScalarToken, TemplateToken>>();
|
||||
}
|
||||
|
||||
m_items.Insert(index, item);
|
||||
m_dictionary = null;
|
||||
}
|
||||
|
||||
public void Insert(
|
||||
Int32 index,
|
||||
ScalarToken key,
|
||||
TemplateToken value)
|
||||
{
|
||||
Insert(index, new KeyValuePair<ScalarToken, TemplateToken>(key, value));
|
||||
}
|
||||
|
||||
public void RemoveAt(Int32 index)
|
||||
{
|
||||
m_items.RemoveAt(index);
|
||||
m_dictionary = null;
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
Boolean IReadOnlyObject.ContainsKey(String key)
|
||||
{
|
||||
InitializeDictionary();
|
||||
return m_dictionary.Contains(key);
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
IEnumerator IReadOnlyObject.GetEnumerator()
|
||||
{
|
||||
InitializeDictionary();
|
||||
return m_dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
// IReadOnlyObject (for expressions)
|
||||
Boolean IReadOnlyObject.TryGetValue(
|
||||
String key,
|
||||
out Object value)
|
||||
{
|
||||
InitializeDictionary();
|
||||
if (!m_dictionary.Contains(key))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = m_dictionary[key];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the dictionary used for the expressions IReadOnlyObject interface
|
||||
/// </summary>
|
||||
private void InitializeDictionary()
|
||||
{
|
||||
if (m_dictionary == null)
|
||||
{
|
||||
m_dictionary = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
foreach (var pair in m_items)
|
||||
{
|
||||
if (pair.Key is StringToken stringToken &&
|
||||
!m_dictionary.Contains(stringToken.Value))
|
||||
{
|
||||
m_dictionary.Add(stringToken.Value, pair.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
private void OnSerializing(StreamingContext context)
|
||||
{
|
||||
if (m_items?.Count == 0)
|
||||
{
|
||||
m_items = null;
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember(Name = "map", EmitDefaultValue = false)]
|
||||
private List<KeyValuePair<ScalarToken, TemplateToken>> m_items;
|
||||
|
||||
private IDictionary m_dictionary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class NullToken : LiteralToken, INull
|
||||
{
|
||||
public NullToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(TokenType.Null, fileId, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new NullToken(null, null, null) : new NullToken(FileId, Line, Column);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class NumberToken : LiteralToken, INumber
|
||||
{
|
||||
public NumberToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
Double value)
|
||||
: base(TokenType.Number, fileId, line, column)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public Double Value => m_value;
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new NumberToken(null, null, null, m_value) : new NumberToken(FileId, Line, Column, m_value);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return m_value.ToString("G15", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
Double INumber.GetNumber()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
[DataMember(Name = "num", EmitDefaultValue = false)]
|
||||
private Double m_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class ScalarToken : TemplateToken
|
||||
{
|
||||
protected ScalarToken(
|
||||
Int32 type,
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(type, fileId, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual String ToDisplayString()
|
||||
{
|
||||
return TrimDisplayString(ToString());
|
||||
}
|
||||
|
||||
protected String TrimDisplayString(String displayString)
|
||||
{
|
||||
var firstLine = displayString.TrimStart(' ', '\t', '\r', '\n');
|
||||
var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' });
|
||||
if (firstNewLine >= 0)
|
||||
{
|
||||
firstLine = firstLine.Substring(0, firstNewLine);
|
||||
}
|
||||
return firstLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class SequenceToken : TemplateToken, IEnumerable<TemplateToken>, IReadOnlyArray
|
||||
{
|
||||
public SequenceToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
: base(TokenType.Sequence, fileId, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public Int32 Count => m_items?.Count ?? 0;
|
||||
|
||||
public TemplateToken this[Int32 index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_items[index];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_items[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyArray (for expressions)
|
||||
Object IReadOnlyArray.this[Int32 index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_items[index];
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TemplateToken value)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<TemplateToken>();
|
||||
}
|
||||
|
||||
m_items.Add(value);
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
var result = omitSource ? new SequenceToken(null, null, null) : new SequenceToken(FileId, Line, Column);
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
foreach (var item in m_items)
|
||||
{
|
||||
result.Add(item?.Clone());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerator<TemplateToken> GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new TemplateToken[0] as IEnumerable<TemplateToken>).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new TemplateToken[0] as IEnumerable<TemplateToken>).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
// IReadOnlyArray (for expressions)
|
||||
IEnumerator IReadOnlyArray.GetEnumerator()
|
||||
{
|
||||
if (m_items?.Count > 0)
|
||||
{
|
||||
return m_items.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (new TemplateToken[0] as IEnumerable<TemplateToken>).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(
|
||||
Int32 index,
|
||||
TemplateToken item)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<TemplateToken>();
|
||||
}
|
||||
|
||||
m_items.Insert(index, item);
|
||||
}
|
||||
|
||||
public void InsertRange(
|
||||
Int32 index,
|
||||
IEnumerable<TemplateToken> items)
|
||||
{
|
||||
if (m_items == null)
|
||||
{
|
||||
m_items = new List<TemplateToken>();
|
||||
}
|
||||
|
||||
m_items.InsertRange(index, items);
|
||||
}
|
||||
|
||||
public void RemoveAt(Int32 index)
|
||||
{
|
||||
m_items.RemoveAt(index);
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
private void OnSerializing(StreamingContext context)
|
||||
{
|
||||
if (m_items?.Count == 0)
|
||||
{
|
||||
m_items = null;
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember(Name = "seq", EmitDefaultValue = false)]
|
||||
private List<TemplateToken> m_items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
[DataContract]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class StringToken : LiteralToken, IString
|
||||
{
|
||||
public StringToken(
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column,
|
||||
String value)
|
||||
: base(TokenType.String, fileId, line, column)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
public String Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_value == null)
|
||||
{
|
||||
m_value = String.Empty;
|
||||
}
|
||||
|
||||
return m_value;
|
||||
}
|
||||
}
|
||||
|
||||
public override TemplateToken Clone(Boolean omitSource)
|
||||
{
|
||||
return omitSource ? new StringToken(null, null, null, m_value) : new StringToken(FileId, Line, Column, m_value);
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
return m_value ?? String.Empty;
|
||||
}
|
||||
|
||||
String IString.GetString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
private void OnSerializing(StreamingContext context)
|
||||
{
|
||||
if (m_value?.Length == 0)
|
||||
{
|
||||
m_value = null;
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember(Name = "lit", EmitDefaultValue = false)]
|
||||
private String m_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all template tokens
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonConverter(typeof(TemplateTokenJsonConverter))]
|
||||
[ClientIgnore]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class TemplateToken
|
||||
{
|
||||
protected TemplateToken(
|
||||
Int32 type,
|
||||
Int32? fileId,
|
||||
Int32? line,
|
||||
Int32? column)
|
||||
{
|
||||
Type = type;
|
||||
FileId = fileId;
|
||||
Line = line;
|
||||
Column = column;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
internal Int32? FileId { get; set; }
|
||||
|
||||
[DataMember(Name = "line", EmitDefaultValue = false)]
|
||||
internal Int32? Line { get; }
|
||||
|
||||
[DataMember(Name = "col", EmitDefaultValue = false)]
|
||||
internal Int32? Column { get; }
|
||||
|
||||
[DataMember(Name = "type", EmitDefaultValue = false)]
|
||||
internal Int32 Type { get; }
|
||||
|
||||
public TemplateToken Clone()
|
||||
{
|
||||
return Clone(false);
|
||||
}
|
||||
|
||||
public abstract TemplateToken Clone(Boolean omitSource);
|
||||
|
||||
protected StringToken EvaluateStringToken(
|
||||
TemplateContext context,
|
||||
String expression,
|
||||
out Int32 bytes)
|
||||
{
|
||||
var originalBytes = context.Memory.CurrentBytes;
|
||||
try
|
||||
{
|
||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||
var options = new EvaluationOptions
|
||||
{
|
||||
MaxMemory = context.Memory.MaxBytes,
|
||||
};
|
||||
var result = tree.Evaluate(context.TraceWriter.ToExpressionTraceWriter(), null, context, options);
|
||||
|
||||
if (result.Raw is LiteralToken literalToken)
|
||||
{
|
||||
var stringToken = new StringToken(FileId, Line, Column, literalToken.ToString());
|
||||
context.Memory.AddBytes(stringToken);
|
||||
return stringToken;
|
||||
}
|
||||
|
||||
if (!result.IsPrimitive)
|
||||
{
|
||||
context.Error(this, "Expected a string");
|
||||
return CreateStringToken(context, expression);
|
||||
}
|
||||
|
||||
var stringValue = result.Kind == ValueKind.Null ? String.Empty : result.ConvertToString();
|
||||
return CreateStringToken(context, stringValue);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bytes = context.Memory.CurrentBytes - originalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
protected SequenceToken EvaluateSequenceToken(
|
||||
TemplateContext context,
|
||||
String expression,
|
||||
out Int32 bytes)
|
||||
{
|
||||
var originalBytes = context.Memory.CurrentBytes;
|
||||
try
|
||||
{
|
||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||
var options = new EvaluationOptions
|
||||
{
|
||||
MaxMemory = context.Memory.MaxBytes,
|
||||
};
|
||||
var result = tree.Evaluate(context.TraceWriter.ToExpressionTraceWriter(), null, context, options);
|
||||
var templateToken = ConvertToTemplateToken(context, result);
|
||||
if (templateToken is SequenceToken sequence)
|
||||
{
|
||||
return sequence;
|
||||
}
|
||||
|
||||
context.Error(this, TemplateStrings.ExpectedSequence());
|
||||
return CreateSequenceToken(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bytes = context.Memory.CurrentBytes - originalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
protected MappingToken EvaluateMappingToken(
|
||||
TemplateContext context,
|
||||
String expression,
|
||||
out Int32 bytes)
|
||||
{
|
||||
var originalBytes = context.Memory.CurrentBytes;
|
||||
try
|
||||
{
|
||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||
var options = new EvaluationOptions
|
||||
{
|
||||
MaxMemory = context.Memory.MaxBytes,
|
||||
};
|
||||
var result = tree.Evaluate(context.TraceWriter.ToExpressionTraceWriter(), null, context, options);
|
||||
var templateToken = ConvertToTemplateToken(context, result);
|
||||
if (templateToken is MappingToken mapping)
|
||||
{
|
||||
return mapping;
|
||||
}
|
||||
|
||||
context.Error(this, TemplateStrings.ExpectedMapping());
|
||||
return CreateMappingToken(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bytes = context.Memory.CurrentBytes - originalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
protected TemplateToken EvaluateTemplateToken(
|
||||
TemplateContext context,
|
||||
String expression,
|
||||
out Int32 bytes)
|
||||
{
|
||||
var originalBytes = context.Memory.CurrentBytes;
|
||||
try
|
||||
{
|
||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
||||
var options = new EvaluationOptions
|
||||
{
|
||||
MaxMemory = context.Memory.MaxBytes,
|
||||
};
|
||||
var result = tree.Evaluate(context.TraceWriter.ToExpressionTraceWriter(), null, context, options);
|
||||
return ConvertToTemplateToken(context, result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bytes = context.Memory.CurrentBytes - originalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
private TemplateToken ConvertToTemplateToken(
|
||||
TemplateContext context,
|
||||
EvaluationResult result)
|
||||
{
|
||||
// Literal
|
||||
if (TryConvertToLiteralToken(context, result, out LiteralToken literal))
|
||||
{
|
||||
return literal;
|
||||
}
|
||||
// Known raw types
|
||||
else if (!Object.ReferenceEquals(result.Raw, null))
|
||||
{
|
||||
if (result.Raw is SequenceToken sequence)
|
||||
{
|
||||
context.Memory.AddBytes(sequence, true);
|
||||
return sequence;
|
||||
}
|
||||
else if (result.Raw is MappingToken mapping)
|
||||
{
|
||||
context.Memory.AddBytes(mapping, true);
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
// Leverage the expression SDK to traverse the object
|
||||
if (result.TryGetCollectionInterface(out Object collection))
|
||||
{
|
||||
if (collection is IReadOnlyObject dictionary)
|
||||
{
|
||||
var mapping = CreateMappingToken(context);
|
||||
|
||||
foreach (KeyValuePair<String, Object> pair in dictionary)
|
||||
{
|
||||
var keyToken = CreateStringToken(context, pair.Key);
|
||||
var valueResult = EvaluationResult.CreateIntermediateResult(null, pair.Value);
|
||||
var valueToken = ConvertToTemplateToken(context, valueResult);
|
||||
mapping.Add(keyToken, valueToken);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
else if (collection is IReadOnlyArray list)
|
||||
{
|
||||
var sequence = CreateSequenceToken(context);
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
var itemResult = EvaluationResult.CreateIntermediateResult(null, item);
|
||||
var itemToken = ConvertToTemplateToken(context, itemResult);
|
||||
sequence.Add(itemToken);
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException(TemplateStrings.UnableToConvertToTemplateToken(result.Value?.GetType().FullName));
|
||||
}
|
||||
|
||||
private Boolean TryConvertToLiteralToken(
|
||||
TemplateContext context,
|
||||
EvaluationResult result,
|
||||
out LiteralToken literal)
|
||||
{
|
||||
if (result.Raw is LiteralToken literal2)
|
||||
{
|
||||
context.Memory.AddBytes(literal2);
|
||||
literal = literal2;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (result.Kind)
|
||||
{
|
||||
case ValueKind.Null:
|
||||
literal = new NullToken(FileId, Line, Column);
|
||||
break;
|
||||
|
||||
case ValueKind.Boolean:
|
||||
literal = new BooleanToken(FileId, Line, Column, (Boolean)result.Value);
|
||||
break;
|
||||
|
||||
case ValueKind.Number:
|
||||
literal = new NumberToken(FileId, Line, Column, (Double)result.Value);
|
||||
break;
|
||||
|
||||
case ValueKind.String:
|
||||
literal = new StringToken(FileId, Line, Column, (String)result.Value);
|
||||
break;
|
||||
|
||||
default:
|
||||
literal = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
context.Memory.AddBytes(literal);
|
||||
return true;
|
||||
}
|
||||
|
||||
private StringToken CreateStringToken(
|
||||
TemplateContext context,
|
||||
String value)
|
||||
{
|
||||
var result = new StringToken(FileId, Line, Column, value);
|
||||
context.Memory.AddBytes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private SequenceToken CreateSequenceToken(TemplateContext context)
|
||||
{
|
||||
var result = new SequenceToken(FileId, Line, Column);
|
||||
context.Memory.AddBytes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private MappingToken CreateMappingToken(TemplateContext context)
|
||||
{
|
||||
var result = new MappingToken(FileId, Line, Column);
|
||||
context.Memory.AddBytes(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
public static class TemplateTokenExtensions
|
||||
{
|
||||
internal static BooleanToken AssertBoolean(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is BooleanToken booleanToken)
|
||||
{
|
||||
return booleanToken;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(BooleanToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static NullToken AssertNull(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is NullToken nullToken)
|
||||
{
|
||||
return nullToken;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(NullToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static NumberToken AssertNumber(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is NumberToken numberToken)
|
||||
{
|
||||
return numberToken;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(NumberToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static StringToken AssertString(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is StringToken stringToken)
|
||||
{
|
||||
return stringToken;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(StringToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static MappingToken AssertMapping(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is MappingToken mapping)
|
||||
{
|
||||
return mapping;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(MappingToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static void AssertNotEmpty(
|
||||
this MappingToken mapping,
|
||||
string objectDescription)
|
||||
{
|
||||
if (mapping.Count == 0)
|
||||
{
|
||||
throw new ArgumentException($"Unexpected empty mapping when reading '{objectDescription}'");
|
||||
}
|
||||
}
|
||||
|
||||
internal static ScalarToken AssertScalar(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is ScalarToken scalar)
|
||||
{
|
||||
return scalar;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(ScalarToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static SequenceToken AssertSequence(
|
||||
this TemplateToken value,
|
||||
string objectDescription)
|
||||
{
|
||||
if (value is SequenceToken sequence)
|
||||
{
|
||||
return sequence;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(SequenceToken)}' was expected.");
|
||||
}
|
||||
|
||||
internal static void AssertUnexpectedValue(
|
||||
this LiteralToken literal,
|
||||
string objectDescription)
|
||||
{
|
||||
throw new ArgumentException($"Error while reading '{objectDescription}'. Unexpected value '{literal.ToString()}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tokens (depth first)
|
||||
/// </summary>
|
||||
public static IEnumerable<TemplateToken> Traverse(this TemplateToken token)
|
||||
{
|
||||
return Traverse(token, omitKeys: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all tokens (depth first)
|
||||
/// </summary>
|
||||
public static IEnumerable<TemplateToken> Traverse(
|
||||
this TemplateToken token,
|
||||
bool omitKeys)
|
||||
{
|
||||
if (token != null)
|
||||
{
|
||||
yield return token;
|
||||
|
||||
if (token is SequenceToken || token is MappingToken)
|
||||
{
|
||||
var state = new TraversalState(null, token);
|
||||
while (state != null)
|
||||
{
|
||||
if (state.MoveNext(omitKeys))
|
||||
{
|
||||
token = state.Current;
|
||||
yield return token;
|
||||
|
||||
if (token is SequenceToken || token is MappingToken)
|
||||
{
|
||||
state = new TraversalState(state, token);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state = state.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TraversalState
|
||||
{
|
||||
public TraversalState(
|
||||
TraversalState parent,
|
||||
TemplateToken token)
|
||||
{
|
||||
Parent = parent;
|
||||
m_token = token;
|
||||
}
|
||||
|
||||
public bool MoveNext(bool omitKeys)
|
||||
{
|
||||
switch (m_token.Type)
|
||||
{
|
||||
case TokenType.Sequence:
|
||||
var sequence = m_token as SequenceToken;
|
||||
if (++m_index < sequence.Count)
|
||||
{
|
||||
Current = sequence[m_index];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
case TokenType.Mapping:
|
||||
var mapping = m_token as MappingToken;
|
||||
|
||||
// Return the value
|
||||
if (m_isKey)
|
||||
{
|
||||
m_isKey = false;
|
||||
Current = mapping[m_index].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (++m_index < mapping.Count)
|
||||
{
|
||||
// Skip the key, return the value
|
||||
if (omitKeys)
|
||||
{
|
||||
m_isKey = false;
|
||||
Current = mapping[m_index].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the key
|
||||
m_isKey = true;
|
||||
Current = mapping[m_index].Key;
|
||||
return true;
|
||||
}
|
||||
|
||||
Current = null;
|
||||
return false;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unexpected token type '{m_token.Type}'");
|
||||
}
|
||||
}
|
||||
|
||||
private TemplateToken m_token;
|
||||
private int m_index = -1;
|
||||
private bool m_isKey;
|
||||
public TemplateToken Current;
|
||||
public TraversalState Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON serializer for TemplateToken objects
|
||||
/// </summary>
|
||||
internal sealed class TemplateTokenJsonConverter : VssSecureJsonConverter
|
||||
{
|
||||
public override Boolean CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override Boolean CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(TemplateToken).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
|
||||
}
|
||||
|
||||
public override Object ReadJson(
|
||||
JsonReader reader,
|
||||
Type objectType,
|
||||
Object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.String:
|
||||
return new StringToken(null, null, null, reader.Value.ToString());
|
||||
case JsonToken.Boolean:
|
||||
return new BooleanToken(null, null, null, (Boolean)reader.Value);
|
||||
case JsonToken.Float:
|
||||
return new NumberToken(null, null, null, (Double)reader.Value);
|
||||
case JsonToken.Integer:
|
||||
return new NumberToken(null, null, null, (Double)(Int64)reader.Value);
|
||||
case JsonToken.Null:
|
||||
return new NullToken(null, null, null);
|
||||
case JsonToken.StartObject:
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
Int32? type = null;
|
||||
JObject value = JObject.Load(reader);
|
||||
if (!value.TryGetValue("type", StringComparison.OrdinalIgnoreCase, out JToken typeValue))
|
||||
{
|
||||
type = TokenType.String;
|
||||
}
|
||||
else if (typeValue.Type == JTokenType.Integer)
|
||||
{
|
||||
type = (Int32)typeValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return existingValue;
|
||||
}
|
||||
|
||||
Object newValue = null;
|
||||
switch (type)
|
||||
{
|
||||
case TokenType.Null:
|
||||
newValue = new NullToken(null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.Boolean:
|
||||
newValue = new BooleanToken(null, null, null, default(Boolean));
|
||||
break;
|
||||
|
||||
case TokenType.Number:
|
||||
newValue = new NumberToken(null, null, null, default(Double));
|
||||
break;
|
||||
|
||||
case TokenType.String:
|
||||
newValue = new StringToken(null, null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.BasicExpression:
|
||||
newValue = new BasicExpressionToken(null, null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.InsertExpression:
|
||||
newValue = new InsertExpressionToken(null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.Sequence:
|
||||
newValue = new SequenceToken(null, null, null);
|
||||
break;
|
||||
|
||||
case TokenType.Mapping:
|
||||
newValue = new MappingToken(null, null, null);
|
||||
break;
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
using (JsonReader objectReader = value.CreateReader())
|
||||
{
|
||||
serializer.Populate(objectReader, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public override void WriteJson(
|
||||
JsonWriter writer,
|
||||
Object value,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
base.WriteJson(writer, value, serializer);
|
||||
if (value is TemplateToken token)
|
||||
{
|
||||
switch (token.Type)
|
||||
{
|
||||
case TokenType.Null:
|
||||
if (token.Line == null && token.Column == null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
return;
|
||||
|
||||
case TokenType.Boolean:
|
||||
var booleanToken = token as BooleanToken;
|
||||
if (token.Line == null && token.Column == null)
|
||||
{
|
||||
writer.WriteValue(booleanToken.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WritePropertyName("bool");
|
||||
writer.WriteValue(booleanToken.Value);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
return;
|
||||
|
||||
case TokenType.Number:
|
||||
var numberToken = token as NumberToken;
|
||||
if (token.Line == null && token.Column == null)
|
||||
{
|
||||
writer.WriteValue(numberToken.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WritePropertyName("num");
|
||||
writer.WriteValue(numberToken.Value);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
return;
|
||||
|
||||
case TokenType.String:
|
||||
var stringToken = token as StringToken;
|
||||
if (token.Line == null && token.Column == null)
|
||||
{
|
||||
writer.WriteValue(stringToken.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WritePropertyName("lit");
|
||||
writer.WriteValue(stringToken.Value);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
return;
|
||||
|
||||
case TokenType.BasicExpression:
|
||||
var basicExpressionToken = token as BasicExpressionToken;
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
if (!String.IsNullOrEmpty(basicExpressionToken.Expression))
|
||||
{
|
||||
writer.WritePropertyName("expr");
|
||||
writer.WriteValue(basicExpressionToken.Expression);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
|
||||
case TokenType.InsertExpression:
|
||||
var insertExpressionToken = token as InsertExpressionToken;
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
writer.WritePropertyName("directive");
|
||||
writer.WriteValue(insertExpressionToken.Directive);
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
|
||||
case TokenType.Sequence:
|
||||
var sequenceToken = token as SequenceToken;
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
if (sequenceToken.Count > 0)
|
||||
{
|
||||
writer.WritePropertyName("seq");
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in sequenceToken)
|
||||
{
|
||||
serializer.Serialize(writer, item);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
|
||||
case TokenType.Mapping:
|
||||
var mappingToken = token as MappingToken;
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(token.Type);
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("line");
|
||||
writer.WriteValue(token.Line);
|
||||
}
|
||||
if (token.Line != null)
|
||||
{
|
||||
writer.WritePropertyName("col");
|
||||
writer.WriteValue(token.Column);
|
||||
}
|
||||
if (mappingToken.Count > 0)
|
||||
{
|
||||
writer.WritePropertyName("map");
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in mappingToken)
|
||||
{
|
||||
serializer.Serialize(writer, item);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"Unexpected type '{value?.GetType().FullName}' when serializing template token");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||
{
|
||||
internal static class TokenType
|
||||
{
|
||||
internal const Int32 String = 0;
|
||||
|
||||
internal const Int32 Sequence = 1;
|
||||
|
||||
internal const Int32 Mapping = 2;
|
||||
|
||||
internal const Int32 BasicExpression = 3;
|
||||
|
||||
internal const Int32 InsertExpression = 4;
|
||||
|
||||
internal const Int32 Boolean = 5;
|
||||
|
||||
internal const Int32 Number = 6;
|
||||
|
||||
internal const Int32 Null = 7;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user