Init
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QFSW.QC.Utilities
|
||||
{
|
||||
public static class CollectionExtensions
|
||||
{
|
||||
/// <summary>Inverts the key/value relationship between the items in the dictionary.</summary>
|
||||
/// <returns>Dictionary with the inverted relationship.</returns>
|
||||
public static Dictionary<TValue, TKey> Invert<TKey, TValue>(this IDictionary<TKey, TValue> source)
|
||||
{
|
||||
Dictionary<TValue, TKey> dictionary = new Dictionary<TValue, TKey>();
|
||||
foreach (KeyValuePair<TKey, TValue> item in source)
|
||||
{
|
||||
if (!dictionary.ContainsKey(item.Value))
|
||||
{
|
||||
dictionary.Add(item.Value, item.Key);
|
||||
}
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>Gets a sub array of an existing array.</summary>
|
||||
/// <param name="index">Index to take the sub array from.</param>
|
||||
/// <param name="length">The length of the sub array.</param>
|
||||
public static T[] SubArray<T>(this T[] data, int index, int length)
|
||||
{
|
||||
T[] result = new T[length];
|
||||
Array.Copy(data, index, result, 0, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Skips the last element in the sequence.</summary>
|
||||
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
|
||||
{
|
||||
using (IEnumerator<T> enumurator = source.GetEnumerator())
|
||||
{
|
||||
if (enumurator.MoveNext())
|
||||
{
|
||||
for (T value = enumurator.Current; enumurator.MoveNext(); value = enumurator.Current)
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reverses the order of the sequence.</summary>
|
||||
public static IEnumerable<T> Reversed<T>(this IReadOnlyList<T> source)
|
||||
{
|
||||
for (int i = source.Count - 1; i >= 0; i--)
|
||||
{
|
||||
yield return source[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a distinct stream based on a custom predicate.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of the IEnumerable.</typeparam>
|
||||
/// <typeparam name="TDistinct">The type of the value to test for distinctness.</typeparam>
|
||||
/// <param name="source">The source IEnumerable.</param>
|
||||
/// <param name="predicate">The custom distinct item producer.</param>
|
||||
/// <returns>The distinct stream.</returns>
|
||||
public static IEnumerable<TValue> DistinctBy<TValue, TDistinct>(this IEnumerable<TValue> source, Func<TValue, TDistinct> predicate)
|
||||
{
|
||||
HashSet<TDistinct> set = new HashSet<TDistinct>();
|
||||
foreach (TValue value in source)
|
||||
{
|
||||
if (set.Add(predicate(value)))
|
||||
{
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Yield<T>(this T item)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
public static T LastOr<T>(this IEnumerable<T> source, T value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return source.Last();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe void InsertionSortBy<T>(this IList<T> collection, Func<T, int> keySelector)
|
||||
{
|
||||
const int maxStackSize = 512;
|
||||
|
||||
if (collection.Count <= maxStackSize)
|
||||
{
|
||||
int* keyBuffer = stackalloc int[collection.Count];
|
||||
InsertionSortBy(collection, keySelector, keyBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
int[] keyArray = new int[collection.Count];
|
||||
fixed (int* keyBuffer = keyArray)
|
||||
{
|
||||
InsertionSortBy(collection, keySelector, keyBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void InsertionSortBy<T>(this IList<T> collection, Func<T, int> keySelector, int* keyBuffer)
|
||||
{
|
||||
int n = collection.Count;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
keyBuffer[i] = keySelector(collection[i]);
|
||||
}
|
||||
|
||||
for (int i = 1; i < n; i++)
|
||||
{
|
||||
T item = collection[i];
|
||||
int key = keyBuffer[i];
|
||||
int j = i - 1;
|
||||
|
||||
while (j >= 0 && keyBuffer[j] > key)
|
||||
{
|
||||
collection[j + 1] = collection[j];
|
||||
keyBuffer[j + 1] = keyBuffer[j];
|
||||
j -= 1;
|
||||
}
|
||||
|
||||
collection[j + 1] = item;
|
||||
keyBuffer[j + 1] = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99be75fe8deafca40aeb940896f68a4f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
using QFSW.QC.Pooling;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QFSW.QC.Utilities
|
||||
{
|
||||
public static class ColorExtensions
|
||||
{
|
||||
private static readonly ConcurrentStringBuilderPool _stringBuilderPool = new ConcurrentStringBuilderPool();
|
||||
|
||||
/// <summary>Colors a string using rich formatting.</summary>
|
||||
/// <returns>The formatted text.</returns>
|
||||
/// <param name="text">The text to color.</param>
|
||||
/// <param name="color">The color to add to the text.</param>
|
||||
public static string ColorText(this string text, Color color)
|
||||
{
|
||||
StringBuilder buffer = _stringBuilderPool.GetStringBuilder(text.Length + 10);
|
||||
buffer.AppendColoredText(text, color);
|
||||
return _stringBuilderPool.ReleaseAndToString(buffer);
|
||||
}
|
||||
|
||||
/// <summary>Colors a string using rich formatting and inserts the result into a string builder.</summary>
|
||||
/// <returns>The formatted text.</returns>
|
||||
/// <param name="stringBuilder">String builder to add the result into</param>
|
||||
/// <param name="text">The text to color.</param>
|
||||
/// <param name="color">The color to add to the text.</param>
|
||||
public static void AppendColoredText(this StringBuilder stringBuilder, string text, Color color)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
stringBuilder.Append(text);
|
||||
}
|
||||
|
||||
string hexColor = Color32ToStringNonAlloc(color);
|
||||
stringBuilder.Append("<#");
|
||||
stringBuilder.Append(hexColor);
|
||||
stringBuilder.Append('>');
|
||||
stringBuilder.Append(text);
|
||||
stringBuilder.Append("</color>");
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<int, string> _colorLookupTable = new ConcurrentDictionary<int, string>();
|
||||
public static unsafe string Color32ToStringNonAlloc(Color32 color)
|
||||
{
|
||||
int colorKey = color.r << 24 | color.g << 16 | color.b << 8 | color.a;
|
||||
if (_colorLookupTable.ContainsKey(colorKey))
|
||||
{
|
||||
return _colorLookupTable[colorKey];
|
||||
}
|
||||
|
||||
char* buffer = stackalloc char[8];
|
||||
Color32ToHexNonAlloc(color, buffer);
|
||||
|
||||
int bufferLength = color.a < 0xFF ? 8 : 6;
|
||||
string colorText = new string(buffer, 0, bufferLength);
|
||||
|
||||
_colorLookupTable[colorKey] = colorText;
|
||||
return colorText;
|
||||
}
|
||||
|
||||
private static unsafe void Color32ToHexNonAlloc(Color32 color, char* buffer)
|
||||
{
|
||||
ByteToHex(color.r, out buffer[0], out buffer[1]);
|
||||
ByteToHex(color.g, out buffer[2], out buffer[3]);
|
||||
ByteToHex(color.b, out buffer[4], out buffer[5]);
|
||||
ByteToHex(color.a, out buffer[6], out buffer[7]);
|
||||
}
|
||||
|
||||
private static void ByteToHex(byte value, out char dig1, out char dig2)
|
||||
{
|
||||
dig1 = NibbleToHex((byte)(value >> 4));
|
||||
dig2 = NibbleToHex((byte)(value & 0xF));
|
||||
}
|
||||
|
||||
private static char NibbleToHex(byte nibble)
|
||||
{
|
||||
if (nibble < 10) { return (char)('0' + nibble); }
|
||||
else { return (char)('A' + nibble - 10); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5551e6df9beb04cd08039b2d8912821a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace QFSW.QC.Utilities
|
||||
{
|
||||
public static class GameObjectExtensions
|
||||
{
|
||||
private static readonly Dictionary<string, GameObject> GameObjectCache = new Dictionary<string, GameObject>();
|
||||
private static readonly List<GameObject> RootGameObjectBuffer = new List<GameObject>();
|
||||
|
||||
public static GameObject Find(string name, bool includeInactive = false)
|
||||
{
|
||||
if (GameObjectCache.TryGetValue(name, out GameObject obj)
|
||||
&& obj
|
||||
&& obj.activeInHierarchy | includeInactive
|
||||
&& obj.name == name)
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
|
||||
obj = GameObject.Find(name);
|
||||
if (obj)
|
||||
{
|
||||
return GameObjectCache[name] = obj;
|
||||
}
|
||||
|
||||
if (includeInactive)
|
||||
{
|
||||
int sceneCount = SceneManager.sceneCountInBuildSettings;
|
||||
for (int i = 0; i < sceneCount; i++)
|
||||
{
|
||||
Scene scene = SceneManager.GetSceneByBuildIndex(i);
|
||||
if (scene.isLoaded)
|
||||
{
|
||||
RootGameObjectBuffer.Clear();
|
||||
scene.GetRootGameObjects(RootGameObjectBuffer);
|
||||
|
||||
foreach (GameObject root in RootGameObjectBuffer)
|
||||
{
|
||||
obj = Find(name, root);
|
||||
if (obj)
|
||||
{
|
||||
return GameObjectCache[name] = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj = Resources
|
||||
.FindObjectsOfTypeAll<GameObject>()
|
||||
.Where(x => !x.hideFlags.HasFlag(HideFlags.HideInHierarchy))
|
||||
.FirstOrDefault(x => x.name == name);
|
||||
|
||||
if (obj)
|
||||
{
|
||||
return GameObjectCache[name] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static GameObject Find(string name, GameObject root)
|
||||
{
|
||||
if (root.name == name)
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
for (int i = 0; i < root.transform.childCount; i++)
|
||||
{
|
||||
GameObject obj = Find(name, root.transform.GetChild(i).gameObject);
|
||||
if (obj)
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9afd52ab3444b8f4bb5518c7a215c953
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
public static class LogTypeExtensions
|
||||
{
|
||||
public static LoggingThreshold ToLoggingThreshold(this LogType logType)
|
||||
{
|
||||
LoggingThreshold severity = LoggingThreshold.Always;
|
||||
switch (logType)
|
||||
{
|
||||
case LogType.Exception: severity = LoggingThreshold.Exception; break;
|
||||
case LogType.Error: severity = LoggingThreshold.Error; break;
|
||||
case LogType.Assert: severity = LoggingThreshold.Error; break;
|
||||
case LogType.Warning: severity = LoggingThreshold.Warning; break;
|
||||
case LogType.Log: severity = LoggingThreshold.Always; break;
|
||||
}
|
||||
|
||||
return severity;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6173ce0a9ecacf0408e91b424d44ab09
|
||||
timeCreated: 1554342642
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,381 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace QFSW.QC.Utilities
|
||||
{
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
#region Lookup Tables
|
||||
private static readonly Dictionary<Type, string> _typeDisplayNames = new Dictionary<Type, string>
|
||||
{
|
||||
{ typeof(int), "int" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(decimal), "decimal" },
|
||||
{ typeof(double), "double" },
|
||||
{ typeof(string), "string" },
|
||||
{ typeof(bool), "bool" },
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(uint), "uint" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(ushort), "ushort" },
|
||||
{ typeof(long), "decimal" },
|
||||
{ typeof(ulong), "ulong" },
|
||||
{ typeof(char), "char" },
|
||||
{ typeof(object), "object" }
|
||||
};
|
||||
|
||||
private static readonly Type[] _valueTupleTypes =
|
||||
{
|
||||
typeof(ValueTuple<>),
|
||||
typeof(ValueTuple<,>),
|
||||
typeof(ValueTuple<,,>),
|
||||
typeof(ValueTuple<,,,>),
|
||||
typeof(ValueTuple<,,,,>),
|
||||
typeof(ValueTuple<,,,,,>),
|
||||
typeof(ValueTuple<,,,,,,>),
|
||||
typeof(ValueTuple<,,,,,,,>)
|
||||
};
|
||||
|
||||
private static readonly Type[][] _primitiveTypeCastHierarchy =
|
||||
{
|
||||
new[] { typeof(byte), typeof(sbyte), typeof(char) },
|
||||
new[] { typeof(short), typeof(ushort) },
|
||||
new[] { typeof(int), typeof(uint) },
|
||||
new[] { typeof(long), typeof(ulong) },
|
||||
new[] { typeof(float) },
|
||||
new[] { typeof(double) }
|
||||
};
|
||||
#endregion
|
||||
|
||||
/// <summary>Determines if a type is a delegate.</summary>
|
||||
/// <returns>If the type is a delegate.</returns>
|
||||
public static bool IsDelegate(this Type type)
|
||||
{
|
||||
if (!typeof(Delegate).IsAssignableFrom(type)) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Determines if a type is a strongly typed delegate.</summary>
|
||||
/// <returns>If the type is a strongly typed delegate.</returns>
|
||||
public static bool IsStrongDelegate(this Type type)
|
||||
{
|
||||
if (!type.IsDelegate()) { return false; }
|
||||
if (type.IsAbstract) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Determines if a field is a delegate.</summary>
|
||||
/// <returns>If the field is a delegate.</returns>
|
||||
public static bool IsDelegate(this FieldInfo fieldInfo)
|
||||
{
|
||||
return fieldInfo.FieldType.IsDelegate();
|
||||
}
|
||||
|
||||
/// <summary>Determines if a field is a strongly typed delegate.</summary>
|
||||
/// <param name="fieldInfo">The field to query.</param>
|
||||
/// <returns>If the field is a strongly typed delegate.</returns>
|
||||
public static bool IsStrongDelegate(this FieldInfo fieldInfo)
|
||||
{
|
||||
return fieldInfo.FieldType.IsStrongDelegate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the type is a generic type of the given non-generic type.
|
||||
/// </summary>
|
||||
/// <param name="nonGenericType">The non-generic type to test against.</param>
|
||||
/// <returns>If the type is a generic type of the non-generic type.</returns>
|
||||
public static bool IsGenericTypeOf(this Type genericType, Type nonGenericType)
|
||||
{
|
||||
if (!genericType.IsGenericType) { return false; }
|
||||
return genericType.GetGenericTypeDefinition() == nonGenericType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the type is a derived type of the given base type.
|
||||
/// </summary>
|
||||
/// <param name="baseType">The base type to test against.</param>
|
||||
/// <returns>If the type is a derived type of the base type.</returns>
|
||||
public static bool IsDerivedTypeOf(this Type type, Type baseType)
|
||||
{
|
||||
return baseType.IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if an object the given type can be casted to the specified type.
|
||||
/// </summary>
|
||||
/// <param name="to">The destination type of the cast.</param>
|
||||
/// <param name="implicitly">If only implicit casts should be considered.</param>
|
||||
/// <returns>If the cast can be performed.</returns>
|
||||
public static bool IsCastableTo(this Type from, Type to, bool implicitly = false)
|
||||
{
|
||||
return to.IsAssignableFrom(from) || from.HasCastDefined(to, implicitly);
|
||||
}
|
||||
|
||||
private static bool HasCastDefined(this Type from, Type to, bool implicitly)
|
||||
{
|
||||
if ((from.IsPrimitive || from.IsEnum) && (to.IsPrimitive || to.IsEnum))
|
||||
{
|
||||
if (!implicitly)
|
||||
{
|
||||
return from == to || (from != typeof(bool) && to != typeof(bool));
|
||||
}
|
||||
|
||||
IEnumerable<Type> lowerTypes = Enumerable.Empty<Type>();
|
||||
foreach (Type[] types in _primitiveTypeCastHierarchy)
|
||||
{
|
||||
if (types.Any(t => t == to))
|
||||
{
|
||||
return lowerTypes.Any(t => t == from);
|
||||
}
|
||||
|
||||
lowerTypes = lowerTypes.Concat(types);
|
||||
}
|
||||
|
||||
return false; // IntPtr, UIntPtr, Enum, Boolean
|
||||
}
|
||||
|
||||
return IsCastDefined(to, m => m.GetParameters()[0].ParameterType, _ => from, implicitly, false)
|
||||
|| IsCastDefined(from, _ => to, m => m.ReturnType, implicitly, true);
|
||||
}
|
||||
|
||||
private static bool IsCastDefined(Type type, Func<MethodInfo, Type> baseType, Func<MethodInfo, Type> derivedType, bool implicitly, bool lookInBase)
|
||||
{
|
||||
BindingFlags flags = BindingFlags.Public | BindingFlags.Static | (lookInBase ? BindingFlags.FlattenHierarchy : BindingFlags.DeclaredOnly);
|
||||
MethodInfo[] methods = type.GetMethods(flags);
|
||||
|
||||
return methods.Where(m => m.Name == "op_Implicit" || (!implicitly && m.Name == "op_Explicit"))
|
||||
.Any(m => baseType(m).IsAssignableFrom(derivedType(m)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dynamically casts an object to the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The destination type of the cast.</param>
|
||||
/// <param name="data">The object to cast.</param>
|
||||
/// <returns>The dynamically casted object.</returns>
|
||||
public static object Cast(this Type type, object data)
|
||||
{
|
||||
if (type.IsInstanceOfType(data))
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Convert.ChangeType(data, type);
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
Type srcType = data.GetType();
|
||||
ParameterExpression dataParam = Expression.Parameter(srcType, "data");
|
||||
Expression body = Expression.Convert(Expression.Convert(dataParam, srcType), type);
|
||||
|
||||
Delegate run = Expression.Lambda(body, dataParam).Compile();
|
||||
return run.DynamicInvoke(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Determines if the given method is an override.</summary>
|
||||
/// <returns>If the method is an override.</returns>
|
||||
public static bool IsOverride(this MethodInfo methodInfo)
|
||||
{
|
||||
return methodInfo.GetBaseDefinition().DeclaringType != methodInfo.DeclaringType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the provider has the specified attribute.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The attribute to test.</typeparam>
|
||||
/// <param name="provider">The attribute provider.</param>
|
||||
/// <param name="searchInherited">If base declarations should be searched.</param>
|
||||
/// <returns>If the attribute is present.</returns>
|
||||
public static bool HasAttribute<T>(this ICustomAttributeProvider provider, bool searchInherited = true) where T : Attribute
|
||||
{
|
||||
try
|
||||
{
|
||||
return provider.IsDefined(typeof(T), searchInherited);
|
||||
}
|
||||
catch (MissingMethodException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatted display name for a given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to generate a display name for.</param>
|
||||
/// <param name="includeNamespace">If the namespace should be included when generating the typename.</param>
|
||||
/// <returns>The generated display name.</returns>
|
||||
public static string GetDisplayName(this Type type, bool includeNamespace = false)
|
||||
{
|
||||
if (type.IsGenericParameter)
|
||||
{
|
||||
return type.Name;
|
||||
}
|
||||
|
||||
if (type.IsArray)
|
||||
{
|
||||
int rank = type.GetArrayRank();
|
||||
string innerTypeName = GetDisplayName(type.GetElementType(), includeNamespace);
|
||||
return $"{innerTypeName}[{new string(',', rank - 1)}]";
|
||||
}
|
||||
|
||||
if (_typeDisplayNames.ContainsKey(type))
|
||||
{
|
||||
string baseName = _typeDisplayNames[type];
|
||||
if (type.IsGenericType && !type.IsConstructedGenericType)
|
||||
{
|
||||
Type[] genericArgs = type.GetGenericArguments();
|
||||
return $"{baseName}<{new string(',', genericArgs.Length - 1)}>";
|
||||
}
|
||||
|
||||
return baseName;
|
||||
}
|
||||
|
||||
if (type.IsGenericTypeOf(typeof(Nullable<>)))
|
||||
{
|
||||
Type innerType = type.GetGenericArguments()[0];
|
||||
return $"{innerType.GetDisplayName()}?";
|
||||
}
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
Type baseType = type.GetGenericTypeDefinition();
|
||||
Type[] genericArgs = type.GetGenericArguments();
|
||||
|
||||
if (_valueTupleTypes.Contains(baseType))
|
||||
{
|
||||
return GetTupleDisplayName(type, includeNamespace);
|
||||
}
|
||||
|
||||
if (type.IsConstructedGenericType)
|
||||
{
|
||||
string[] genericNames = new string[genericArgs.Length];
|
||||
for (int i = 0; i < genericArgs.Length; i++)
|
||||
{
|
||||
genericNames[i] = GetDisplayName(genericArgs[i], includeNamespace);
|
||||
}
|
||||
|
||||
string baseName = GetDisplayName(baseType, includeNamespace).Split('<')[0];
|
||||
return $"{baseName}<{string.Join(", ", genericNames)}>";
|
||||
}
|
||||
|
||||
string typeName = includeNamespace
|
||||
? type.FullName
|
||||
: type.Name;
|
||||
|
||||
return $"{typeName.Split('`')[0]}<{new string(',', genericArgs.Length - 1)}>";
|
||||
}
|
||||
|
||||
Type declaringType = type.DeclaringType;
|
||||
if (declaringType != null)
|
||||
{
|
||||
string declaringName = GetDisplayName(declaringType, includeNamespace);
|
||||
return $"{declaringName}.{type.Name}";
|
||||
}
|
||||
|
||||
return includeNamespace
|
||||
? type.FullName
|
||||
: type.Name;
|
||||
}
|
||||
|
||||
private static string GetTupleDisplayName(this Type type, bool includeNamespace = false)
|
||||
{
|
||||
IEnumerable<string> parts = type
|
||||
.GetGenericArguments()
|
||||
.Select(x => x.GetDisplayName(includeNamespace));
|
||||
|
||||
return $"({string.Join(", ", parts)})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if two methods from different types have the same signature.
|
||||
/// </summary>
|
||||
/// <param name="a">First method</param>
|
||||
/// <param name="b">Second method</param>
|
||||
/// <returns><c>true</c> if they are equal</returns>
|
||||
public static bool AreMethodsEqual(MethodInfo a, MethodInfo b)
|
||||
{
|
||||
if (a.Name != b.Name) return false;
|
||||
|
||||
ParameterInfo[] paramsA = a.GetParameters();
|
||||
ParameterInfo[] paramsB = b.GetParameters();
|
||||
|
||||
if (paramsA.Length != paramsB.Length) return false;
|
||||
for (int i = 0; i < paramsA.Length; i++)
|
||||
{
|
||||
ParameterInfo pa = paramsA[i];
|
||||
ParameterInfo pb = paramsB[i];
|
||||
|
||||
if (pa.Name != pb.Name) return false;
|
||||
if (pa.HasDefaultValue != pb.HasDefaultValue) return false;
|
||||
|
||||
Type ta = pa.ParameterType;
|
||||
Type tb = pb.ParameterType;
|
||||
|
||||
if (!ta.ContainsGenericParameters && !tb.ContainsGenericParameters)
|
||||
{
|
||||
if (ta != tb) return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (a.IsGenericMethod != b.IsGenericMethod) return false;
|
||||
if (a.IsGenericMethod && b.IsGenericMethod)
|
||||
{
|
||||
Type[] genericA = a.GetGenericArguments();
|
||||
Type[] genericB = b.GetGenericArguments();
|
||||
|
||||
if (genericA.Length != genericB.Length) return false;
|
||||
for (int i = 0; i < genericA.Length; i++)
|
||||
{
|
||||
Type ga = genericA[i];
|
||||
Type gb = genericB[i];
|
||||
|
||||
if (ga.Name != gb.Name) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebases a method onto a new type by finding the corresponding method with an equal signature.
|
||||
/// </summary>
|
||||
/// <param name="method">Method to rebase</param>
|
||||
/// <param name="newBase">New type to rebase the method onto</param>
|
||||
/// <returns>The rebased method</returns>
|
||||
public static MethodInfo RebaseMethod(this MethodInfo method, Type newBase)
|
||||
{
|
||||
BindingFlags flags = BindingFlags.Default;
|
||||
|
||||
flags |= method.IsStatic
|
||||
? BindingFlags.Static
|
||||
: BindingFlags.Instance;
|
||||
|
||||
flags |= method.IsPublic
|
||||
? BindingFlags.Public
|
||||
: BindingFlags.NonPublic;
|
||||
|
||||
MethodInfo[] candidates = newBase.GetMethods(flags)
|
||||
.Where(x => AreMethodsEqual(x, method))
|
||||
.ToArray();
|
||||
|
||||
if (candidates.Length == 0)
|
||||
{
|
||||
throw new ArgumentException($"Could not rebase method {method} onto type {newBase} as no matching candidates were found");
|
||||
}
|
||||
|
||||
if (candidates.Length > 1)
|
||||
{
|
||||
throw new ArgumentException($"Could not rebase method {method} onto type {newBase} as too many matching candidates were found");
|
||||
}
|
||||
|
||||
return candidates[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 095d8e0dd1a06544485e6498a6907792
|
||||
timeCreated: 1544062910
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace QFSW.QC.Utilities
|
||||
{
|
||||
public static class SceneUtilities
|
||||
{
|
||||
public static IEnumerable<Scene> GetScenesInBuild()
|
||||
{
|
||||
int sceneCount = SceneManager.sceneCountInBuildSettings;
|
||||
for (int i = 0; i < sceneCount; i++)
|
||||
{
|
||||
yield return SceneManager.GetSceneByBuildIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Scene> GetLoadedScenes()
|
||||
{
|
||||
int sceneCount = SceneManager.sceneCount;
|
||||
for (int i = 0; i < sceneCount; i++)
|
||||
{
|
||||
yield return SceneManager.GetSceneAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Scene> GetAllScenes()
|
||||
{
|
||||
return GetScenesInBuild();
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetAllScenePaths()
|
||||
{
|
||||
int sceneCount = SceneManager.sceneCountInBuildSettings;
|
||||
for (int i = 0; i < sceneCount; i++)
|
||||
{
|
||||
yield return SceneUtility.GetScenePathByBuildIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetAllSceneNames()
|
||||
{
|
||||
return GetAllScenePaths().Select(Path.GetFileNameWithoutExtension);
|
||||
}
|
||||
|
||||
public static AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode mode)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
string scenePath = sceneName;
|
||||
Scene scene = SceneManager.GetSceneByName(sceneName);
|
||||
|
||||
if (scene.IsValid())
|
||||
{
|
||||
scenePath = scene.path;
|
||||
}
|
||||
else if (!Path.HasExtension(sceneName))
|
||||
{
|
||||
scenePath = GetAllScenePaths()
|
||||
.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == sceneName);
|
||||
}
|
||||
|
||||
if (!File.Exists(scenePath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot load scene '{sceneName}' as it is not present in the build settings or the AssetDatabase");
|
||||
}
|
||||
|
||||
LoadSceneParameters parameters = new LoadSceneParameters {loadSceneMode = mode};
|
||||
return EditorSceneManager.LoadSceneAsyncInPlayMode(scenePath, parameters);
|
||||
#else
|
||||
return SceneManager.LoadSceneAsync(sceneName, mode);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e98d4c992c1e914694e6b0873b6f078
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace QFSW.QC.Utilities
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static bool ContainsCaseInsensitive(this string source, string value)
|
||||
{
|
||||
return string.IsNullOrEmpty(source)
|
||||
? string.IsNullOrEmpty(value)
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
: source.ToLower().Contains(value.ToLower());
|
||||
#else
|
||||
: source.Contains(value, StringComparison.OrdinalIgnoreCase);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool Contains(this string source, string value, StringComparison comp)
|
||||
{
|
||||
return source?.IndexOf(value, comp) >= 0;
|
||||
}
|
||||
|
||||
public static int CountFromIndex(this string source, char target, int index)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = index; i < source.Length; i++)
|
||||
{
|
||||
if (source[i] == target)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac9ccb14571900847893f62b5ebaa305
|
||||
timeCreated: 1561417301
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,514 @@
|
||||
using QFSW.QC.Containers;
|
||||
using QFSW.QC.Pooling;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
public static class TextProcessing
|
||||
{
|
||||
public static readonly char[] DefaultLeftScopers = { '<', '[', '(', '{', '"' };
|
||||
public static readonly char[] DefaultRightScopers = { '>', ']', ')', '}', '"' };
|
||||
|
||||
private static readonly ConcurrentStringBuilderPool _stringBuilderPool = new ConcurrentStringBuilderPool();
|
||||
|
||||
/// <summary>
|
||||
/// Options to provide the ReduceScoped functions with
|
||||
/// </summary>
|
||||
public struct ReduceScopeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum number of times the scope can be reduced by.
|
||||
/// Setting to -1 will allow for an unlimited number.
|
||||
/// </summary>
|
||||
public int MaxReductions;
|
||||
|
||||
/// <summary>
|
||||
/// If incomplete scopes should also be reduced
|
||||
/// For example, the following text -> "((foo0 foo1)" when using () for scoping
|
||||
/// - false: "(foo0 foo1"
|
||||
/// - true: "foo0 foo1"
|
||||
/// </summary>
|
||||
public bool ReduceIncompleteScope;
|
||||
|
||||
/// <summary>
|
||||
/// The default set of options for the ReduceScope functions
|
||||
/// </summary>
|
||||
public static readonly ReduceScopeOptions Default = new ReduceScopeOptions
|
||||
{
|
||||
MaxReductions = -1,
|
||||
ReduceIncompleteScope = false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options to provide the SplitScoped functions with
|
||||
/// </summary>
|
||||
public struct ScopedSplitOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum number of items to split the string into.
|
||||
/// Setting to -1 will allow for an unlimited number.
|
||||
/// </summary>
|
||||
public int MaxCount;
|
||||
|
||||
/// <summary>
|
||||
/// If the scope should automatically be reduced when performing scoped splitting.
|
||||
/// </summary>
|
||||
public bool AutoReduceScope;
|
||||
|
||||
/// <summary>
|
||||
/// The default set of options for the SplitScoped functions
|
||||
/// </summary>
|
||||
public static readonly ScopedSplitOptions Default = new ScopedSplitOptions
|
||||
{
|
||||
MaxCount = -1,
|
||||
AutoReduceScope = false,
|
||||
};
|
||||
}
|
||||
|
||||
#region GetMaxScopeDepthAtEnd
|
||||
|
||||
public static int GetMaxScopeDepthAtEnd(this string input)
|
||||
{
|
||||
return input.GetMaxScopeDepthAtEnd(DefaultLeftScopers, DefaultRightScopers);
|
||||
}
|
||||
|
||||
public static int GetMaxScopeDepthAtEnd(this string input, char leftScoper, char rightScoper)
|
||||
{
|
||||
return input.GetMaxScopeDepthAtEnd(leftScoper.AsArraySingle(), rightScoper.AsArraySingle());
|
||||
}
|
||||
|
||||
public static int GetMaxScopeDepthAtEnd<T>(this string input, T leftScopers, T rightScopers)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
return input.GetMaxScopeDepthAt(input.Length - 1, leftScopers, rightScopers);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetMaxScopeDepthAt
|
||||
|
||||
public static int GetMaxScopeDepthAt(this string input, int cursor)
|
||||
{
|
||||
return input.GetMaxScopeDepthAt(cursor, DefaultLeftScopers, DefaultRightScopers);
|
||||
}
|
||||
|
||||
public static int GetMaxScopeDepthAt(this string input, int cursor, char leftScoper, char rightScoper)
|
||||
{
|
||||
return input.GetMaxScopeDepthAt(cursor, leftScoper.AsArraySingle(), rightScoper.AsArraySingle());
|
||||
}
|
||||
|
||||
public static int GetMaxScopeDepthAt<T>(this string input, int cursor, T leftScopers, T rightScopers)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
int[] scopes = new int[leftScopers.Count];
|
||||
for (int i = 0; i <= cursor; i++)
|
||||
{
|
||||
if (i == 0 || input[i - 1] != '\\')
|
||||
{
|
||||
for (int j = 0; j < leftScopers.Count; j++)
|
||||
{
|
||||
char leftScoper = leftScopers[j];
|
||||
char rightScoper = rightScopers[j];
|
||||
|
||||
if (input[i] == leftScoper && leftScoper == rightScoper) { scopes[j] = 1 - scopes[j]; }
|
||||
else if (input[i] == leftScoper) { scopes[j]++; }
|
||||
else if (input[i] == rightScoper) { scopes[j]--; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scopes.Max();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReduceScope
|
||||
|
||||
public static string ReduceScope(this string input)
|
||||
{
|
||||
return input.ReduceScope(DefaultLeftScopers, DefaultRightScopers, ReduceScopeOptions.Default);
|
||||
}
|
||||
|
||||
public static string ReduceScope(this string input, ReduceScopeOptions options)
|
||||
{
|
||||
return input.ReduceScope(DefaultLeftScopers, DefaultRightScopers, options);
|
||||
}
|
||||
|
||||
public static string ReduceScope(this string input, char leftScoper, char rightScoper)
|
||||
{
|
||||
return input.ReduceScope(leftScoper.AsArraySingle(), rightScoper.AsArraySingle(), ReduceScopeOptions.Default);
|
||||
}
|
||||
|
||||
public static string ReduceScope(this string input, char leftScoper, char rightScoper, ReduceScopeOptions options)
|
||||
{
|
||||
return input.ReduceScope(leftScoper.AsArraySingle(), rightScoper.AsArraySingle(), options);
|
||||
}
|
||||
|
||||
public static string ReduceScope<T>(this string input, T leftScopers, T rightScopers)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
return ReduceScope(input, leftScopers, rightScopers, ReduceScopeOptions.Default);
|
||||
}
|
||||
|
||||
public static string ReduceScope<T>(this string input, T leftScopers, T rightScopers, ReduceScopeOptions options)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
if (leftScopers.Count != rightScopers.Count)
|
||||
{
|
||||
throw new ArgumentException("There must be an equal number of corresponding left and right scopers");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (options.MaxReductions == 0)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
// Use cursors to point into the current string instead of repeated
|
||||
// substrings for improved performance
|
||||
int leftCursor = 0;
|
||||
int rightCursor = input.Length - 1;
|
||||
|
||||
// Determines if a cursor is pointing to an escaped character
|
||||
bool IsEscaped(int cursor)
|
||||
{
|
||||
return cursor > 0 && input[cursor - 1] == '\\';
|
||||
}
|
||||
|
||||
int totalScopeReductions = 0;
|
||||
bool workRemaining = true;
|
||||
|
||||
// Keep descoping the string until we either run out of work to do, or we hit the maximum number of reductions
|
||||
while (workRemaining && (totalScopeReductions < options.MaxReductions || options.MaxReductions < 0))
|
||||
{
|
||||
// If the left cursor ever surpasses the right, we have descoped to an empty string
|
||||
if (leftCursor > rightCursor)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Update cursors to skip over any whitespace to emulate Trim
|
||||
workRemaining = false;
|
||||
while (char.IsWhiteSpace(input[leftCursor])) { leftCursor++; }
|
||||
while (char.IsWhiteSpace(input[rightCursor])) { rightCursor--; }
|
||||
|
||||
// If the right cursor is escaped, then finish here
|
||||
if (IsEscaped(rightCursor))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Check each pair of scopers
|
||||
for (int i = 0; i < leftScopers.Count; i++)
|
||||
{
|
||||
char leftScoper = leftScopers[i];
|
||||
char rightScoper = rightScopers[i];
|
||||
bool sameScoper = leftScoper == rightScoper;
|
||||
|
||||
// Determines if we've hit a valid scoper pair
|
||||
bool validScoperPair = input[leftCursor] == leftScoper && input[rightCursor] == rightScoper;
|
||||
bool incompleteReduction = false;
|
||||
|
||||
if (!validScoperPair && options.ReduceIncompleteScope)
|
||||
{
|
||||
// Only the left cursor needs to match for incomplete scope reduction
|
||||
validScoperPair = input[leftCursor] == leftScoper;
|
||||
incompleteReduction = validScoperPair;
|
||||
}
|
||||
|
||||
if (validScoperPair)
|
||||
{
|
||||
// Search between the two cursors to make sure scope never drops down to 0 between them
|
||||
// as this would be two separate scopes being incorrectly descoped
|
||||
bool scopeBreaks = false;
|
||||
int currentScope = 1;
|
||||
int leftSearch = leftCursor + 1;
|
||||
int rightSearch = rightCursor - 1;
|
||||
|
||||
// Only perform search if there is a valid search range
|
||||
if (leftSearch <= rightSearch)
|
||||
{
|
||||
// Logic for same scoper is a bit different since we can't really define a scope depth
|
||||
// If it's impossible to remove inner scope characters by stripping the outer pair
|
||||
// of scopers then this is a broken scope, otherwise its fine - update the search range to allow for this
|
||||
// fine : ""foo""
|
||||
// not fine : "foo1""foo2"
|
||||
if (sameScoper)
|
||||
{
|
||||
// Determines if the cursor location can skip searching for same scoper case
|
||||
bool SkipSearch(int cursor)
|
||||
{
|
||||
if (IsEscaped(cursor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return input[cursor] == leftScoper || char.IsWhiteSpace(input[cursor]);
|
||||
}
|
||||
|
||||
while (SkipSearch(leftSearch)) { leftSearch++; }
|
||||
while (SkipSearch(rightSearch)) { rightSearch--; }
|
||||
}
|
||||
|
||||
// Perform the search
|
||||
for (int j = leftSearch; j <= rightSearch; j++)
|
||||
{
|
||||
// Ignore escaped characters
|
||||
if (IsEscaped(j))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sameScoper)
|
||||
{
|
||||
// If we find any scopers inside the refined scope range then we can't descope
|
||||
if (input[j] == leftScoper)
|
||||
{
|
||||
scopeBreaks = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For normal scopers, just check that scope never hits 0
|
||||
// Update the current scope level
|
||||
if (input[j] == leftScoper) { currentScope++; }
|
||||
else if (input[j] == rightScoper) { currentScope--; }
|
||||
|
||||
// Scope broken if it hits 0
|
||||
if (currentScope == 0)
|
||||
{
|
||||
scopeBreaks = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the scope never breaks, then we can successfully descope
|
||||
if (!scopeBreaks)
|
||||
{
|
||||
// Update the cursors and break out of for loop
|
||||
// Don't move the right cursor for incomplete reduction
|
||||
if (!incompleteReduction)
|
||||
{
|
||||
rightCursor--;
|
||||
}
|
||||
|
||||
leftCursor++;
|
||||
totalScopeReductions++;
|
||||
workRemaining = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform substring on cursors to get final descoped output if required
|
||||
return totalScopeReductions > 0
|
||||
? input.Substring(leftCursor, rightCursor - leftCursor + 1)
|
||||
: input;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SplitScoped
|
||||
|
||||
public static string[] SplitScoped(this string input, char splitChar)
|
||||
{
|
||||
return input.SplitScoped(splitChar, ScopedSplitOptions.Default);
|
||||
}
|
||||
|
||||
public static string[] SplitScoped(this string input, char splitChar, ScopedSplitOptions options)
|
||||
{
|
||||
return input.SplitScoped(splitChar, DefaultLeftScopers, DefaultRightScopers, options);
|
||||
}
|
||||
|
||||
public static string[] SplitScoped(this string input, char splitChar, char leftScoper, char rightScoper)
|
||||
{
|
||||
return input.SplitScoped(splitChar, leftScoper.AsArraySingle(), rightScoper.AsArraySingle(), ScopedSplitOptions.Default);
|
||||
}
|
||||
|
||||
public static string[] SplitScoped(this string input, char splitChar, char leftScoper, char rightScoper, ScopedSplitOptions options)
|
||||
{
|
||||
return input.SplitScoped(splitChar, leftScoper.AsArraySingle(), rightScoper.AsArraySingle(), options);
|
||||
}
|
||||
|
||||
public static string[] SplitScoped<T>(this string input, char splitChar, T leftScopers, T rightScopers)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
return SplitScoped(input, splitChar, leftScopers, rightScopers, ScopedSplitOptions.Default);
|
||||
}
|
||||
|
||||
public static string[] SplitScoped<T>(this string input, char splitChar, T leftScopers, T rightScopers, ScopedSplitOptions options)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
if (options.AutoReduceScope)
|
||||
{
|
||||
input = input.ReduceScope(leftScopers, rightScopers);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
IEnumerable<int> rawSplitIndices = GetScopedSplitPoints(input, splitChar, leftScopers, rightScopers);
|
||||
int[] splitIndices =
|
||||
options.MaxCount > 0
|
||||
? rawSplitIndices.Take(options.MaxCount - 1).ToArray()
|
||||
: rawSplitIndices.ToArray();
|
||||
|
||||
// Return single array when no splits occurred
|
||||
if (splitIndices.Length == 0)
|
||||
{
|
||||
return new[] { input };
|
||||
}
|
||||
|
||||
string[] splitString = new string[splitIndices.Length + 1];
|
||||
int lastSplitIndex = 0;
|
||||
for (int i = 0; i < splitIndices.Length; i++)
|
||||
{
|
||||
splitString[i] = input.Substring(lastSplitIndex, splitIndices[i] - lastSplitIndex).Trim();
|
||||
lastSplitIndex = splitIndices[i] + 1;
|
||||
}
|
||||
|
||||
splitString[splitIndices.Length] = input.Substring(lastSplitIndex).Trim();
|
||||
return splitString;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetScopedSplitPoints
|
||||
|
||||
public static IEnumerable<int> GetScopedSplitPoints<T>(string input, char splitChar, T leftScopers, T rightScopers)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
return GetScopedSplitPoints(input, splitChar, leftScopers, rightScopers, ScopedSplitOptions.Default);
|
||||
}
|
||||
|
||||
public static IEnumerable<int> GetScopedSplitPoints<T>(
|
||||
string input, char splitChar, T leftScopers, T rightScopers, ScopedSplitOptions options)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
if (leftScopers.Count != rightScopers.Count)
|
||||
{
|
||||
throw new ArgumentException("There must be an equal number of corresponding left and right scopers");
|
||||
}
|
||||
|
||||
int[] scopes = new int[leftScopers.Count];
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (i == 0 || input[i - 1] != '\\')
|
||||
{
|
||||
for (int j = 0; j < leftScopers.Count; j++)
|
||||
{
|
||||
char leftScoper = leftScopers[j];
|
||||
char rightScoper = rightScopers[j];
|
||||
|
||||
if (input[i] == leftScoper && leftScoper == rightScoper) { scopes[j] = 1 - scopes[j]; }
|
||||
else if (input[i] == leftScoper) { scopes[j]++; }
|
||||
else if (input[i] == rightScoper) { scopes[j]--; }
|
||||
}
|
||||
}
|
||||
|
||||
if (input[i] == splitChar && scopes.All(x => x == 0))
|
||||
{
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static bool CanSplitScoped(this string input, char splitChar)
|
||||
{
|
||||
return input.CanSplitScoped(splitChar, DefaultLeftScopers, DefaultRightScopers);
|
||||
}
|
||||
|
||||
public static bool CanSplitScoped(this string input, char splitChar, char leftScoper, char rightScoper)
|
||||
{
|
||||
return input.CanSplitScoped(splitChar, leftScoper.AsArraySingle(), rightScoper.AsArraySingle());
|
||||
}
|
||||
|
||||
public static bool CanSplitScoped<T>(this string input, char splitChar, T leftScopers, T rightScopers)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
return GetScopedSplitPoints(input, splitChar, leftScopers, rightScopers).Any();
|
||||
}
|
||||
|
||||
public static string SplitFirst(this string input, char splitChar)
|
||||
{
|
||||
return input.SplitScopedFirst(splitChar, Array.Empty<char>(), Array.Empty<char>());
|
||||
}
|
||||
|
||||
public static string SplitScopedFirst(this string input, char splitChar)
|
||||
{
|
||||
return input.SplitScopedFirst(splitChar, DefaultLeftScopers, DefaultRightScopers);
|
||||
}
|
||||
|
||||
public static string SplitScopedFirst(this string input, char splitChar, char leftScoper, char rightScoper)
|
||||
{
|
||||
return input.SplitScopedFirst(splitChar, leftScoper.AsArraySingle(), rightScoper.AsArraySingle());
|
||||
}
|
||||
|
||||
public static string SplitScopedFirst<T>(this string input, char splitChar, T leftScopers, T rightScopers)
|
||||
where T : IReadOnlyList<char>
|
||||
{
|
||||
IEnumerable<int> splitPoints = GetScopedSplitPoints(input, splitChar, leftScopers, rightScopers);
|
||||
foreach (int splitPoint in splitPoints)
|
||||
{
|
||||
return input.Substring(0, splitPoint).Trim();
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
public static string UnescapeText(this string input, char escapeChar) { return input.UnescapeText(escapeChar.AsArraySingle()); }
|
||||
public static string UnescapeText<T>(this string input, T escapeChars)
|
||||
where T : IReadOnlyCollection<char>
|
||||
{
|
||||
foreach (char escapeChar in escapeChars)
|
||||
{
|
||||
input = input.Replace($"\\{escapeChar}", escapeChar.ToString());
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
public static string ReverseItems(this string input, char splitChar)
|
||||
{
|
||||
int lastSplit = input.Length;
|
||||
StringBuilder buffer = _stringBuilderPool.GetStringBuilder(input.Length);
|
||||
|
||||
for (int i = input.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (input[i] == splitChar)
|
||||
{
|
||||
int substringIndex = i + 1;
|
||||
if (substringIndex < input.Length)
|
||||
{
|
||||
buffer.Append(input, substringIndex, lastSplit - substringIndex);
|
||||
}
|
||||
|
||||
buffer.Append(splitChar);
|
||||
lastSplit = i;
|
||||
}
|
||||
else if (i == 0)
|
||||
{
|
||||
buffer.Append(input, 0, lastSplit);
|
||||
}
|
||||
}
|
||||
|
||||
return _stringBuilderPool.ReleaseAndToString(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8fc02e3f995d8444db9bad572a47be03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user