Skip to content

Commit

Permalink
Merge pull request #97 from HicServices/feature/nullable
Browse files Browse the repository at this point in the history
Add nullable, fix uninitialised culture setting in DateTimeTypeDecider
  • Loading branch information
JFriel authored Feb 1, 2024
2 parents 637ee67 + 8e1151a commit 2416e54
Show file tree
Hide file tree
Showing 19 changed files with 48 additions and 34 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.2.2] - 2024-02-01

- Bugfix in culture handling in DateTimeTypeDecider
- Add nullability annotations

## [1.2.1] - 2024-01-29

- Version bump to resolve symbol package issue on Nuget.org
Expand Down
5 changes: 5 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
6 changes: 3 additions & 3 deletions SharedAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: AssemblyVersion("1.2.1")]
[assembly: AssemblyFileVersion("1.2.1")]
[assembly: AssemblyInformationalVersion("1.2.1")]
[assembly: AssemblyVersion("1.2.2")]
[assembly: AssemblyFileVersion("1.2.2")]
[assembly: AssemblyInformationalVersion("1.2.2")]
2 changes: 2 additions & 0 deletions Tests/GuesserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,14 @@ public void Test_OneString_IsType(string guessFor, Type expectedGuess, string cu
: guessFor, Is.EqualTo(expectedParseValue));
}

#pragma warning disable CA1861
[TestCase("en-us",new []{"5","10"},
typeof(int),2,2,0)]
[TestCase("en-us", new []{"5","10.1"},
typeof(decimal),4,2,1)]
[TestCase("en-us", new []{"5.1","5.000000000"},
typeof(decimal),11,1,1)]
#pragma warning restore CA1861
public void Test_ManyString_IsType(string culture, string[] guessFor, Type expectedGuess,int expectedStringLength, int expectedBefore,int expectedAfter)
{
var cultureInfo = new CultureInfo(culture);
Expand Down
4 changes: 2 additions & 2 deletions Tests/PackageListIsCorrectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public sealed partial class PackageListIsCorrectTests
/// </summary>
/// <param name="rootPath"></param>
[TestCase]
public void TestPackagesDocumentCorrect(string rootPath=null)
public void TestPackagesDocumentCorrect(string? rootPath=null)
{
var root= FindRoot(rootPath);
var undocumented = new StringBuilder();
Expand Down Expand Up @@ -72,7 +72,7 @@ public void TestPackagesDocumentCorrect(string rootPath=null)
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private static DirectoryInfo FindRoot(string path = null)
private static DirectoryInfo FindRoot(string? path = null)
{
if (path != null)
{
Expand Down
3 changes: 2 additions & 1 deletion Tests/PerformanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public void Performance_Decimals()

var decider = new DecimalTypeDecider(new CultureInfo("en-GB"));

var req = new DatabaseTypeRequest(null);
// ReSharper disable once NullableWarningSuppressionIsUsed - this is just for benchmarking
var req = new DatabaseTypeRequest(null!);

var sw = new Stopwatch();

Expand Down
1 change: 1 addition & 0 deletions TypeGuesser.sln
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{EAE289E1-E8AF-4627-91A6-12E6D5F9F027}"
ProjectSection(SolutionItems) = preProject
CHANGELOG.md = CHANGELOG.md
Directory.Build.props = Directory.Build.props
Packages.md = Packages.md
README.md = README.md
SharedAssemblyInfo.cs = SharedAssemblyInfo.cs
Expand Down
4 changes: 2 additions & 2 deletions TypeGuesser/DatabaseTypeRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public int? Width
/// <param name="maxWidthForStrings"></param>
/// <param name="decimalPlacesBeforeAndAfter"></param>
public DatabaseTypeRequest(Type cSharpType, int? maxWidthForStrings = null,
DecimalSize decimalPlacesBeforeAndAfter = null)
DecimalSize? decimalPlacesBeforeAndAfter = null)
{
CSharpType = cSharpType;
Width = maxWidthForStrings;
Expand All @@ -80,7 +80,7 @@ private bool Equals(DatabaseTypeRequest other)
}

/// <inheritdoc/>
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
Expand Down
2 changes: 1 addition & 1 deletion TypeGuesser/Deciders/BoolTypeDecider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected override IDecideTypesForStrings CloneImpl(CultureInfo newCulture)
}

/// <inheritdoc/>
protected override bool IsAcceptableAsTypeImpl(string candidateString, IDataTypeSize size)
protected override bool IsAcceptableAsTypeImpl(string candidateString, IDataTypeSize? size)
{
// "Y" / "N" is boolean unless the settings say it can't
if (!Settings.CharCanBeBoolean && SingleCharacter.IsMatch(candidateString))
Expand Down
14 changes: 7 additions & 7 deletions TypeGuesser/Deciders/DateTimeTypeDecider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ public class DateTimeTypeDecider(CultureInfo cultureInfo) : DecideTypesForString
/// </summary>
public static readonly string[] TimeFormats;

private string[] _dateFormatToUse;
private CultureInfo _culture;
private string[] _dateFormatToUse =
cultureInfo.DateTimeFormat.ShortDatePattern.IndexOf('M') > cultureInfo.DateTimeFormat.ShortDatePattern.IndexOf('d')
? DateFormatsDM
: DateFormatsMD;
private CultureInfo _culture=cultureInfo;

/// <summary>
/// Setting this to false will prevent <see cref="GuessDateFormat(IEnumerable{string})"/> changing the <see cref="Culture"/> e.g. when
Expand All @@ -49,10 +52,7 @@ public class DateTimeTypeDecider(CultureInfo cultureInfo) : DecideTypesForString
public override CultureInfo Culture { get => _culture;
set
{
value ??= CultureInfo.CurrentCulture;

_dateFormatToUse = value.DateTimeFormat.ShortDatePattern.IndexOf('M') > value.DateTimeFormat.ShortDatePattern.IndexOf('d') ? DateFormatsDM : DateFormatsMD;

_culture = value;
} }

Expand Down Expand Up @@ -196,13 +196,13 @@ public void GuessDateFormat(IEnumerable<string> samples)
}

/// <inheritdoc />
public override bool IsAcceptableAsType(string candidateString, IDataTypeSize size)
public override bool IsAcceptableAsType(string candidateString, IDataTypeSize? size)
{
return IsExplicitDate(candidateString) || base.IsAcceptableAsType(candidateString, size);
}

/// <inheritdoc/>
protected override bool IsAcceptableAsTypeImpl(string candidateString, IDataTypeSize sizeRecord)
protected override bool IsAcceptableAsTypeImpl(string candidateString, IDataTypeSize? sizeRecord)
{
//if it's a float then it isn't a date is it! thanks C# for thinking 1.1 is the first of January
if (_decimalChecker.IsAcceptableAsType(candidateString, sizeRecord))
Expand Down
8 changes: 4 additions & 4 deletions TypeGuesser/Deciders/DecideTypesForStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected DecideTypesForStrings(CultureInfo culture, TypeCompatibilityGroup comp
}

/// <inheritdoc/>
public virtual bool IsAcceptableAsType(string candidateString,IDataTypeSize size)
public virtual bool IsAcceptableAsType(string candidateString,IDataTypeSize? size)
{
//we must preserve leading zeroes if it's not actually 0 -- if they have 010101 then we have to use string but if they have just 0 we can use decimal
return !IDecideTypesForStrings.ZeroPrefixedNumber.IsMatch(candidateString) && IsAcceptableAsTypeImpl(candidateString, size);
Expand All @@ -71,7 +71,7 @@ protected bool IsExplicitDate(string candidateString)
}

/// <inheritdoc/>
public object Parse(string value)
public object? Parse(string value)
{
if (string.IsNullOrWhiteSpace(value))
return null;
Expand Down Expand Up @@ -105,7 +105,7 @@ public IDecideTypesForStrings Clone()
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected virtual object ParseImpl(string value)
protected virtual object? ParseImpl(string value)
{
return value.To<T>(Culture);
}
Expand All @@ -116,7 +116,7 @@ protected virtual object ParseImpl(string value)
/// <param name="candidateString"></param>
/// <param name="size"></param>
/// <returns></returns>
protected virtual bool IsAcceptableAsTypeImpl(string candidateString,IDataTypeSize size)
protected virtual bool IsAcceptableAsTypeImpl(string candidateString,IDataTypeSize? size)
{
return candidateString.IsConvertibleTo<T>(Culture);
}
Expand Down
4 changes: 2 additions & 2 deletions TypeGuesser/Deciders/DecimalTypeDecider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected override IDecideTypesForStrings CloneImpl(CultureInfo culture)
}

/// <inheritdoc/>
protected override bool IsAcceptableAsTypeImpl(string candidateString,IDataTypeSize sizeRecord)
protected override bool IsAcceptableAsTypeImpl(string candidateString,IDataTypeSize? sizeRecord)
{
candidateString = TrimTrailingZeros(candidateString);

Expand All @@ -41,7 +41,7 @@ protected override bool IsAcceptableAsTypeImpl(string candidateString,IDataTypeS
return false;

var dec = (SqlDecimal) t;
sizeRecord.Size.IncreaseTo(dec.Precision - dec.Scale,dec.Scale);
sizeRecord?.Size.IncreaseTo(dec.Precision - dec.Scale,dec.Scale);

return true;
}
Expand Down
4 changes: 2 additions & 2 deletions TypeGuesser/Deciders/IDecideTypesForStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ public partial interface IDecideTypesForStrings
/// <param name="size">The current size estimate of floating point numbers (or null if not appropriate). This will be modified by the method
/// if appropriate to the data passed</param>
/// <returns>True if the <paramref name="candidateString"/> is a valid value for the <see cref="TypesSupported"/> by the decider</returns>
bool IsAcceptableAsType(string candidateString,IDataTypeSize size);
bool IsAcceptableAsType(string candidateString,IDataTypeSize? size);

/// <summary>
/// Converts the provided <paramref name="value"/> to an object of the Type modelled by this <see cref="IDecideTypesForStrings"/>.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
object Parse(string value);
object? Parse(string value);

/// <summary>
/// Returns a new instance of this class with the same <see cref="Culture"/> and <see cref="Settings"/> etc
Expand Down
4 changes: 2 additions & 2 deletions TypeGuesser/Deciders/IntTypeDecider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ protected override IDecideTypesForStrings CloneImpl(CultureInfo newCulture)
}

/// <inheritdoc/>
protected override bool IsAcceptableAsTypeImpl(string candidateString, IDataTypeSize sizeRecord)
protected override bool IsAcceptableAsTypeImpl(string candidateString, IDataTypeSize? sizeRecord)
{
if(IsExplicitDate(candidateString))
return false;

if (!candidateString.IsConvertibleTo(out int i, Culture))
return false;

sizeRecord.Size.IncreaseTo(i.ToString().Trim('-').Length,0);
sizeRecord?.Size.IncreaseTo(i.ToString().Trim('-').Length,0);
return true;
}
}
2 changes: 1 addition & 1 deletion TypeGuesser/Deciders/NeverGuessedTypeDecider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed class NeverGuessTheseTypeDecider(CultureInfo culture) : DecideType
protected override object ParseImpl(string value) => throw new NotSupportedException();

/// <inheritdoc/>
protected override bool IsAcceptableAsTypeImpl(string candidateString, IDataTypeSize sizeRecord) =>
protected override bool IsAcceptableAsTypeImpl(string candidateString, IDataTypeSize? sizeRecord) =>
//strings should never be interpreted as byte arrays
false;
}
2 changes: 1 addition & 1 deletion TypeGuesser/Deciders/TimeSpanTypeDecider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed class TimeSpanTypeDecider(CultureInfo culture) : DecideTypesForStr
protected override object ParseImpl(string value) => DateTime.Parse(value).TimeOfDay;

/// <inheritdoc/>
protected override bool IsAcceptableAsTypeImpl(string candidateString,IDataTypeSize sizeRecord)
protected override bool IsAcceptableAsTypeImpl(string candidateString,IDataTypeSize? sizeRecord)
{
try
{
Expand Down
2 changes: 1 addition & 1 deletion TypeGuesser/DecimalSize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private bool Equals(DecimalSize other)
}

/// <inheritdoc/>
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
Expand Down
2 changes: 1 addition & 1 deletion TypeGuesser/GuessSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class GuessSettings
/// <summary>
/// Optional, when set dates must be in one of these formats and any string in this format will be picked as a date.
/// </summary>
public string[] ExplicitDateFormats { get; set; }
public string[] ExplicitDateFormats { get; set; } = [];


/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions TypeGuesser/Guesser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public void AdjustToCompensateForValues(IEnumerable<object> collection)
/// </summary>
/// <exception cref="MixedTypingException">Thrown if you mix strings with hard Typed objects when supplying <paramref name="o"/></exception>
/// <param name="o"></param>
public void AdjustToCompensateForValue(object o)
public void AdjustToCompensateForValue(object? o)
{
while (true)
{
Expand All @@ -136,7 +136,7 @@ public void AdjustToCompensateForValue(object o)
var oToString = o.ToString();

//we might need to fallback on a string later on, in this case we should always record the maximum length of input seen before even if it is acceptable as int, double, dates etc
Guess.Width = Math.Max(Guess.Width ?? -1, GetStringLength(oToString));
Guess.Width = Math.Max(Guess.Width ?? -1, GetStringLength(oToString??string.Empty));

//if it's a string
if (o is string oAsString)
Expand Down Expand Up @@ -179,7 +179,7 @@ public void AdjustToCompensateForValue(object o)
}

//if we have a decider for this lets get it to tell us the decimal places (if any)
if (_typeDeciders.Dictionary.TryGetValue(o.GetType(),out var decider))
if (oToString!=null && _typeDeciders.Dictionary.TryGetValue(o.GetType(),out var decider))
decider.IsAcceptableAsType(oToString, Guess);
break;
}
Expand Down Expand Up @@ -265,7 +265,7 @@ private void ThrowIfNotSupported(Type currentEstimate)
/// <param name="val"></param>
/// <exception cref="NotSupportedException">If the current <see cref="Guess"/> does not have a parser defined</exception>
/// <returns></returns>
public object Parse(string val)
public object? Parse(string val)
{
if (Guess.CSharpType == typeof(string))
return val;
Expand Down

0 comments on commit 2416e54

Please sign in to comment.