2020-12-21 11:50:46 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
2020-12-28 14:34:13 -04:00
using System.Diagnostics.CodeAnalysis ;
2020-12-21 11:50:46 -04:00
using System.Linq ;
using System.Text ;
2020-12-21 23:07:37 -04:00
namespace EpicGames.Core
2020-12-21 11:50:46 -04:00
{
public static class StringUtils
{
2020-12-28 14:34:13 -04:00
/// <summary>
/// Array mapping from ascii index to hexadecimal digits.
/// </summary>
2022-03-24 16:35:00 -04:00
static readonly sbyte [ ] s_hexDigits ;
2020-12-28 14:34:13 -04:00
2021-06-17 14:41:07 -04:00
/// <summary>
/// Hex digits to utf8 byte
/// </summary>
2022-03-24 16:35:00 -04:00
static readonly byte [ ] s_hexDigitToUtf8Byte = Encoding . UTF8 . GetBytes ( "0123456789abcdef" ) ;
2021-06-17 14:41:07 -04:00
2021-04-08 16:20:00 -04:00
/// <summary>
/// Array mapping human readable size of bytes, 1024^x. long max is within the range of Exabytes.
/// </summary>
2022-03-24 16:35:00 -04:00
static readonly string [ ] s_byteSizes = { "B" , "KB" , "MB" , "GB" , "TB" , "PB" , "EB" } ;
2021-04-08 16:20:00 -04:00
2020-12-28 14:34:13 -04:00
/// <summary>
/// Static constructor. Initializes the HexDigits array.
/// </summary>
static StringUtils ( )
{
2022-03-24 16:35:00 -04:00
s_hexDigits = new sbyte [ 256 ] ;
for ( int idx = 0 ; idx < 256 ; idx + + )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
s_hexDigits [ idx ] = - 1 ;
2020-12-28 14:34:13 -04:00
}
2022-03-24 16:35:00 -04:00
for ( int idx = '0' ; idx < = '9' ; idx + + )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
s_hexDigits [ idx ] = ( sbyte ) ( idx - '0' ) ;
2020-12-28 14:34:13 -04:00
}
2022-03-24 16:35:00 -04:00
for ( int idx = 'a' ; idx < = 'f' ; idx + + )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
s_hexDigits [ idx ] = ( sbyte ) ( 10 + idx - 'a' ) ;
2020-12-28 14:34:13 -04:00
}
2022-03-24 16:35:00 -04:00
for ( int idx = 'A' ; idx < = 'F' ; idx + + )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
s_hexDigits [ idx ] = ( sbyte ) ( 10 + idx - 'A' ) ;
2020-12-28 14:34:13 -04:00
}
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Indents a string by a given indent
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="text">The text to indent</param>
/// <param name="indent">The indent to add to each line</param>
2020-12-21 11:50:46 -04:00
/// <returns>The indented string</returns>
2022-03-24 16:35:00 -04:00
public static string Indent ( string text , string indent )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
string result = "" ;
if ( text . Length > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
result = indent + text . Replace ( "\n" , "\n" + indent ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return result ;
2020-12-21 11:50:46 -04:00
}
2020-12-28 14:34:13 -04:00
/// <summary>
/// Expand all the property references (of the form $(PropertyName)) in a string.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="text">The input string to expand properties in</param>
/// <param name="properties">Dictionary of properties to expand</param>
2020-12-28 14:34:13 -04:00
/// <returns>The expanded string</returns>
2022-03-24 16:35:00 -04:00
public static string ExpandProperties ( string text , Dictionary < string , string > properties )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
return ExpandProperties ( text , name = >
{
properties . TryGetValue ( name , out string? value ) ;
return value ;
} ) ;
2020-12-28 14:34:13 -04:00
}
/// <summary>
/// Expand all the property references (of the form $(PropertyName)) in a string.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="text">The input string to expand properties in</param>
/// <param name="getPropertyValue">Delegate to retrieve a property value</param>
2020-12-28 14:34:13 -04:00
/// <returns>The expanded string</returns>
2022-03-24 16:35:00 -04:00
public static string ExpandProperties ( string text , Func < string , string? > getPropertyValue )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
string result = text ;
for ( int idx = result . IndexOf ( "$(" ) ; idx ! = - 1 ; idx = result . IndexOf ( "$(" , idx ) )
2020-12-28 14:34:13 -04:00
{
// Find the end of the variable name
2022-03-24 16:35:00 -04:00
int endIdx = result . IndexOf ( ')' , idx + 2 ) ;
if ( endIdx = = - 1 )
2020-12-28 14:34:13 -04:00
{
break ;
}
// Extract the variable name from the string
2022-03-24 16:35:00 -04:00
string name = result . Substring ( idx + 2 , endIdx - ( idx + 2 ) ) ;
2020-12-28 14:34:13 -04:00
// Check if we've got a value for this variable
2022-03-24 16:35:00 -04:00
string? value = getPropertyValue ( name ) ;
if ( value = = null )
2020-12-28 14:34:13 -04:00
{
// Do not expand it; must be preprocessing the script.
2022-03-24 16:35:00 -04:00
idx = endIdx ;
2020-12-28 14:34:13 -04:00
}
else
{
// Replace the variable, or skip past it
2022-03-24 16:35:00 -04:00
result = result . Substring ( 0 , idx ) + value + result . Substring ( endIdx + 1 ) ;
2020-12-28 14:34:13 -04:00
// Make sure we skip over the expanded variable; we don't want to recurse on it.
2022-03-24 16:35:00 -04:00
idx + = value . Length ;
2020-12-28 14:34:13 -04:00
}
}
2022-03-24 16:35:00 -04:00
return result ;
2020-12-28 14:34:13 -04:00
}
2022-03-24 16:35:00 -04:00
/// <inheritdoc cref="WordWrap(String, Int32, Int32, Int32)">
public static IEnumerable < string > WordWrap ( string text , int maxWidth )
2022-01-03 17:00:37 -05:00
{
2022-03-24 16:35:00 -04:00
return WordWrap ( text , 0 , 0 , maxWidth ) ;
2022-01-03 17:00:37 -05:00
}
2020-12-21 11:50:46 -04:00
/// <summary>
2022-01-03 17:00:37 -05:00
/// Takes a given sentence and wraps it on a word by word basis so that no line exceeds the set maximum line length. Words longer than a line
/// are broken up. Returns the sentence as a list of individual lines.
2020-12-21 11:50:46 -04:00
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="text">The text to be wrapped</param>
/// <param name="initialIndent">Indent for the first line</param>
/// <param name="hangingIndent">Indent for subsequent lines</param>
/// <param name="maxWidth">The maximum (non negative) length of the returned sentences</param>
public static IEnumerable < string > WordWrap ( string text , int initialIndent , int hangingIndent , int maxWidth )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
StringBuilder builder = new StringBuilder ( ) ;
2022-01-03 17:00:37 -05:00
2022-03-24 16:35:00 -04:00
int minIdx = 0 ;
for ( int lineIdx = 0 ; minIdx < text . Length ; lineIdx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
int indent = ( lineIdx = = 0 ) ? initialIndent : hangingIndent ;
int maxWidthForLine = maxWidth - indent ;
int maxIdx = GetWordWrapLineEnd ( text , minIdx , maxWidthForLine ) ;
2022-01-03 17:00:37 -05:00
2022-03-24 16:35:00 -04:00
int printMaxIdx = maxIdx ;
while ( printMaxIdx > minIdx & & Char . IsWhiteSpace ( text [ printMaxIdx - 1 ] ) )
2022-01-03 17:00:37 -05:00
{
2022-03-24 16:35:00 -04:00
printMaxIdx - - ;
2022-01-03 17:00:37 -05:00
}
2022-03-24 16:35:00 -04:00
builder . Clear ( ) ;
builder . Append ( ' ' , indent ) ;
builder . Append ( text , minIdx , printMaxIdx - minIdx ) ;
yield return builder . ToString ( ) ;
2022-01-03 17:00:37 -05:00
2022-03-24 16:35:00 -04:00
minIdx = maxIdx ;
2022-01-03 17:00:37 -05:00
}
}
/// <summary>
/// Gets the next character index to end a word-wrapped line on
/// </summary>
2022-03-24 16:35:00 -04:00
static int GetWordWrapLineEnd ( string text , int minIdx , int maxWidth )
2022-01-03 17:00:37 -05:00
{
2022-03-24 16:35:00 -04:00
maxWidth = Math . Min ( maxWidth , text . Length - minIdx ) ;
2022-01-10 09:33:30 -05:00
2022-03-24 16:35:00 -04:00
int maxIdx = text . IndexOf ( '\n' , minIdx , maxWidth ) ;
if ( maxIdx = = - 1 )
2022-01-03 17:00:37 -05:00
{
2022-03-24 16:35:00 -04:00
maxIdx = minIdx + maxWidth ;
2022-01-10 09:33:30 -05:00
}
else
{
2022-03-24 16:35:00 -04:00
return maxIdx + 1 ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
if ( maxIdx = = text . Length )
2022-01-10 09:33:30 -05:00
{
2022-03-24 16:35:00 -04:00
return maxIdx ;
2022-01-10 09:33:30 -05:00
}
2022-03-24 16:35:00 -04:00
else if ( Char . IsWhiteSpace ( text [ maxIdx ] ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
for ( ; ; maxIdx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( maxIdx = = text . Length )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return maxIdx ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
if ( text [ maxIdx ] ! = ' ' )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return maxIdx ;
2020-12-21 11:50:46 -04:00
}
}
}
2022-01-03 17:00:37 -05:00
else
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
for ( int tryMaxIdx = maxIdx ; ; tryMaxIdx - - )
2022-01-03 17:00:37 -05:00
{
2022-03-24 16:35:00 -04:00
if ( tryMaxIdx = = minIdx )
2022-01-03 17:00:37 -05:00
{
2022-03-24 16:35:00 -04:00
return maxIdx ;
2022-01-03 17:00:37 -05:00
}
2022-03-24 16:35:00 -04:00
if ( text [ tryMaxIdx - 1 ] = = ' ' )
2022-01-03 17:00:37 -05:00
{
2022-03-24 16:35:00 -04:00
return tryMaxIdx ;
2022-01-03 17:00:37 -05:00
}
}
2020-12-21 11:50:46 -04:00
}
}
/// <summary>
/// Extension method to allow formatting a string to a stringbuilder and appending a newline
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="builder">The string builder</param>
/// <param name="format">Format string, as used for StringBuilder.AppendFormat</param>
/// <param name="args">Arguments for the format string</param>
public static void AppendLine ( this StringBuilder builder , string format , params object [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
builder . AppendFormat ( format , args ) ;
builder . AppendLine ( ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Formats a list of strings in the style "1, 2, 3 and 4"
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="arguments">List of strings to format</param>
/// <param name="conjunction">Conjunction to use between the last two items in the list (eg. "and" or "or")</param>
2020-12-21 11:50:46 -04:00
/// <returns>Formatted list of strings</returns>
2022-04-27 16:29:08 -04:00
public static string FormatList ( IReadOnlyList < string > arguments , string conjunction = "and" )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
StringBuilder result = new StringBuilder ( ) ;
2022-04-27 16:29:08 -04:00
if ( arguments . Count > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
result . Append ( arguments [ 0 ] ) ;
2022-04-27 16:29:08 -04:00
for ( int idx = 1 ; idx < arguments . Count ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-04-27 16:29:08 -04:00
if ( idx = = arguments . Count - 1 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
result . AppendFormat ( " {0} " , conjunction ) ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
result . Append ( ", " ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
result . Append ( arguments [ idx ] ) ;
2020-12-21 11:50:46 -04:00
}
}
2022-03-24 16:35:00 -04:00
return result . ToString ( ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Formats a list of strings in the style "1, 2, 3 and 4"
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="arguments">List of strings to format</param>
/// <param name="conjunction">Conjunction to use between the last two items in the list (eg. "and" or "or")</param>
2020-12-21 11:50:46 -04:00
/// <returns>Formatted list of strings</returns>
2022-03-24 16:35:00 -04:00
public static string FormatList ( IEnumerable < string > arguments , string conjunction = "and" )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return FormatList ( arguments . ToArray ( ) , conjunction ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
2020-12-28 14:34:13 -04:00
/// Formats a list of items
2020-12-21 11:50:46 -04:00
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="items">Array of items</param>
/// <param name="maxCount">Maximum number of items to include in the list</param>
2020-12-28 14:34:13 -04:00
/// <returns>Formatted list of items</returns>
2022-03-24 16:35:00 -04:00
public static string FormatList ( string [ ] items , int maxCount )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( items . Length = = 0 )
2020-12-21 11:50:46 -04:00
{
2020-12-28 14:34:13 -04:00
return "unknown" ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
else if ( items . Length = = 1 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return items [ 0 ] ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
else if ( items . Length < = maxCount )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return $"{String.Join(" , ", items.Take(items.Length - 1))} and {items.Last()}" ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
return $"{String.Join(" , ", items.Take(maxCount - 1))} and {items.Length - (maxCount - 1)} others" ;
2020-12-21 11:50:46 -04:00
}
}
2020-12-28 14:34:13 -04:00
/// <summary>
/// Parses a hexadecimal digit
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="character">Character to parse</param>
2020-12-28 14:34:13 -04:00
/// <returns>Value of this digit, or -1 if invalid</returns>
2022-03-24 16:35:00 -04:00
public static int GetHexDigit ( byte character )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
return s_hexDigits [ character ] ;
2020-12-28 14:34:13 -04:00
}
/// <summary>
/// Parses a hexadecimal digit
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="character">Character to parse</param>
2020-12-28 14:34:13 -04:00
/// <returns>Value of this digit, or -1 if invalid</returns>
2022-03-24 16:35:00 -04:00
public static int GetHexDigit ( char character )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
return s_hexDigits [ Math . Min ( ( uint ) character , 127 ) ] ;
2020-12-28 14:34:13 -04:00
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Parses a hexadecimal string into an array of bytes
/// </summary>
/// <returns>Array of bytes</returns>
2022-03-24 16:35:00 -04:00
public static byte [ ] ParseHexString ( string text )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
byte [ ] ? bytes ;
if ( ! TryParseHexString ( text , out bytes ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new FormatException ( String . Format ( "Invalid hex string: '{0}'" , text ) ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return bytes ;
2020-12-21 11:50:46 -04:00
}
2020-12-28 14:34:13 -04:00
/// <summary>
/// Parses a hexadecimal string into an array of bytes
/// </summary>
/// <returns>Array of bytes</returns>
2022-03-24 16:35:00 -04:00
public static byte [ ] ParseHexString ( ReadOnlySpan < byte > text )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
byte [ ] ? bytes ;
if ( ! TryParseHexString ( text , out bytes ) )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
throw new FormatException ( $"Invalid hex string: '{Encoding.UTF8.GetString(text)}'" ) ;
2020-12-28 14:34:13 -04:00
}
2022-03-24 16:35:00 -04:00
return bytes ;
2020-12-28 14:34:13 -04:00
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Parses a hexadecimal string into an array of bytes
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="text">Text to parse</param>
2020-12-21 11:50:46 -04:00
/// <returns></returns>
2022-03-24 16:35:00 -04:00
public static bool TryParseHexString ( string text , [ NotNullWhen ( true ) ] out byte [ ] ? outBytes )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( ( text . Length & 1 ) ! = 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
outBytes = null ;
2022-02-28 12:55:47 -05:00
return false ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
byte [ ] bytes = new byte [ text . Length / 2 ] ;
for ( int idx = 0 ; idx < text . Length ; idx + = 2 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
int value = ( GetHexDigit ( text [ idx ] ) < < 4 ) | GetHexDigit ( text [ idx + 1 ] ) ;
if ( value < 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
outBytes = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
2022-03-24 16:35:00 -04:00
bytes [ idx / 2 ] = ( byte ) value ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
outBytes = bytes ;
2020-12-21 11:50:46 -04:00
return true ;
}
2020-12-28 14:34:13 -04:00
/// <summary>
/// Parses a hexadecimal string into an array of bytes
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="text">Text to parse</param>
2020-12-28 14:34:13 -04:00
/// <returns></returns>
2022-03-24 16:35:00 -04:00
public static bool TryParseHexString ( ReadOnlySpan < byte > text , [ NotNullWhen ( true ) ] out byte [ ] ? outBytes )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
if ( ( text . Length & 1 ) ! = 0 )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
outBytes = null ;
2022-02-28 12:55:47 -05:00
return false ;
2020-12-28 14:34:13 -04:00
}
2022-03-24 16:35:00 -04:00
byte [ ] bytes = new byte [ text . Length / 2 ] ;
for ( int idx = 0 ; idx < text . Length ; idx + = 2 )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
int value = ParseHexByte ( text , idx ) ;
if ( value < 0 )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
outBytes = null ;
2020-12-28 14:34:13 -04:00
return false ;
}
2022-03-24 16:35:00 -04:00
bytes [ idx / 2 ] = ( byte ) value ;
2020-12-28 14:34:13 -04:00
}
2022-03-24 16:35:00 -04:00
outBytes = bytes ;
2020-12-28 14:34:13 -04:00
return true ;
}
/// <summary>
/// Parse a hex byte from the given offset into a span of utf8 characters
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="text">The text to parse</param>
/// <param name="idx">Index within the text to parse</param>
2020-12-28 14:34:13 -04:00
/// <returns>The parsed value, or a negative value on error</returns>
2022-03-24 16:35:00 -04:00
public static int ParseHexByte ( ReadOnlySpan < byte > text , int idx )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
return ( ( int ) s_hexDigits [ text [ idx ] ] < < 4 ) | ( ( int ) s_hexDigits [ text [ idx + 1 ] ] ) ;
2020-12-28 14:34:13 -04:00
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Formats an array of bytes as a hexadecimal string
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="bytes">An array of bytes</param>
2020-12-21 11:50:46 -04:00
/// <returns>String representation of the array</returns>
2022-03-24 16:35:00 -04:00
public static string FormatHexString ( byte [ ] bytes )
2021-02-08 14:09:52 -04:00
{
2022-03-24 16:35:00 -04:00
return FormatHexString ( bytes . AsSpan ( ) ) ;
2021-02-08 14:09:52 -04:00
}
/// <summary>
/// Formats an array of bytes as a hexadecimal string
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="bytes">An array of bytes</param>
2021-02-08 14:09:52 -04:00
/// <returns>String representation of the array</returns>
2022-03-24 16:35:00 -04:00
public static string FormatHexString ( ReadOnlySpan < byte > bytes )
2020-12-21 11:50:46 -04:00
{
const string HexDigits = "0123456789abcdef" ;
2022-03-24 16:35:00 -04:00
char [ ] characters = new char [ bytes . Length * 2 ] ;
for ( int idx = 0 ; idx < bytes . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
characters [ idx * 2 + 0 ] = HexDigits [ bytes [ idx ] > > 4 ] ;
characters [ idx * 2 + 1 ] = HexDigits [ bytes [ idx ] & 15 ] ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return new string ( characters ) ;
2020-12-21 11:50:46 -04:00
}
2020-12-28 14:34:13 -04:00
2021-06-17 14:41:07 -04:00
/// <summary>
/// Formats an array of bytes as a hexadecimal string
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="bytes">An array of bytes</param>
2021-06-17 14:41:07 -04:00
/// <returns>String representation of the array</returns>
2022-03-24 16:35:00 -04:00
public static Utf8String FormatUtf8HexString ( ReadOnlySpan < byte > bytes )
2021-06-17 14:41:07 -04:00
{
2022-03-24 16:35:00 -04:00
byte [ ] characters = new byte [ bytes . Length * 2 ] ;
for ( int idx = 0 ; idx < bytes . Length ; idx + + )
2021-06-17 14:41:07 -04:00
{
2022-03-24 16:35:00 -04:00
characters [ idx * 2 + 0 ] = s_hexDigitToUtf8Byte [ bytes [ idx ] > > 4 ] ;
characters [ idx * 2 + 1 ] = s_hexDigitToUtf8Byte [ bytes [ idx ] & 15 ] ;
2021-06-17 14:41:07 -04:00
}
2022-03-24 16:35:00 -04:00
return new Utf8String ( characters ) ;
2021-06-17 14:41:07 -04:00
}
2020-12-28 14:34:13 -04:00
/// <summary>
/// Quotes a string as a command line argument
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="str">The string to quote</param>
2020-12-28 14:34:13 -04:00
/// <returns>The quoted argument if it contains any spaces, otherwise the original string</returns>
2022-03-24 16:35:00 -04:00
public static string QuoteArgument ( this string str )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
if ( str . Contains ( ' ' ) )
2020-12-28 14:34:13 -04:00
{
2022-03-24 16:35:00 -04:00
return $"\" { str } \ "" ;
2020-12-28 14:34:13 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
return str ;
2020-12-28 14:34:13 -04:00
}
}
2021-04-08 16:20:00 -04:00
/// <summary>
/// Formats bytes into a human readable string
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="bytes">The total number of bytes</param>
/// <param name="decimalPlaces">The number of decimal places to round the resulting value</param>
2021-04-08 16:20:00 -04:00
/// <returns>Human readable string based on the value of Bytes</returns>
2022-03-24 16:35:00 -04:00
public static string FormatBytesString ( long bytes , int decimalPlaces = 2 )
2021-04-08 16:20:00 -04:00
{
2022-03-24 16:35:00 -04:00
if ( bytes = = 0 )
2021-04-08 16:20:00 -04:00
{
2022-03-24 16:35:00 -04:00
return $"0 {s_byteSizes[0]}" ;
2021-04-08 16:20:00 -04:00
}
2022-03-24 16:35:00 -04:00
long bytesAbs = Math . Abs ( bytes ) ;
int power = Convert . ToInt32 ( Math . Floor ( Math . Log ( bytesAbs , 1024 ) ) ) ;
double value = Math . Round ( bytesAbs / Math . Pow ( 1024 , power ) , decimalPlaces ) ;
return $"{(Math.Sign(bytes) * value)} {s_byteSizes[power]}" ;
2021-04-08 16:20:00 -04:00
}
2021-05-21 04:04:11 -04:00
/// <summary>
/// Converts a bytes string into bytes. E.g 1.2KB -> 1229
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="bytesString"></param>
2021-05-21 04:04:11 -04:00
/// <returns></returns>
2022-03-24 16:35:00 -04:00
public static long ParseBytesString ( string bytesString )
2021-05-21 04:04:11 -04:00
{
2022-03-24 16:35:00 -04:00
bytesString = bytesString . Trim ( ) ;
2021-05-21 04:04:11 -04:00
2022-03-24 16:35:00 -04:00
int power = s_byteSizes . FindIndex ( s = > ( s ! = s_byteSizes [ 0 ] ) & & bytesString . EndsWith ( s , StringComparison . InvariantCultureIgnoreCase ) ) ; // need to handle 'B' suffix separately
if ( power = = - 1 & & bytesString . EndsWith ( s_byteSizes [ 0 ] ) )
2021-05-21 04:04:11 -04:00
{
2022-03-24 16:35:00 -04:00
power = 0 ;
2021-05-21 04:04:11 -04:00
}
2022-03-24 16:35:00 -04:00
if ( power ! = - 1 )
2021-05-21 04:04:11 -04:00
{
2022-03-24 16:35:00 -04:00
bytesString = bytesString . Substring ( 0 , bytesString . Length - s_byteSizes [ power ] . Length ) ;
2021-05-21 04:04:11 -04:00
}
2022-03-24 16:35:00 -04:00
double value = Double . Parse ( bytesString ) ;
if ( power > 0 )
2021-05-21 04:04:11 -04:00
{
2022-03-24 16:35:00 -04:00
value * = Math . Pow ( 1024 , power ) ;
2021-05-21 04:04:11 -04:00
}
2022-03-24 16:35:00 -04:00
return ( long ) Math . Round ( value ) ;
2021-05-21 04:04:11 -04:00
}
/// <summary>
/// Converts a bytes string into bytes. E.g 1.5KB -> 1536
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="bytesString"></param>
2021-05-21 04:04:11 -04:00
/// <returns></returns>
2022-03-24 16:35:00 -04:00
public static bool TryParseBytesString ( string bytesString , out long? bytes )
2021-05-21 04:04:11 -04:00
{
try
{
2022-03-24 16:35:00 -04:00
bytes = ParseBytesString ( bytesString ) ;
2021-05-21 04:04:11 -04:00
return true ;
}
catch ( Exception )
{
}
2022-03-24 16:35:00 -04:00
bytes = null ;
2021-05-21 04:04:11 -04:00
return false ;
}
2021-12-16 11:24:47 -05:00
/// <summary>
/// Parses a string to remove VT100 escape codes
/// </summary>
/// <returns></returns>
2022-03-24 16:35:00 -04:00
public static string ParseEscapeCodes ( string line )
2021-12-16 11:24:47 -05:00
{
2022-03-24 16:35:00 -04:00
char escapeChar = ' \ u001b ' ;
2021-12-16 11:24:47 -05:00
2022-03-24 16:35:00 -04:00
int index = line . IndexOf ( escapeChar ) ;
if ( index ! = - 1 )
2021-12-16 11:24:47 -05:00
{
2022-03-24 16:35:00 -04:00
int lastIndex = 0 ;
2021-12-16 11:24:47 -05:00
2022-03-24 16:35:00 -04:00
StringBuilder result = new StringBuilder ( ) ;
2021-12-16 11:24:47 -05:00
for ( ; ; )
{
2022-03-24 16:35:00 -04:00
result . Append ( line , lastIndex , index - lastIndex ) ;
2021-12-16 11:24:47 -05:00
2022-03-24 16:35:00 -04:00
while ( index < line . Length )
2021-12-16 11:24:47 -05:00
{
2022-03-24 16:35:00 -04:00
char character = line [ index ] ;
if ( ( character > = 'a' & & character < = 'z' ) | | ( character > = 'A' & & character < = 'Z' ) )
2021-12-16 11:24:47 -05:00
{
2022-03-24 16:35:00 -04:00
index + + ;
2021-12-16 11:24:47 -05:00
break ;
}
2022-03-24 16:35:00 -04:00
index + + ;
2021-12-16 11:24:47 -05:00
}
2022-03-24 16:35:00 -04:00
lastIndex = index ;
2021-12-16 11:24:47 -05:00
2022-03-24 16:35:00 -04:00
index = line . IndexOf ( escapeChar , index ) ;
if ( index = = - 1 )
2021-12-16 11:24:47 -05:00
{
break ;
}
}
2022-03-24 16:35:00 -04:00
result . Append ( line , lastIndex , line . Length - lastIndex ) ;
2021-12-16 11:24:47 -05:00
2022-03-24 16:35:00 -04:00
line = result . ToString ( ) ;
2021-12-16 11:24:47 -05:00
}
2022-03-24 16:35:00 -04:00
return line ;
2021-12-16 11:24:47 -05:00
}
2022-01-10 09:33:30 -05:00
/// <summary>
/// Truncates the given string to the maximum length, appending an elipsis if it is longer than allowed.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="text"></param>
/// <param name="maxLength"></param>
2022-01-10 09:33:30 -05:00
/// <returns></returns>
2022-03-24 16:35:00 -04:00
public static string Truncate ( string text , int maxLength )
2022-01-10 09:33:30 -05:00
{
2022-03-24 16:35:00 -04:00
if ( text . Length > maxLength )
2022-01-10 09:33:30 -05:00
{
2022-03-24 16:35:00 -04:00
text = text . Substring ( 0 , maxLength - 3 ) + "..." ;
2022-01-10 09:33:30 -05:00
}
2022-03-24 16:35:00 -04:00
return text ;
2022-01-10 09:33:30 -05:00
}
2022-02-17 07:40:56 -05:00
/// <summary>
/// Compare two strings using UnrealEngine's ignore case algorithm
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="x">First string to compare</param>
/// <param name="y">Second string to compare</param>
2022-02-17 07:40:56 -05:00
/// <returns>Less than zero if X < Y, zero if X == Y, and greater than zero if X > y</returns>
2022-03-24 16:35:00 -04:00
public static int CompareIgnoreCaseUe ( ReadOnlySpan < char > x , ReadOnlySpan < char > y )
2022-02-17 07:40:56 -05:00
{
2022-03-24 16:35:00 -04:00
int length = x . Length < y . Length ? x . Length : y . Length ;
2022-02-17 07:40:56 -05:00
2022-03-24 16:35:00 -04:00
for ( int index = 0 ; index < length ; + + index )
2022-02-17 07:40:56 -05:00
{
2022-03-24 16:35:00 -04:00
char xc = x [ index ] ;
char yc = y [ index ] ;
if ( xc = = yc )
2022-02-17 07:40:56 -05:00
{
continue ;
}
2022-03-24 16:35:00 -04:00
else if ( ( ( xc | yc ) & 0xffffff80 ) = = 0 ) // if (BothAscii)
2022-02-17 07:40:56 -05:00
{
2022-03-24 16:35:00 -04:00
if ( xc > = 'A' & & xc < = 'Z' )
2022-02-17 07:40:56 -05:00
{
2022-03-24 16:35:00 -04:00
xc + = ( char ) 32 ;
2022-02-17 07:40:56 -05:00
}
2022-03-24 16:35:00 -04:00
if ( yc > = 'A' & & yc < = 'Z' )
2022-02-17 07:40:56 -05:00
{
2022-03-24 16:35:00 -04:00
yc + = ( char ) 32 ;
2022-02-17 07:40:56 -05:00
}
2022-03-24 16:35:00 -04:00
int diff = xc - yc ;
if ( diff ! = 0 )
2022-02-17 07:40:56 -05:00
{
2022-03-24 16:35:00 -04:00
return diff ;
2022-02-17 07:40:56 -05:00
}
}
else
{
2022-03-24 16:35:00 -04:00
return xc - yc ;
2022-02-17 07:40:56 -05:00
}
}
2022-03-24 16:35:00 -04:00
if ( x . Length = = length )
2022-02-17 07:40:56 -05:00
{
2022-03-24 16:35:00 -04:00
return y . Length = = length ? 0 : /* X[Length] */ - y [ length ] ;
2022-02-17 07:40:56 -05:00
}
else
{
2022-03-24 16:35:00 -04:00
return x [ length ] /* - Y[Length] */ ;
2022-02-17 07:40:56 -05:00
}
}
2020-12-21 11:50:46 -04:00
}
}