Skip to content

Instantly share code, notes, and snippets.

@chrisfcarroll
Last active February 3, 2024 13:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisfcarroll/2911ac14ccdf028f6cc2ccb2607f82b3 to your computer and use it in GitHub Desktop.
Save chrisfcarroll/2911ac14ccdf028f6cc2ccb2607f82b3 to your computer and use it in GitHub Desktop.
C# string.MaskStart(), .MaskEnd(), string.Chop() string.ToWikiWords(), PascalCaseToWords() string.WithWhiteSpaceRemoved() and similar
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.RegularExpressions;
public static class StringExtensions
{
/// <summary>Trim one occurrence of <paramref name="terminator"/> string from
/// the end of <paramref name="str"/>, if there is one.
/// </summary>
/// <param name="str"></param>
/// <param name="terminator"></param>
/// <returns>
/// <c>str.Substring(0, str.Length - terminator.Length)</c> if <paramref name="str"/>
/// ends with <paramref name="terminator"/>, or else returns <paramref name="str"/> if not.
/// </returns>
[return: NotNullIfNotNull("str")]public static string? TrimEnd(this string? str, string terminator)
{
if (terminator is null || str is null
|| str.Length == 0 || terminator.Length == 0
|| terminator.Length > str.Length) return str;
if (str.EndsWith(terminator)) return str.Substring(0, str.Length - terminator.Length);
return str;
}
/// <summary>Trim one occurrence of <paramref name="terminator"/> string from
/// the start of <paramref name="str"/>, if there is one.
/// </summary>
/// <param name="str"></param>
/// <param name="terminator"></param>
/// <returns>
/// <c>str.Substring(terminator.Length, str.Length - terminator.Length)</c>
/// if <paramref name="str"/> starts with <paramref name="terminator"/>,
/// or else returns <paramref name="str"/> if not.
/// </returns>
[return: NotNullIfNotNull("str")]public static string? TrimStart(this string str, string terminator)
{
if (terminator is null || str is null
|| str.Length == 0 || terminator.Length == 0
|| terminator.Length > str.Length) return str;
if (str.StartsWith(terminator))
return str.Substring(terminator.Length, str.Length - terminator.Length);
return str;
}
[return: NotNullIfNotNull("str")]public static string? Trim(this string str, string terminator)
=> str.TrimEnd(terminator).TrimStart(terminator);
/// <summary>Abbreviation for
/// <c>string.Join(delim, values.Where(v => v is not null));</c></summary>
/// <returns>The joined string</returns>
public static string JoinDropNulls<T>(this IEnumerable<T> values, string delim)
=> string.Join(delim, values.Where(v => v is not null));
/// <summary>Abbreviation for
/// <c>string.Join(delim, values.Where(v => v is not null));</c></summary>
/// <returns>The joined string</returns>
public static string JoinDropNulls<T>(this IEnumerable<T> values, char delim = ',')
=> string.Join(delim, values.Where(v => v is not null));
/// <summary>Chop the string to maximum length of <paramref name="maxlength"/></summary>
/// <param name="str"></param>
/// <param name="maxlength"></param>
/// <returns>the chopped string. If the string is shorter than <paramref name="maxlength"/>
/// then the whole string is returned.</returns>
[return: NotNullIfNotNull("str")]public static string? Chop(this string? str, int maxlength)
{
if (str is null) return null;
if (str.Length <= maxlength) return str;
return str[..maxlength];
}
/// <summary>Replaces newline characters — <c>\n</c> or <see cref="Environment.NewLine"/> —
/// with <c>&gt;br/></c></summary>
/// <param name="str"></param>
/// <returns>the altered string</returns>
public static string WithHtmlLineBreaks(this string str)
=> str.Replace("\n", "<br/>").Replace(Environment.NewLine,"<br/>");
/// <summary>Abbreviation for <c>str.LastIndexOf(sought, StringComparison.Ordinal)</c></summary>
public static int LastIndexOfOrdinal(this string str, string sought)
=> str.LastIndexOf(sought, StringComparison.Ordinal);
/// <summary>Abbreviation for <see cref="string.IsNullOrEmpty"/></summary>
public static bool IsNullOrEmpty(this string str) => string.IsNullOrEmpty(str);
static readonly Regex RegexlowerUpper = new Regex(@"(\p{Ll}|\d)(\p{Lu})");
static readonly Regex RegexWord_Word = new Regex(@"(\S)_(\S)");
static readonly Regex Regex_Word = new Regex(@"_(\S)");
static readonly Regex RegexWord_ = new Regex(@"(\S)_");
/// <summary>
/// Convert PascalCase string to words, e.g. :
/// <list type="bullet">
/// <item>PascalCase->"Pascal Case"</item>
/// <item>Pascal1Case->"Pascal1 Case"</item>
/// <item>PascalCaseB->"Pascal Case B"</item>
/// </list>
/// </summary>
/// <param name="wikiWordString"></param>
/// <returns>The transformed string</returns>
[return:NotNullIfNotNull("wikiWordString")]public static string? PascalCaseToWords(this string? wikiWordString)
=> wikiWordString == null ? null :RegexlowerUpper.Replace(wikiWordString, "$1 $2");
/// <summary>Convert Snake_Cased_Strings to Words - with - Hyphens
/// <list type="bullet">
/// <item>Snake_Cased_Strings->"Snake - Cased - Strings"</item>
/// <item>Snake _ Cased->"Snake - Cased"</item>
/// <item>Snake_Cased_ ->"Snake - Cased -"</item>
/// </list>
/// </summary>
/// <param name="snakeCasedString"></param>
/// <returns>The transformed string</returns>
[return:NotNullIfNotNull("snakeCasedString")]public static string? UnderscoreToHyphenedWords(this string? snakeCasedString)
=> snakeCasedString?.RegexReplace(RegexWord_Word, "$1 - $2")
.RegexReplace(Regex_Word, "- $1")
.RegexReplace(RegexWord_, "$1 -")
.Replace("_","-");
/// <summary>
/// Combines both <see cref="PascalCaseToWords"/> and <see cref="UnderscoreToHyphenedWords"/>
/// <list type="bullet">
/// <item>PascalCased_Snake->"Pascal Cased - Snake"</item>
/// <item>PascalCased _ Snake->"Pascal Cased - Snake"</item>
/// <item>PascalCased_->"Pascal Cased -"</item>
/// </list>
/// </summary>
/// <param name="str"></param>
/// <returns>the transformed string</returns>
public static string PascalSnakeToHyphenedWords(this string str)
=> str.UnderscoreToHyphenedWords().PascalCaseToWords();
/// <summary>
/// Combines both <see cref="PascalCaseToWords"/> and <see cref="UnderscoreToHyphenedWords"/>
/// to an enum's ToString() value
/// <list type="bullet">
/// <item>PascalCased_Snake->"Pascal Cased - Snake"</item>
/// <item>PascalCased _ Snake->"Pascal Cased - Snake"</item>
/// <item>PascalCased_->"Pascal Cased -"</item>
/// </list>
/// </summary>
/// <param name="value">an enum value of type <typeparamref name="T"/></param>
/// <returns>the transformed string</returns>
public static string PascalSnakeToHyphenedWords<T>(this T value) where T : struct,Enum
=> value.ToString().UnderscoreToHyphenedWords().PascalCaseToWords();
/// <summary>Abbreviation for <c>regex.Replace(str, replace)</c></summary>
public static string RegexReplace(this string str, Regex regex, string replace)
=> regex.Replace(str, replace);
/// <summary>Removes spaces, newlines (<c>\n</c>)and tabs (<c>\t</c>) from <paramref name="str"/>
/// and returns the results</summary>
/// <param name="str"></param>
/// <returns></returns>
[return: NotNullIfNotNull("str")]public static string? WithWhiteSpaceRemoved(this string str)
{
return str?.Replace(" ", "").Replace("\n", "").Replace("\r", "").Replace("\t", "");
}
/// <summary>Abbreviation for <c>Regex.IsMatch(@this, pattern, options)</c></summary>
/// <param name="str">The input string</param>
/// <param name="pattern">the regex pattern to match against</param>
/// <param name="options">the <see cref="RegexOptions"/> to use</param>
/// <returns><c>true</c> if <paramref name="str"/> matches the pattern with the options</returns>
public static bool Matches(this string str, string pattern, RegexOptions options = RegexOptions.None)
{
return Regex.IsMatch(str, pattern, options);
}
/// <summary>Masks – that is, overwrites with the mask character – the beginning of a string.
/// </summary>
/// <example>
/// <c>"hello there".MaskToFirst('l')</c> => <c>"***lo there"</c>
/// </example>
/// <param name="str"></param>
/// <param name="marker">The phrase to mask as far as.</param>
/// <param name="maskChar"></param>
/// <param name="inclusive">If this is 0, then the marker phrase itself is not masked.
/// If it is 1, then the first character of marker is masked.
/// Any other value (positive or negative) is an offset from the marker for how far to mask
/// from the first character of marker.</param>
/// <seealso cref="MaskEnd"/>
/// <seealso cref="MaskStart"/>
/// <returns>The masked string.</returns>
public static string MaskToFirst(this string str, string marker, char maskChar = '*', int inclusive=1)
=> MaskStart(str, str.LastIndexOfOrdinal(marker)+inclusive, maskChar);
/// <summary>Masks – that is, overwrites with the mask character – the beginning of a string.
/// </summary>
/// <example>
/// <c>"hello there".MaskToFirst('l')</c> => <c>"***lo there"</c>
/// </example>
/// <param name="str"></param>
/// <param name="marker">The character to mask as far as.</param>
/// <param name="maskChar"></param>
/// <param name="offset">If this is 0, then the marker phrase itself is masked.
/// If it is 1, then the first character of the marker phrase is not masked.
/// Any other value (positive or negative) is an offset from the first character of marker
/// for how far to mask.</param>
/// <seealso cref="MaskEnd"/>
/// <seealso cref="MaskStart"/>
/// <returns>The masked string.</returns>
public static string MaskFromLast(this string str, string marker, char maskChar = '*', int offset=0)
{
var index = str.LastIndexOfOrdinal(marker);
return index == -1
? MaskEnd(str, offset, maskChar)
: MaskEnd(str, str.Length-index + offset, maskChar);
}
/// <summary>Masks – that is, overwrites with the mask character – the beginning of a string.
/// </summary>
/// <example>
/// <c>"hello there".MaskStart(3)</c> => <c>"***lo there"</c>
/// <c>"hello there".MaskStart(-3)</c> => <c>"********ere"</c>
/// </example>
/// <param name="str"></param>
/// <param name="masked">How many characters to mask. If masked is negative it is interpreted
/// as “How many characters to leave unmasked”</param>
/// <param name="maskChar"></param>
/// <seealso cref="MaskEnd"/>
/// <returns>The masked string.
/// If <paramref name="str"/> is shorter than <paramref name="masked"/> then only a
/// string of <paramref name="maskChar"/> of length <paramref name="masked"/> is returned
/// If <paramref name="masked"/> is longer than <c>me.Length</c> then only a
/// string of <paramref name="maskChar"/> of length <c>me.Length</c> is returned.
/// If <paramref name="masked"/>==0 then the original string is returned.
/// </returns>
public static string MaskStart(this string str, int masked=4, char maskChar = '*')
{
if (string.IsNullOrEmpty(str)) return str;
if (masked==0) return str;
var length = str.Length;
if (masked >= length) return new string(maskChar,length);
int unmasked;
if (masked < 0)
{
unmasked = -masked;
if (unmasked > length) unmasked = length;
masked = length - unmasked;
}
else
{
unmasked = length - masked;
}
return str.Substring(masked, unmasked).PadLeft(str.Length, maskChar);
}
/// <summary>Masks – that is, overwrites with the mask character – the end of a string.
/// </summary>
/// <example>
/// <c>"hello there".MaskEnd(3)</c> => <c>"Hello th***"</c>
/// <c>"hello there".MaskEnd(-3)</c> => <c>"Hel********"</c>
/// </example>
/// <param name="str"></param>
/// <param name="masked">How many characters to mask. If masked is negative it is interpreted
/// as “How many characters to leave unmasked”</param>
/// <param name="maskChar"></param>
/// <seealso cref="MaskStart"/>
/// <returns>The masked string.
/// If <paramref name="str"/> is shorter than <paramref name="masked"/> then only a
/// string of <paramref name="maskChar"/> of length <paramref name="masked"/> is returned
/// If <paramref name="masked"/> is longer than <c>me.Length</c> then only a
/// string of <paramref name="maskChar"/> of length <c>me.Length</c> is returned.
/// If <paramref name="masked"/>==0 then the original string is returned.
/// </returns>
public static string MaskEnd(this string str, int masked=4, char maskChar = '*')
{
if (string.IsNullOrEmpty(str)) return str;
if (masked==0) return str;
var length = str.Length;
if (masked >= length) return new string(maskChar,length);
int unmasked;
if (masked < 0)
{
unmasked = -masked;
if (unmasked > length) unmasked = length;
}
else
{
unmasked = length - masked;
}
return str.Substring(0, unmasked).PadRight(str.Length, maskChar);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment