Created
July 31, 2020 23:13
-
-
Save JakeSays/2aba0c5af54ebe0fc08b768058020a35 to your computer and use it in GitHub Desktop.
String things
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Text; | |
using JetBrains.Annotations; | |
namespace Csx.Utility | |
{ | |
[PublicAPI] | |
public static class StringExtensions | |
{ | |
[ContractAnnotation("value:null => true")] | |
public static bool NotUseful(this string value) => | |
string.IsNullOrEmpty(value) || | |
string.IsNullOrWhiteSpace(value); | |
public static bool IsUseful(this string value) => !value.NotUseful(); | |
[ContractAnnotation("value:null => true")] | |
public static bool IsNullOrEmpty(this string value) => string.IsNullOrEmpty(value); | |
[ContractAnnotation("value:null => false")] | |
public static bool NotNullOrEmpty(this string value) => !string.IsNullOrEmpty(value); | |
[ContractAnnotation("value:null => true")] | |
public static bool IsNullOrWhiteSpace(this string value) => string.IsNullOrWhiteSpace(value); | |
[ContractAnnotation("value:null => false")] | |
public static bool NotNullOrWhiteSpace(this string value) => !string.IsNullOrWhiteSpace(value); | |
//^ was return !string.IsNullOrEmpty(value); | |
public static string ValueOrEmpty(this string value) => | |
string.IsNullOrEmpty(value) | |
? "" | |
: value; | |
public static string Flatten(this string[] values, string separator) => | |
values == null | |
? "" | |
: string.Join(separator, values); | |
public static string ValueOrNull(this string value) => | |
string.IsNullOrEmpty(value) | |
? null | |
: value; | |
public static string Last(this string value, int charCount) | |
{ | |
if (value == null) | |
{ | |
throw new ArgumentNullException(nameof(value)); | |
} | |
var len = value.Length; | |
return len <= charCount | |
? value | |
: value.Substring(len - charCount, charCount); | |
} | |
public static string[] Trim(this string[] value) | |
{ | |
if (value == null) | |
{ | |
throw new ArgumentNullException(nameof(value)); | |
} | |
for (var idx = 0; idx < value.Length; idx++) | |
{ | |
value[idx] = value[idx].SafeTrim(); | |
} | |
return value; | |
} | |
public static string ValueOrDefault(this string value, string defaultValue) => | |
string.IsNullOrEmpty(value) | |
? defaultValue | |
: value; | |
public static string SafeTrim(this string value) => value?.Trim(); | |
public static string SafeToUpperInvariant(this string value) => value?.ToUpperInvariant(); | |
public static string SafeToLowerInvariant(this string value) => value?.ToLowerInvariant(); | |
public static string Substring(this string value, string leftDelimiter, string rightDelimiter) | |
{ | |
if (value == null) | |
{ | |
throw new ArgumentNullException(nameof(value)); | |
} | |
var startPos = 0; | |
var endPos = 0; | |
if (!IsNullOrEmpty(leftDelimiter)) | |
{ | |
startPos = value.IndexOf(leftDelimiter, StringComparison.InvariantCulture); | |
if (startPos == -1) | |
{ | |
return null; | |
} | |
startPos += leftDelimiter.Length; | |
} | |
if (!IsNullOrEmpty(rightDelimiter)) | |
{ | |
endPos = value.IndexOf(rightDelimiter, StringComparison.InvariantCulture); | |
if (endPos == -1) | |
{ | |
return null; | |
} | |
} | |
if (endPos == 0) | |
{ | |
endPos = value.Length; | |
} | |
var substr = value.Substring(startPos, endPos - startPos); | |
return substr; | |
} | |
public static bool EqualsInvariant(this string lhs, string rhs) => | |
string.Equals(lhs, rhs, StringComparison.InvariantCulture); | |
public static bool EqualsInvariantIgnoreCase(this string lhs, string rhs) => | |
string.Equals(lhs, rhs, StringComparison.InvariantCultureIgnoreCase); | |
public static string RemovePrefix(this string src, | |
string prefix, | |
StringComparison comparison = StringComparison.Ordinal) | |
{ | |
if (src == null) | |
{ | |
return null; | |
} | |
if (IsNullOrEmpty(prefix)) | |
{ | |
throw new ArgumentNullException(nameof(prefix)); | |
} | |
if (src == prefix) | |
{ | |
return ""; | |
} | |
return src.StartsWith(prefix, comparison) | |
? src.Substring(prefix.Length) | |
: src; | |
} | |
public static byte[] Chop(this byte[] src, int maxLength) | |
{ | |
if (src == null) | |
{ | |
return null; | |
} | |
if (src.Length <= maxLength) | |
{ | |
return src; | |
} | |
var result = new byte[maxLength]; | |
Buffer.BlockCopy(src, 0, result, 0, maxLength); | |
return result; | |
} | |
public static string Chop(this string src, int maxLength) | |
{ | |
if (src == null) | |
{ | |
return null; | |
} | |
if (maxLength < 0) | |
{ | |
throw new ArgumentOutOfRangeException(nameof(maxLength)); | |
} | |
if (src.Length == 0 || | |
src.Length == maxLength) | |
{ | |
return src; | |
} | |
return src.Substring(0, Math.Min(src.Length, maxLength)); | |
} | |
public static string WordWrap(string text, int width) | |
{ | |
//lifted from: http://www.blackbeltcoder.com/Articles/strings/implementing-word-wrap-in-email-messages | |
//and improved | |
if (text == null) | |
{ | |
throw new ArgumentNullException(nameof(text)); | |
} | |
if (width < 1) | |
{ | |
throw new ArgumentOutOfRangeException(nameof(width)); | |
} | |
int pos, next; | |
var sb = new StringBuilder(); | |
// Parse each line of text | |
for (pos = 0; pos < text.Length; pos = next) | |
{ | |
// Find end of line | |
var eol = text.IndexOf(System.Environment.NewLine, pos, StringComparison.Ordinal); | |
if (eol == -1) | |
{ | |
next = eol = text.Length; | |
} | |
else | |
{ | |
next = eol + System.Environment.NewLine.Length; | |
} | |
// Copy this line of text, breaking into smaller lines as needed | |
if (eol > pos) | |
{ | |
do | |
{ | |
var len = eol - pos; | |
if (len > width) | |
{ | |
len = BreakLine(text, pos, width); | |
} | |
sb.Append(text, pos, len); | |
sb.Append(System.Environment.NewLine); | |
// Trim whitespace following break | |
pos += len; | |
while (pos < eol && | |
char.IsWhiteSpace(text[pos])) | |
{ | |
pos++; | |
} | |
} while (eol > pos); | |
} | |
else | |
{ | |
sb.Append(System.Environment.NewLine); // Empty line | |
} | |
} | |
return sb.ToString(); | |
} | |
private static int BreakLine(string text, int pos, int max) | |
{ | |
// Find last whitespace in line | |
var i = max; | |
while (i >= 0 && | |
!char.IsWhiteSpace(text[pos + i])) | |
{ | |
i--; | |
} | |
// If no whitespace found, break at maximum length | |
if (i < 0) | |
{ | |
return max; | |
} | |
// Find start of whitespace | |
while (i >= 0 && | |
char.IsWhiteSpace(text[pos + i])) | |
{ | |
i--; | |
} | |
// Return length of text before whitespace | |
return i + 1; | |
} | |
public static List<string> BreakIntoLines(string text, int width) | |
{ | |
if (text == null) | |
{ | |
throw new ArgumentNullException(nameof(text)); | |
} | |
if (width < 1) | |
{ | |
throw new ArgumentOutOfRangeException(nameof(width)); | |
} | |
var pos = 0; | |
var eol = text.Length; | |
var lines = new List<string>(); | |
do | |
{ | |
var len = eol - pos; | |
if (len > width) | |
{ | |
len = BreakLine(text, pos, width); | |
} | |
var line = text.Substring(pos, len); | |
lines.Add(line); | |
// Trim whitespace following break | |
pos += len; | |
while (pos < eol && | |
char.IsWhiteSpace(text[pos])) | |
{ | |
pos++; | |
} | |
} while (eol > pos); | |
return lines; | |
} | |
public static string ToPascalCase(this string source) => FormatName(source, true); | |
public static string ToCamelCase(this string source) => FormatName(source, false); | |
private static string FormatName(string source, bool isPascalCase) | |
{ | |
if (source == null) | |
{ | |
throw new ArgumentNullException(nameof(source)); | |
} | |
if (source.Length == 0) | |
{ | |
return source; | |
} | |
var buffer = source.ToCharArray(); | |
var first = false; | |
var needle = 0; | |
for (var idx = 0; idx < buffer.Length; idx++) | |
{ | |
var c = buffer[idx]; | |
if (char.IsLetterOrDigit(c)) | |
{ | |
if (first || needle == 0) | |
{ | |
buffer[needle] = first || isPascalCase && needle == 0 | |
? char.ToUpperInvariant(c) | |
: char.ToLowerInvariant(c); | |
} | |
else | |
{ | |
if (isPascalCase) | |
{ | |
buffer[needle] = char.IsUpper(c) | |
? c | |
: char.ToLowerInvariant(c); | |
} | |
else | |
{ | |
buffer[needle] = c; | |
} | |
} | |
first = false; | |
needle += 1; | |
} | |
else if (char.IsWhiteSpace(c) || | |
char.IsPunctuation(c)) | |
{ | |
first = true; | |
} | |
else | |
{ | |
buffer[needle] = c; | |
needle += 1; | |
} | |
} | |
return new string(buffer, 0, needle); | |
} | |
//lifted from System.CommandLine | |
public static string ToKebabCase(this string value) | |
{ | |
if (string.IsNullOrEmpty(value)) | |
{ | |
return value; | |
} | |
var kebab = new StringBuilder(); | |
var i = 0; | |
var addDash = false; | |
// handles beginning of string, breaks on first letter or digit. addDash might be better named "canAddDash" | |
for (; i < value.Length; i++) | |
{ | |
var ch = value[i]; | |
if (!char.IsLetterOrDigit(ch)) | |
{ | |
continue; | |
} | |
addDash = !char.IsUpper(ch); | |
kebab.Append(char.ToLowerInvariant(ch)); | |
i++; | |
break; | |
} | |
// reusing i, start at the same place | |
for (; i < value.Length; i++) | |
{ | |
var ch = value[i]; | |
if (char.IsUpper(ch)) | |
{ | |
if (addDash) | |
{ | |
addDash = false; | |
kebab.Append('-'); | |
} | |
kebab.Append(char.ToLowerInvariant(ch)); | |
continue; | |
} | |
if (char.IsLetterOrDigit(ch)) | |
{ | |
addDash = true; | |
kebab.Append(ch); | |
continue; | |
} | |
//this coverts all non letter/digits to dash - specifically periods and underscores. Is this needed? | |
addDash = false; | |
kebab.Append('-'); | |
} | |
return kebab.ToString(); | |
} | |
private static readonly string[] EmptyStrings = new string[0]; | |
public static string[] SplitOnFirst(this string strVal, char needle) | |
{ | |
if (strVal == null) | |
{ | |
return EmptyStrings; | |
} | |
var pos = strVal.IndexOf(needle); | |
return pos == -1 | |
? new[] {strVal} | |
: new[] {strVal.Substring(0, pos), strVal.Substring(pos + 1)}; | |
} | |
public static string[] SplitOnFirst(this string strVal, string needle) | |
{ | |
if (strVal == null) | |
{ | |
return EmptyStrings; | |
} | |
var pos = strVal.IndexOf(needle, StringComparison.Ordinal); | |
return pos == -1 | |
? new[] {strVal} | |
: new[] {strVal.Substring(0, pos), strVal.Substring(pos + 1)}; | |
} | |
public static (string FirstPart, string LastPart) SplitFirst(this string value, char needle) | |
{ | |
var parts = SplitOnFirst(value, needle); | |
return parts.Length == 1 | |
? (parts[0], null) | |
: (parts[0], parts[1]); | |
} | |
public static (string FirstPart, string LastPart) SplitFirst(this string value, string needle) | |
{ | |
var parts = SplitOnFirst(value, needle); | |
return parts.Length == 1 | |
? (parts[0], null) | |
: (parts[0], parts[1]); | |
} | |
public static (string FirstPart, string LastPart) SplitLast(this string value, char needle) | |
{ | |
var parts = value.SplitOnLast(needle); | |
return parts.Length == 1 | |
? (parts[0], null) | |
: (parts[0], parts[1]); | |
} | |
public static (string FirstPart, string LastPart) SplitLast(this string value, string needle) | |
{ | |
var parts = value.SplitOnLast(needle); | |
return parts.Length == 1 | |
? (parts[0], null) | |
: (parts[0], parts[1]); | |
} | |
public static string[] SplitOnLast(this string strVal, char needle) | |
{ | |
if (strVal == null) | |
{ | |
return new string[0]; | |
} | |
var pos = strVal.LastIndexOf(needle); | |
return pos == -1 | |
? new[] {strVal} | |
: new[] {strVal.Substring(0, pos), strVal.Substring(pos + 1)}; | |
} | |
public static string[] SplitOnLast(this string strVal, string needle) | |
{ | |
if (strVal == null) | |
{ | |
return EmptyStrings; | |
} | |
var pos = strVal.LastIndexOf(needle, StringComparison.Ordinal); | |
return pos == -1 | |
? new[] {strVal} | |
: new[] {strVal.Substring(0, pos), strVal.Substring(pos + 1)}; | |
} | |
public static string[] SplitWithoutEmptyEntries(this string value, params char[] needles) | |
{ | |
var parts = value?.Split(needles, StringSplitOptions.RemoveEmptyEntries) ?? EmptyStrings; | |
return parts; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment