diff --git a/FormatWith/Internal/FormatHelpers.cs b/FormatWith/Internal/FormatHelpers.cs index ed55fb6..2d8e72d 100644 --- a/FormatWith/Internal/FormatHelpers.cs +++ b/FormatWith/Internal/FormatHelpers.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -43,29 +43,22 @@ public static string ProcessTokens( { // token is a parameter token // perform parameter logic now. - var tokenKey = thisToken.Value; - string format = null; - var separatorIdx = tokenKey.IndexOf(":", StringComparison.Ordinal); - if (separatorIdx > -1) - { - tokenKey = thisToken.Value.Substring(0, separatorIdx); - format = thisToken.Value.Substring(separatorIdx + 1); - } + TokenInformation tokenInfo = new TokenInformation(thisToken.Value); // append the replacement for this parameter - ReplacementResult replacementResult = handler(tokenKey, format); - + ReplacementResult replacementResult = handler(tokenInfo.TokenKey, tokenInfo.Format); + if (replacementResult.Success) { // the key exists, add the replacement value // this does nothing if replacement value is null - if (string.IsNullOrWhiteSpace(format)) + if (string.IsNullOrWhiteSpace(tokenInfo.FormatString)) { resultBuilder.Append(replacementResult.Value); } else { - resultBuilder.AppendFormat("{0:" + format + "}", replacementResult.Value); + resultBuilder.AppendFormat("{0" + tokenInfo.FormatString + "}", replacementResult.Value); } } else @@ -133,26 +126,19 @@ public static FormattableString ProcessTokensIntoFormattableString( { // token is a parameter token // perform parameter logic now. - var tokenKey = thisToken.Value; - string format = null; - var separatorIdx = tokenKey.IndexOf(":", StringComparison.Ordinal); - if (separatorIdx > -1) - { - tokenKey = thisToken.Value.Substring(0, separatorIdx); - format = thisToken.Value.Substring(separatorIdx + 1); - } + TokenInformation tokenInfo = new TokenInformation(thisToken.Value); // append the replacement for this parameter - ReplacementResult replacementResult = handler(tokenKey); + ReplacementResult replacementResult = handler(tokenInfo.TokenKey); string IndexAndFormat() { - if (string.IsNullOrWhiteSpace(format)) + if (string.IsNullOrWhiteSpace(tokenInfo.FormatString)) { return "{" + placeholderIndex + "}"; } - return "{" + placeholderIndex + ":" + format + "}"; + return "{" + placeholderIndex + tokenInfo.FormatString + "}"; } // append the replacement for this parameter diff --git a/FormatWith/TokenInformation.cs b/FormatWith/TokenInformation.cs new file mode 100644 index 0000000..b02a539 --- /dev/null +++ b/FormatWith/TokenInformation.cs @@ -0,0 +1,192 @@ +namespace FormatWith +{ + + /// + /// Encapsulates the token-parsing logic and values, ensuring consistent behavior and allowing them to be passed to handlers more concisely. + /// + public class TokenInformation + { + + #region Static & Const + + ///// + ///// A regular expression to parse the token + ///// + //private static readonly System.Text.RegularExpressions.Regex _parseRegEx = new System.Text.RegularExpressions.Regex(@"(?([^,;:\s]+))(,(?-?\d+))?(:(?.*))?", System.Text.RegularExpressions.RegexOptions.Compiled); + + #endregion + + #region Properties + + /// + /// The raw token parsed. + /// + public string RawToken { get; private set; } + + /// + /// The token key. + /// + public string TokenKey { get; private set; } + + /// + /// The alignment indicator + /// + public string Alignment { get; private set; } + + /// + /// The base format string. (no alignment) + /// + public string Format { get; private set; } + + private string _formatString = null; + /// + /// The full composite formatting string. [,align][:format] + /// + public string FormatString + { + get + { + if (null == this._formatString) + { + this._formatString = string.Concat((!string.IsNullOrEmpty(this.Alignment) ? $",{this.Alignment}" : string.Empty), (!string.IsNullOrEmpty(this.Format) ? $":{this.Format}" : string.Empty)); + } + return (this._formatString); + } + } + + #endregion + + #region Constructors + + /// + /// Parses token information from a raw token string value. + /// + /// The raw token string. + /// IndexOf/Substring version of parser based on existing code + public TokenInformation(string rawToken) + { + this.RawToken = rawToken; + this.Format = null; + this.Alignment = null; + + // Parse out the format + var separatorIdx = rawToken.IndexOf(":", System.StringComparison.Ordinal); + if (separatorIdx > -1) + { + this.Format = rawToken.Substring(separatorIdx + 1); + rawToken = rawToken.Substring(0, separatorIdx); + } + + // Parse out alignment + var alignmentIdx = rawToken.LastIndexOf(",", System.StringComparison.Ordinal); + if (alignmentIdx > -1) + { + this.Alignment = rawToken.Substring(alignmentIdx + 1); + rawToken = rawToken.Substring(0, alignmentIdx); + } + + // Grab the token key + this.TokenKey = rawToken; + } + + ///// + ///// Parses token information from a raw token string value. + ///// + ///// The raw token string. + ///// RegEx version of parser, for performance comparison, and in case someone else knows how to optimize it more + //public TokenInformation(string rawToken, bool ignoreThisParameterItIsHereForPerformanceComparisons) + //{ + // this.RawToken = rawToken; + // this.Alignment = null; + // this.Format = null; + // + // System.Text.RegularExpressions.Match results = _parseRegEx.Match(rawToken); + // if(results.Success) + // { + // this.TokenKey = results.Groups["token"].Value; + // this.Alignment = results.Groups["alignment"].Value; + // this.Format = results.Groups["format"].Value; + // } + //} + + #endregion + + #region Methods + + private static string BuildString(string tokenKey, string alignment, string format) + { + string rawToken = string.Concat(tokenKey, (!string.IsNullOrEmpty(alignment) ? $",{alignment}" : string.Empty), (!string.IsNullOrEmpty(format) ? $":{format}" : string.Empty)); + return (rawToken); + } + + /// + /// Builds a raw token string given the desired tokenKey and format + /// + /// The key value for the token. + /// The left- or right-padding indicator desired. + /// The format string. + /// + public static string BuildString(string tokenKey, int? alignment, string format) + { + return (TokenInformation.BuildString(tokenKey, alignment.HasValue ? $"{alignment}" : string.Empty, format)); + } + + /// + /// Builds a raw token string given the desired tokenKey and format + /// + /// The key value for the token. + /// The format string. + /// + public static string BuildString(string tokenKey, string format) + { + return (TokenInformation.BuildString(tokenKey, (int?)null, format)); + } + + /// + /// Builds a raw token string given the desired tokenKey and format + /// + /// The key value for the token. + /// The left- or right-padding indicator desired. + /// + public static string BuildString(string tokenKey, int? alignment) + { + return (TokenInformation.BuildString(tokenKey, alignment, null)); + } + + /// + /// Builds a raw token string given the desired tokenKey and format + /// + /// The key value for the token. + /// + public static string BuildString(string tokenKey) + { + return(TokenInformation.BuildString(tokenKey, (int?)null, null)); + } + + /// + /// Builds a TokenInfo object given the desired tokenKey and format + /// + /// The key value for the token. + /// The left- or right-padding indicator desired. + /// The format string. + /// + public static TokenInformation Build(string tokenKey, int? alignment, string format) + { + return (new TokenInformation(TokenInformation.BuildString(tokenKey, alignment, format))); + } + + /// + /// Builds a TokenInfo object given the desired tokenKey and format + /// + /// The key value for the token. + /// The format string. + /// + public static TokenInformation Build(string tokenKey, string format) + { + return (TokenInformation.Build(tokenKey, null, format)); + } + + #endregion + + } // end class +} // end namespace diff --git a/FormatWithTests/TestStrings.cs b/FormatWithTests/TestStrings.cs index a16ce7f..ce76ce6 100644 --- a/FormatWithTests/TestStrings.cs +++ b/FormatWithTests/TestStrings.cs @@ -28,7 +28,7 @@ public static class TestStrings public static readonly string TestFormat6 = "abc{Replacement1:upper}"; public static readonly string TestFormat6Composite = "abc{0:upper}"; public static readonly string TestFormat6Solution = $"abc{Replacement1.ToUpper()}"; - + public static readonly string TestFormat7 = "Today is {Today:YYYYMMDD HH:mm}"; public static readonly string TestFormat7Composite = "Today is {0:YYYYMMDD HH:mm}"; public static readonly DateTime TestFormat7Date = new DateTime(2018, 10, 30, 17, 25, 0); diff --git a/FormatWithTests/TokenInformationTests.cs b/FormatWithTests/TokenInformationTests.cs new file mode 100644 index 0000000..9f8aa96 --- /dev/null +++ b/FormatWithTests/TokenInformationTests.cs @@ -0,0 +1,162 @@ +using Xunit; +using FormatWith; +using Xunit.Sdk; + +namespace FormatWithTests +{ + public class TokenInformationTests + { + + #region Test Values + + private static string testTokenKey = "token"; + private static int? testLeftPad = 15; + private static int? testRightPad = -15; + private static string testFormat = "yyyy-MM-dd"; + private static System.DateTime testDate = new System.DateTime(2024, 8, 29); + + public class Parse_TestData : TheoryData + { + public Parse_TestData() + { + } + }; + + #endregion + + #region BuildString + + [Fact] + public void BuildString_TokenOnly() + { + string expected = "token"; + + string result = TokenInformation.BuildString(testTokenKey); + Assert.Equal(result, expected); + } + + [Fact] + public void BuildString_TokenAndLeftPad() + { + string expected = "token,15"; + + string result = TokenInformation.BuildString(testTokenKey, testLeftPad); + Assert.Equal(result, expected); + } + + [Fact] + public void BuildString_TokenAndRightPad() + { + string expected = "token,-15"; + + string result = TokenInformation.BuildString(testTokenKey, testRightPad); + Assert.Equal(result, expected); + } + + [Fact] + public void BuildString_TokenAndFormat() + { + string expected = "token:yyyy-MM-dd"; + + string result = TokenInformation.BuildString(testTokenKey, testFormat); + Assert.Equal(result, expected); + } + + [Fact] + public void BuildString_TokenLeftPadAndFormat() + { + string expected = "token,15:yyyy-MM-dd"; + + string result = TokenInformation.BuildString(testTokenKey, testLeftPad, testFormat); + Assert.Equal(result, expected); + } + + [Fact] + public void BuildString_TokenAndRightPadAndFormat() + { + string expected = "token,-15:yyyy-MM-dd"; + + string result = TokenInformation.BuildString(testTokenKey, testRightPad, testFormat); + Assert.Equal(result, expected); + } + + #endregion + + #region Parse + + [Theory] + [InlineData("token", "token", null, null, "")] + [InlineData("token,15", "token", "15", null, ",15")] + [InlineData("token,-15", "token", "-15", null, ",-15")] + [InlineData("token:yyyy-MM-dd", "token", null, "yyyy-MM-dd", ":yyyy-MM-dd")] + [InlineData("token,15:yyyy-MM-dd", "token", "15", "yyyy-MM-dd", ",15:yyyy-MM-dd")] + [InlineData("token,-15:yyyy-MM-dd", "token", "-15", "yyyy-MM-dd", ",-15:yyyy-MM-dd")] + public void Parse(string rawToken, string expectedTokenKey, string expectedAlignment, string expectedFormat, string expectedFormatString) + { + TokenInformation result = new TokenInformation(rawToken); + + Assert.Equal(result.RawToken, rawToken); + Assert.Equal(result.TokenKey, expectedTokenKey); + if (null == expectedAlignment) + { + Assert.Null(result.Alignment); + } + else + { + Assert.Equal(result.Alignment, expectedAlignment); + } + if (null == expectedFormat) + { + Assert.Null(result.Format); + } + else + { + Assert.Equal(result.Format, expectedFormat); + } + Assert.Equal(result.FormatString, expectedFormatString); + } + + #endregion + + #region FormatWith + + [Fact] + public void FormatWith_DateTest_FormatOnly() + { + System.Collections.Generic.Dictionary replacmentValues = new System.Collections.Generic.Dictionary(); + replacmentValues.Add(testTokenKey, testDate); + + string expectedResult = "'2024-08-29'"; + string result = "'{token:yyyy-MM-dd}'".FormatWith(replacmentValues); + + Assert.Equal(result, expectedResult); + } + + [Fact] + public void FormatWith_DateTest_FormatAndLeftPad() + { + System.Collections.Generic.Dictionary replacmentValues = new System.Collections.Generic.Dictionary(); + replacmentValues.Add(testTokenKey, testDate); + + string expectedResult = "' 2024-08-29'"; + string result = "'{token,15:yyyy-MM-dd}'".FormatWith(replacmentValues); + + Assert.Equal(result, expectedResult); + } + + [Fact] + public void FormatWith_DateTest_FormatAndRightPad() + { + System.Collections.Generic.Dictionary replacmentValues = new System.Collections.Generic.Dictionary(); + replacmentValues.Add(testTokenKey, testDate); + + string expectedResult = "'2024-08-29 '"; + string result = "'{token,-15:yyyy-MM-dd}'".FormatWith(replacmentValues); + + Assert.Equal(result, expectedResult); + } + + #endregion + + } // end class +} // end namespace