2019-10-10 00:52:42 -04:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using GitHub.DistributedTask.ObjectTemplating.Tokens ;
using Newtonsoft.Json.Linq ;
namespace GitHub.DistributedTask.Pipelines.ContextData
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static class PipelineContextDataExtensions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static ArrayContextData AssertArray (
this PipelineContextData value ,
String objectDescription )
{
if ( value is ArrayContextData array )
{
return array ;
}
throw new ArgumentException ( $"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(ArrayContextData)}' was expected." ) ;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static DictionaryContextData AssertDictionary (
this PipelineContextData value ,
String objectDescription )
{
if ( value is DictionaryContextData dictionary )
{
return dictionary ;
}
throw new ArgumentException ( $"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(DictionaryContextData)}' was expected." ) ;
}
2020-01-27 15:54:28 -05:00
[EditorBrowsable(EditorBrowsableState.Never)]
public static CaseSensitiveDictionaryContextData AssertCaseSensitiveDictionary (
this PipelineContextData value ,
String objectDescription )
{
if ( value is CaseSensitiveDictionaryContextData dictionary )
{
return dictionary ;
}
throw new ArgumentException ( $"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(CaseSensitiveDictionaryContextData)}' was expected." ) ;
}
2019-10-10 00:52:42 -04:00
[EditorBrowsable(EditorBrowsableState.Never)]
public static StringContextData AssertString (
this PipelineContextData value ,
String objectDescription )
{
if ( value is StringContextData str )
{
return str ;
}
throw new ArgumentException ( $"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(StringContextData)}' was expected." ) ;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static BooleanContextData AssertBoolean (
this PipelineContextData value ,
String objectDescription )
{
if ( value is BooleanContextData boolValue )
{
return boolValue ;
}
throw new ArgumentException ( $"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(BooleanContextData)}' was expected." ) ;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static NumberContextData AssertNumber (
this PipelineContextData value ,
String objectDescription )
{
if ( value is NumberContextData num )
{
return num ;
}
throw new ArgumentException ( $"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(NumberContextData)}' was expected." ) ;
}
/// <summary>
/// Returns all context data objects (depth first)
/// </summary>
internal static IEnumerable < PipelineContextData > Traverse ( this PipelineContextData value )
{
return Traverse ( value , omitKeys : false ) ;
}
/// <summary>
/// Returns all context data objects (depth first)
/// </summary>
internal static IEnumerable < PipelineContextData > Traverse (
this PipelineContextData value ,
Boolean omitKeys )
{
yield return value ;
if ( value is ArrayContextData | | value is DictionaryContextData )
{
var state = new TraversalState ( null , value ) ;
while ( state ! = null )
{
if ( state . MoveNext ( omitKeys ) )
{
value = state . Current ;
yield return value ;
if ( value is ArrayContextData | | value is DictionaryContextData )
{
state = new TraversalState ( state , value ) ;
}
}
else
{
state = state . Parent ;
}
}
}
}
internal static JToken ToJToken ( this PipelineContextData value )
{
JToken result ;
if ( value is StringContextData str )
{
result = str . Value ? ? String . Empty ;
}
else if ( value is BooleanContextData booleanValue )
{
result = booleanValue . Value ;
}
else if ( value is NumberContextData num )
{
result = num . Value ;
}
else if ( value is ArrayContextData array )
{
var jarray = new JArray ( ) ;
foreach ( var item in array )
{
jarray . Add ( item . ToJToken ( ) ) ; // Recurse
}
result = jarray ;
}
else if ( value is DictionaryContextData dictionary )
{
var jobject = new JObject ( ) ;
foreach ( var pair in dictionary )
{
var key = pair . Key ? ? String . Empty ;
var value2 = pair . Value . ToJToken ( ) ; // Recurse
if ( value2 ! = null )
{
jobject [ key ] = value2 ;
}
}
result = jobject ;
}
else
{
throw new InvalidOperationException ( "Internal error reading the template. Expected a string, an array, or a dictionary" ) ;
}
return result ;
}
internal static TemplateToken ToTemplateToken ( this PipelineContextData data )
{
if ( data is null )
{
return new NullToken ( null , null , null ) ;
}
switch ( data . Type )
{
case PipelineContextDataType . Dictionary :
var dictionary = data . AssertDictionary ( "dictionary" ) ;
var mapping = new MappingToken ( null , null , null ) ;
if ( dictionary . Count > 0 )
{
foreach ( var pair in dictionary )
{
var key = new StringToken ( null , null , null , pair . Key ) ;
var value = pair . Value . ToTemplateToken ( ) ;
mapping . Add ( key , value ) ;
}
}
return mapping ;
case PipelineContextDataType . Array :
var array = data . AssertArray ( "array" ) ;
var sequence = new SequenceToken ( null , null , null ) ;
if ( array . Count > 0 )
{
foreach ( var item in array )
{
sequence . Add ( item . ToTemplateToken ( ) ) ;
}
}
return sequence ;
case PipelineContextDataType . String :
var stringData = data as StringContextData ;
return new StringToken ( null , null , null , stringData . Value ) ;
case PipelineContextDataType . Boolean :
var booleanData = data as BooleanContextData ;
return new BooleanToken ( null , null , null , booleanData . Value ) ;
case PipelineContextDataType . Number :
var numberData = data as NumberContextData ;
return new NumberToken ( null , null , null , numberData . Value ) ;
default :
throw new NotSupportedException ( $"Unexpected {nameof(PipelineContextDataType)} type '{data.Type}'" ) ;
}
}
private sealed class TraversalState
{
public TraversalState (
TraversalState parent ,
PipelineContextData data )
{
Parent = parent ;
m_data = data ;
}
public Boolean MoveNext ( Boolean omitKeys )
{
switch ( m_data . Type )
{
case PipelineContextDataType . Array :
var array = m_data . AssertArray ( "array" ) ;
if ( + + m_index < array . Count )
{
Current = array [ m_index ] ;
return true ;
}
else
{
Current = null ;
return false ;
}
case PipelineContextDataType . Dictionary :
var dictionary = m_data . AssertDictionary ( "dictionary" ) ;
// Return the value
if ( m_isKey )
{
m_isKey = false ;
Current = dictionary [ m_index ] . Value ;
return true ;
}
if ( + + m_index < dictionary . Count )
{
// Skip the key, return the value
if ( omitKeys )
{
m_isKey = false ;
Current = dictionary [ m_index ] . Value ;
return true ;
}
// Return the key
m_isKey = true ;
Current = new StringContextData ( dictionary [ m_index ] . Key ) ;
return true ;
}
Current = null ;
return false ;
default :
throw new NotSupportedException ( $"Unexpected {nameof(PipelineContextData)} type '{m_data.Type}'" ) ;
}
}
private PipelineContextData m_data ;
private Int32 m_index = - 1 ;
private Boolean m_isKey ;
public PipelineContextData Current ;
public TraversalState Parent ;
}
}
}