Skip to content

Commit

Permalink
Recognize dates when generating steps (#1958)
Browse files Browse the repository at this point in the history
  • Loading branch information
304NotModified committed May 5, 2020
1 parent f47a218 commit cf2b7cb
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 17 deletions.
100 changes: 84 additions & 16 deletions TechTalk.SpecFlow/BindingSkeletons/StepTextAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Globalization;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using TechTalk.SpecFlow.Bindings;

namespace TechTalk.SpecFlow.BindingSkeletons
{
Expand Down Expand Up @@ -38,22 +38,26 @@ public AnalyzedStepText Analyze(string stepText, CultureInfo bindingCulture)
{
var result = new AnalyzedStepText();

var paramMatches = RecognizeQuotedTexts(stepText).Concat(RecognizeIntegers(stepText)).Concat(RecognizeDecimals(stepText, bindingCulture))
.OrderBy(m => m.Index).ThenByDescending(m => m.Length);
var paramMatches = RecognizeQuotedTexts(stepText)
.Concat(RecognizeDates(stepText))
.Concat(RecognizeIntegers(stepText))
.Concat(RecognizeDecimals(stepText, bindingCulture))
.OrderBy(m => m.Capture.Index)
.ThenByDescending(m => m.Capture.Length);

int textIndex = 0;
foreach (var paramMatch in paramMatches)
{
if (paramMatch.Index < textIndex)
if (paramMatch.Capture.Index < textIndex)
continue;

const string singleQuoteRegexPattern = "[^']*";
const string doubleQuoteRegexPattern = "[^\"\"]*";
const string defaultRegexPattern = ".*";

string regexPattern = defaultRegexPattern;
string value = paramMatch.Value;
int index = paramMatch.Index;
string value = paramMatch.Capture.Value;
int index = paramMatch.Capture.Index;

switch (value.Substring(0, 1))
{
Expand All @@ -70,45 +74,109 @@ public AnalyzedStepText Analyze(string stepText, CultureInfo bindingCulture)
}

result.TextParts.Add(stepText.Substring(textIndex, index - textIndex));
result.Parameters.Add(AnalyzeParameter(value, bindingCulture, result.Parameters.Count, regexPattern));
result.Parameters.Add(AnalyzeParameter(value, bindingCulture, result.Parameters.Count, regexPattern, paramMatch.ParameterType));
textIndex = index + value.Length;
}

result.TextParts.Add(stepText.Substring(textIndex));
return result;
}

private AnalyzedStepParameter AnalyzeParameter(string value, CultureInfo bindingCulture, int paramIndex, string regexPattern)
private AnalyzedStepParameter AnalyzeParameter(string value, CultureInfo bindingCulture, int paramIndex, string regexPattern, ParameterType parameterType)
{
string paramName = StepParameterNameGenerator.GenerateParameterName(value, paramIndex, usedParameterNames);

int intParamValue;
if (int.TryParse(value, NumberStyles.Integer, bindingCulture, out intParamValue))
if (parameterType == ParameterType.Int && int.TryParse(value, NumberStyles.Integer, bindingCulture, out intParamValue))
return new AnalyzedStepParameter("Int32", paramName, regexPattern);

decimal decimalParamValue;
if (decimal.TryParse(value, NumberStyles.Number, bindingCulture, out decimalParamValue))
if (parameterType == ParameterType.Decimal && decimal.TryParse(value, NumberStyles.Number, bindingCulture, out decimalParamValue))
return new AnalyzedStepParameter("Decimal", paramName, regexPattern);

DateTime dateParamValue;
if (parameterType == ParameterType.Date && DateTime.TryParse(value, bindingCulture, DateTimeStyles.AllowWhiteSpaces, out dateParamValue))
return new AnalyzedStepParameter("DateTime", paramName, regexPattern);

return new AnalyzedStepParameter("String", paramName, regexPattern);
}

private static readonly Regex quotesRe = new Regex(@"""+(?<param>.*?)""+|'+(?<param>.*?)'+|(?<param>\<.*?\>)");
private IEnumerable<Capture> RecognizeQuotedTexts(string stepText)
private IEnumerable<CaptureWithContext> RecognizeQuotedTexts(string stepText)
{
return quotesRe.Matches(stepText).Cast<Match>().Select(m => (Capture)m.Groups["param"]);
return quotesRe.Matches(stepText)
.Cast<Match>()
.Select(m => (Capture)m.Groups["param"])
.ToCaptureWithContext(ParameterType.Text);
}

private static readonly Regex intRe = new Regex(@"-?\d+");
private IEnumerable<Capture> RecognizeIntegers(string stepText)

private IEnumerable<CaptureWithContext> RecognizeIntegers(string stepText)
{
return intRe.Matches(stepText).Cast<Capture>();
return intRe.Matches(stepText).ToCaptureWithContext(ParameterType.Int);
}

private IEnumerable<Capture> RecognizeDecimals(string stepText, CultureInfo bindingCulture)
private IEnumerable<CaptureWithContext> RecognizeDecimals(string stepText, CultureInfo bindingCulture)
{
Regex decimalRe = new Regex(string.Format(@"-?\d+{0}\d+", bindingCulture.NumberFormat.NumberDecimalSeparator));
return decimalRe.Matches(stepText).Cast<Capture>();
return decimalRe.Matches(stepText).ToCaptureWithContext(ParameterType.Decimal);
}

private static readonly Regex dateRe = new Regex(string.Join("|", GetDateFormats()));

/// <summary>
/// note: space separator not supported to prevent clashes
/// </summary>
private static IEnumerable<string> GetDateFormats()
{
yield return GetDateFormat("/");
yield return GetDateFormat("-");
yield return GetDateFormat(".");
}

private static string GetDateFormat(string separator)
{
var separatorEscaped = Regex.Escape(separator);
return @"\d{1,4}" + separatorEscaped + @"\d{1,4}" + separatorEscaped + @"\d{1,4}";
}

private IEnumerable<CaptureWithContext> RecognizeDates(string stepText)
{
return dateRe.Matches(stepText).ToCaptureWithContext(ParameterType.Date);
}
}

internal static class MatchCollectionExtensions
{
public static IEnumerable<CaptureWithContext> ToCaptureWithContext(this MatchCollection collection, ParameterType parameterType)
{
return collection.Cast<Capture>().ToCaptureWithContext(parameterType);
}
public static IEnumerable<CaptureWithContext> ToCaptureWithContext(this IEnumerable<Capture> collection, ParameterType parameterType)
{
return collection.Select(c => new CaptureWithContext(c, parameterType));
}
}

internal class CaptureWithContext
{
public Capture Capture { get; }

public ParameterType ParameterType { get; }

public CaptureWithContext(Capture capture, ParameterType parameterType)
{
Capture = capture;
ParameterType = parameterType;
}
}

internal enum ParameterType
{
Text,
Int,
Decimal,
Date
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace TechTalk.SpecFlow.RuntimeTests.BindingSkeletons
{

public class StepTextAnalyzerTests
{
private readonly CultureInfo bindingCulture = new CultureInfo("en-US", false);
Expand Down Expand Up @@ -103,6 +103,26 @@ public void Should_recognize_integers()
result.Parameters[0].Type.Should().Be("Int32");
}

[Theory]
[InlineData("2030-12-23", "en-US")]
[InlineData("2030/12/23", "en-US")]
[InlineData("23-12-2030", "nl-NL")]
[InlineData("23.12.2030", "nl-BE")]
public void Should_recognize_dates(string dateString, string cultureCode)
{
var sut = new StepTextAnalyzer();

var culture = CultureInfo.GetCultureInfo(cultureCode);

var result = sut.Analyze("Zombie apocalypse is expected at " + dateString, culture);
result.Parameters.Count.Should().Be(1);
result.Parameters[0].Name.Should().Be("p0");
result.Parameters[0].Type.Should().Be("DateTime");
result.TextParts.Count.Should().Be(2);
result.TextParts[0].Should().Be("Zombie apocalypse is expected at ");
result.TextParts[1].Should().Be("");
}

[Fact]
public void Should_recognize_decimals()
{
Expand Down
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Fixes:
Features:
+ Created VerifyAllColumnsBound check for `table.CreateSet` and `table.CreateInstance`


Changes since 3.1.86

Fixes:
Expand Down

0 comments on commit cf2b7cb

Please sign in to comment.