UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityEngine.InputSystem.Utilities
|
||||
{
|
||||
internal static class StringHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// For every character in <paramref name="str"/> that is contained in <paramref name="chars"/>, replace it
|
||||
/// by the corresponding character in <paramref name="replacements"/> preceded by a backslash.
|
||||
/// </summary>
|
||||
public static string Escape(this string str, string chars = "\n\t\r\\\"", string replacements = "ntr\\\"")
|
||||
{
|
||||
if (str == null)
|
||||
return null;
|
||||
|
||||
// Scan for characters that need escaping. If there's none, just return
|
||||
// string as is.
|
||||
var hasCharacterThatNeedsEscaping = false;
|
||||
foreach (var ch in str)
|
||||
{
|
||||
if (chars.Contains(ch))
|
||||
{
|
||||
hasCharacterThatNeedsEscaping = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasCharacterThatNeedsEscaping)
|
||||
return str;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
foreach (var ch in str)
|
||||
{
|
||||
var index = chars.IndexOf(ch);
|
||||
if (index == -1)
|
||||
{
|
||||
builder.Append(ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append('\\');
|
||||
builder.Append(replacements[index]);
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static string Unescape(this string str, string chars = "ntr\\\"", string replacements = "\n\t\r\\\"")
|
||||
{
|
||||
if (str == null)
|
||||
return str;
|
||||
|
||||
// If there's no backslashes in the string, there's nothing to unescape.
|
||||
if (!str.Contains('\\'))
|
||||
return str;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
for (var i = 0; i < str.Length; ++i)
|
||||
{
|
||||
var ch = str[i];
|
||||
if (ch == '\\' && i < str.Length - 2)
|
||||
{
|
||||
++i;
|
||||
ch = str[i];
|
||||
var index = chars.IndexOf(ch);
|
||||
if (index != -1)
|
||||
builder.Append(replacements[index]);
|
||||
else
|
||||
builder.Append(ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(ch);
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static bool Contains(this string str, char ch)
|
||||
{
|
||||
if (str == null)
|
||||
return false;
|
||||
return str.IndexOf(ch) != -1;
|
||||
}
|
||||
|
||||
public static bool Contains(this string str, string text, StringComparison comparison)
|
||||
{
|
||||
if (str == null)
|
||||
return false;
|
||||
return str.IndexOf(text, comparison) != -1;
|
||||
}
|
||||
|
||||
public static string GetPlural(this string str)
|
||||
{
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
|
||||
switch (str)
|
||||
{
|
||||
case "Mouse": return "Mice";
|
||||
case "mouse": return "mice";
|
||||
case "Axis": return "Axes";
|
||||
case "axis": return "axes";
|
||||
}
|
||||
|
||||
return str + 's';
|
||||
}
|
||||
|
||||
public static string NicifyMemorySize(long numBytes)
|
||||
{
|
||||
// Gigabytes.
|
||||
if (numBytes > 1024 * 1024 * 1024)
|
||||
{
|
||||
var gb = numBytes / (1024 * 1024 * 1024);
|
||||
var remainder = (numBytes % (1024 * 1024 * 1024)) / 1.0f;
|
||||
|
||||
return $"{gb + remainder} GB";
|
||||
}
|
||||
|
||||
// Megabytes.
|
||||
if (numBytes > 1024 * 1024)
|
||||
{
|
||||
var mb = numBytes / (1024 * 1024);
|
||||
var remainder = (numBytes % (1024 * 1024)) / 1.0f;
|
||||
|
||||
return $"{mb + remainder} MB";
|
||||
}
|
||||
|
||||
// Kilobytes.
|
||||
if (numBytes > 1024)
|
||||
{
|
||||
var kb = numBytes / 1024;
|
||||
var remainder = (numBytes % 1024) / 1.0f;
|
||||
|
||||
return $"{kb + remainder} KB";
|
||||
}
|
||||
|
||||
// Bytes.
|
||||
return $"{numBytes} Bytes";
|
||||
}
|
||||
|
||||
public static bool FromNicifiedMemorySize(string text, out long result, long defaultMultiplier = 1)
|
||||
{
|
||||
text = text.Trim();
|
||||
|
||||
var multiplier = defaultMultiplier;
|
||||
if (text.EndsWith("MB", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
multiplier = 1024 * 1024;
|
||||
text = text.Substring(0, text.Length - 2);
|
||||
}
|
||||
else if (text.EndsWith("GB", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
multiplier = 1024 * 1024 * 1024;
|
||||
text = text.Substring(0, text.Length - 2);
|
||||
}
|
||||
else if (text.EndsWith("KB", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
multiplier = 1024;
|
||||
text = text.Substring(0, text.Length - 2);
|
||||
}
|
||||
else if (text.EndsWith("Bytes", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
multiplier = 1;
|
||||
text = text.Substring(0, text.Length - "Bytes".Length);
|
||||
}
|
||||
|
||||
if (!long.TryParse(text, out var num))
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = num * multiplier;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int CountOccurrences(this string str, char ch)
|
||||
{
|
||||
if (str == null)
|
||||
return 0;
|
||||
|
||||
var length = str.Length;
|
||||
var index = 0;
|
||||
var count = 0;
|
||||
|
||||
while (index < length)
|
||||
{
|
||||
var nextIndex = str.IndexOf(ch, index);
|
||||
if (nextIndex == -1)
|
||||
break;
|
||||
|
||||
++count;
|
||||
index = nextIndex + 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static IEnumerable<Substring> Tokenize(this string str)
|
||||
{
|
||||
var pos = 0;
|
||||
var length = str.Length;
|
||||
|
||||
while (pos < length)
|
||||
{
|
||||
while (pos < length && char.IsWhiteSpace(str[pos]))
|
||||
++pos;
|
||||
|
||||
if (pos == length)
|
||||
break;
|
||||
|
||||
if (str[pos] == '"')
|
||||
{
|
||||
++pos;
|
||||
var endPos = pos;
|
||||
while (endPos < length && str[endPos] != '\"')
|
||||
{
|
||||
// Doesn't recognize control sequences but allows escaping double quotes.
|
||||
if (str[endPos] == '\\' && endPos < length - 1)
|
||||
++endPos;
|
||||
++endPos;
|
||||
}
|
||||
yield return new Substring(str, pos, endPos - pos);
|
||||
pos = endPos + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var endPos = pos;
|
||||
while (endPos < length && !char.IsWhiteSpace(str[endPos]))
|
||||
++endPos;
|
||||
yield return new Substring(str, pos, endPos - pos);
|
||||
pos = endPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> Split(this string str, Func<char, bool> predicate)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
yield break;
|
||||
|
||||
var length = str.Length;
|
||||
var position = 0;
|
||||
|
||||
while (position < length)
|
||||
{
|
||||
// Skip separator.
|
||||
var ch = str[position];
|
||||
if (predicate(ch))
|
||||
{
|
||||
++position;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip to next separator.
|
||||
var startPosition = position;
|
||||
++position;
|
||||
while (position < length)
|
||||
{
|
||||
ch = str[position];
|
||||
if (predicate(ch))
|
||||
break;
|
||||
++position;
|
||||
}
|
||||
var endPosition = position;
|
||||
|
||||
yield return str.Substring(startPosition, endPosition - startPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Join<TValue>(string separator, params TValue[] values)
|
||||
{
|
||||
return Join(values, separator);
|
||||
}
|
||||
|
||||
public static string Join<TValue>(IEnumerable<TValue> values, string separator)
|
||||
{
|
||||
// Optimize for there not being any values or only a single one
|
||||
// that needs no concatenation.
|
||||
var firstValue = default(string);
|
||||
var valueCount = 0;
|
||||
StringBuilder result = null;
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (value == null)
|
||||
continue;
|
||||
var str = value.ToString();
|
||||
if (string.IsNullOrEmpty(str))
|
||||
continue;
|
||||
|
||||
++valueCount;
|
||||
if (valueCount == 1)
|
||||
{
|
||||
firstValue = str;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (valueCount == 2)
|
||||
{
|
||||
result = new StringBuilder();
|
||||
result.Append(firstValue);
|
||||
}
|
||||
|
||||
result.Append(separator);
|
||||
result.Append(str);
|
||||
}
|
||||
|
||||
if (valueCount == 0)
|
||||
return null;
|
||||
if (valueCount == 1)
|
||||
return firstValue;
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
public static string MakeUniqueName<TExisting>(string baseName, IEnumerable<TExisting> existingSet,
|
||||
Func<TExisting, string> getNameFunc)
|
||||
{
|
||||
if (getNameFunc == null)
|
||||
throw new ArgumentNullException(nameof(getNameFunc));
|
||||
|
||||
if (existingSet == null)
|
||||
return baseName;
|
||||
|
||||
var name = baseName;
|
||||
var nameIsUnique = false;
|
||||
var namesTried = 1;
|
||||
|
||||
// If the name ends in digits, start counting from the given number.
|
||||
if (baseName.Length > 0)
|
||||
{
|
||||
var lastDigit = baseName.Length;
|
||||
while (lastDigit > 0 && char.IsDigit(baseName[lastDigit - 1]))
|
||||
--lastDigit;
|
||||
if (lastDigit != baseName.Length)
|
||||
{
|
||||
namesTried = int.Parse(baseName.Substring(lastDigit)) + 1;
|
||||
baseName = baseName.Substring(0, lastDigit);
|
||||
}
|
||||
}
|
||||
|
||||
// Find unique name.
|
||||
while (!nameIsUnique)
|
||||
{
|
||||
nameIsUnique = true;
|
||||
foreach (var existing in existingSet)
|
||||
{
|
||||
var existingName = getNameFunc(existing);
|
||||
if (existingName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
name = $"{baseName}{namesTried}";
|
||||
nameIsUnique = false;
|
||||
++namesTried;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
////REVIEW: should we allow whitespace and skip automatically?
|
||||
public static bool CharacterSeparatedListsHaveAtLeastOneCommonElement(string firstList, string secondList,
|
||||
char separator)
|
||||
{
|
||||
if (firstList == null)
|
||||
throw new ArgumentNullException(nameof(firstList));
|
||||
if (secondList == null)
|
||||
throw new ArgumentNullException(nameof(secondList));
|
||||
|
||||
// Go element by element through firstList and try to find a matching
|
||||
// element in secondList.
|
||||
var indexInFirst = 0;
|
||||
var lengthOfFirst = firstList.Length;
|
||||
var lengthOfSecond = secondList.Length;
|
||||
while (indexInFirst < lengthOfFirst)
|
||||
{
|
||||
// Skip empty elements.
|
||||
if (firstList[indexInFirst] == separator)
|
||||
++indexInFirst;
|
||||
|
||||
// Find end of current element.
|
||||
var endIndexInFirst = indexInFirst + 1;
|
||||
while (endIndexInFirst < lengthOfFirst && firstList[endIndexInFirst] != separator)
|
||||
++endIndexInFirst;
|
||||
var lengthOfCurrentInFirst = endIndexInFirst - indexInFirst;
|
||||
|
||||
// Go through element in secondList and match it to the current
|
||||
// element.
|
||||
var indexInSecond = 0;
|
||||
while (indexInSecond < lengthOfSecond)
|
||||
{
|
||||
// Skip empty elements.
|
||||
if (secondList[indexInSecond] == separator)
|
||||
++indexInSecond;
|
||||
|
||||
// Find end of current element.
|
||||
var endIndexInSecond = indexInSecond + 1;
|
||||
while (endIndexInSecond < lengthOfSecond && secondList[endIndexInSecond] != separator)
|
||||
++endIndexInSecond;
|
||||
var lengthOfCurrentInSecond = endIndexInSecond - indexInSecond;
|
||||
|
||||
// If length matches, do character-by-character comparison.
|
||||
if (lengthOfCurrentInFirst == lengthOfCurrentInSecond)
|
||||
{
|
||||
var startIndexInFirst = indexInFirst;
|
||||
var startIndexInSecond = indexInSecond;
|
||||
|
||||
var isMatch = true;
|
||||
for (var i = 0; i < lengthOfCurrentInFirst; ++i)
|
||||
{
|
||||
var first = firstList[startIndexInFirst + i];
|
||||
var second = secondList[startIndexInSecond + i];
|
||||
|
||||
if (char.ToLowerInvariant(first) != char.ToLowerInvariant(second))
|
||||
{
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMatch)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not a match so go to next.
|
||||
indexInSecond = endIndexInSecond + 1;
|
||||
}
|
||||
|
||||
// Go to next element.
|
||||
indexInFirst = endIndexInFirst + 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse an int at the given position in the string.
|
||||
// Unlike int.Parse(), does not require allocating a new string containing only
|
||||
// the substring with the number.
|
||||
public static int ParseInt(string str, int pos)
|
||||
{
|
||||
var multiply = 1;
|
||||
var result = 0;
|
||||
var length = str.Length;
|
||||
|
||||
while (pos < length)
|
||||
{
|
||||
var ch = str[pos];
|
||||
var digit = ch - '0';
|
||||
if (digit < 0 || digit > 9)
|
||||
break;
|
||||
|
||||
result = result * multiply + digit;
|
||||
|
||||
multiply *= 10;
|
||||
++pos;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////TODO: this should use UTF-8 and not UTF-16
|
||||
|
||||
public static bool WriteStringToBuffer(string text, IntPtr buffer, int bufferSizeInCharacters)
|
||||
{
|
||||
uint offset = 0;
|
||||
return WriteStringToBuffer(text, buffer, bufferSizeInCharacters, ref offset);
|
||||
}
|
||||
|
||||
public static unsafe bool WriteStringToBuffer(string text, IntPtr buffer, int bufferSizeInCharacters, ref uint offset)
|
||||
{
|
||||
if (buffer == IntPtr.Zero)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
var length = string.IsNullOrEmpty(text) ? 0 : text.Length;
|
||||
if (length > ushort.MaxValue)
|
||||
throw new ArgumentException(string.Format("String exceeds max size of {0} characters", ushort.MaxValue), "text");
|
||||
|
||||
var endOffset = offset + sizeof(char) * length + sizeof(int);
|
||||
if (endOffset > bufferSizeInCharacters)
|
||||
return false;
|
||||
|
||||
var ptr = ((byte*)buffer) + offset;
|
||||
*((ushort*)ptr) = (ushort)length;
|
||||
ptr += sizeof(ushort);
|
||||
|
||||
for (var i = 0; i < length; ++i, ptr += sizeof(char))
|
||||
*((char*)ptr) = text[i];
|
||||
|
||||
offset = (uint)endOffset;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string ReadStringFromBuffer(IntPtr buffer, int bufferSize)
|
||||
{
|
||||
uint offset = 0;
|
||||
return ReadStringFromBuffer(buffer, bufferSize, ref offset);
|
||||
}
|
||||
|
||||
public static unsafe string ReadStringFromBuffer(IntPtr buffer, int bufferSize, ref uint offset)
|
||||
{
|
||||
if (buffer == IntPtr.Zero)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
if (offset + sizeof(int) > bufferSize)
|
||||
return null;
|
||||
|
||||
var ptr = ((byte*)buffer) + offset;
|
||||
var length = *((ushort*)ptr);
|
||||
ptr += sizeof(ushort);
|
||||
|
||||
if (length == 0)
|
||||
return null;
|
||||
|
||||
var endOffset = offset + sizeof(char) * length + sizeof(int);
|
||||
if (endOffset > bufferSize)
|
||||
return null;
|
||||
|
||||
var text = Marshal.PtrToStringUni(new IntPtr(ptr), length);
|
||||
|
||||
offset = (uint)endOffset;
|
||||
return text;
|
||||
}
|
||||
|
||||
public static bool IsPrintable(this char ch)
|
||||
{
|
||||
// This is crude and far from how Unicode defines printable but it should serve as a good enough approximation.
|
||||
return !char.IsControl(ch) && !char.IsWhiteSpace(ch);
|
||||
}
|
||||
|
||||
public static string WithAllWhitespaceStripped(this string str)
|
||||
{
|
||||
var buffer = new StringBuilder();
|
||||
foreach (var ch in str)
|
||||
if (!char.IsWhiteSpace(ch))
|
||||
buffer.Append(ch);
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
public static bool InvariantEqualsIgnoreCase(this string left, string right)
|
||||
{
|
||||
if (string.IsNullOrEmpty(left))
|
||||
return string.IsNullOrEmpty(right);
|
||||
return string.Equals(left, right, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public static string ExpandTemplateString(string template, Func<string, string> mapFunc)
|
||||
{
|
||||
if (string.IsNullOrEmpty(template))
|
||||
throw new ArgumentNullException(nameof(template));
|
||||
if (mapFunc == null)
|
||||
throw new ArgumentNullException(nameof(mapFunc));
|
||||
|
||||
var buffer = new StringBuilder();
|
||||
|
||||
var length = template.Length;
|
||||
for (var i = 0; i < length; ++i)
|
||||
{
|
||||
var ch = template[i];
|
||||
if (ch != '{')
|
||||
{
|
||||
buffer.Append(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
++i;
|
||||
var tokenStartPos = i;
|
||||
while (i < length && template[i] != '}')
|
||||
++i;
|
||||
var token = template.Substring(tokenStartPos, i - tokenStartPos);
|
||||
// Loop increment will skip closing '}'.
|
||||
|
||||
var mapped = mapFunc(token);
|
||||
buffer.Append(mapped);
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user