diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88cc537..361bedc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,19 +2,26 @@ name: ci on: [push] +# Allow one run of this workflow per branch and cancel existing runs if triggered again +concurrency: + group: fruityfoundation-ci-${{ github.ref_name }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest strategy: matrix: - dotnet-version: ['6.0.x'] + dotnet-version: ['7.0.x', '6.0.x'] steps: - uses: actions/checkout@v2 - uses: actions/setup-dotnet@v1 with: - dotnet-version: "6.0.x" + dotnet-version: | + 6.0.x + 7.0.x - name: Install dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml old mode 100755 new mode 100644 index 8ba9769..a7f0444 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -21,7 +21,7 @@ jobs: fetch-depth: '0' # Load entire history - uses: actions/setup-dotnet@v3 with: - dotnet-version: '6.x' + dotnet-version: '7.x' - run: dotnet tool restore - name: Generate Version id: generate-version diff --git a/Base.Tests/Base.Tests.csproj b/Base.Tests/Base.Tests.csproj old mode 100755 new mode 100644 index d23458e..18a419f --- a/Base.Tests/Base.Tests.csproj +++ b/Base.Tests/Base.Tests.csproj @@ -1,10 +1,11 @@ - net6.0 enable false + + net6.0;net7.0 @@ -21,4 +22,5 @@ + diff --git a/Base.Tests/Extensions/EnumerableExtensionTests.cs b/Base.Tests/Extensions/EnumerableExtensionTests.cs deleted file mode 100644 index 1b0f0e7..0000000 --- a/Base.Tests/Extensions/EnumerableExtensionTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Linq; -using FruityFoundation.Base.Extensions; -using NUnit.Framework; - -namespace Base.Tests.Extensions; - -public class EnumerableExtensionTests -{ - [TestCase(new object[] { 0, 1, 2 }, true, new object[] { 85 }, ExpectedResult = new object[] { 0, 1, 2, 85 })] - [TestCase(new object[] { "hi" }, false, new object[] { "there" }, ExpectedResult = new object[] { "hi" })] - public object[] TestConditionalConcat(object[] input, bool isConditionValid, object[] second) => - input.ConditionalConcat(isConditionValid, second).ToArray(); - - [TestCase(new object[] { 0, 1, 2 }, true, 85, ExpectedResult = new object[0])] - [TestCase(new object[] { "hi", "there" }, false, "there", ExpectedResult = new object[] { "hi", "there" })] - public object[] TestConditionalWhere(object[] input, bool isConditionValid, object valueToKeep) => - input.ConditionalWhere(isConditionValid, x => x.Equals(valueToKeep)).ToArray(); - - [Test] - public void TestChooseWithRefType() - { - var input = new [] { "one", null, "two" }; - - var result = input.Choose(x => x).ToArray(); - - Assert.That(result.GetType(), Is.EqualTo(typeof(string[]))); - Assert.That(result.Length, Is.EqualTo(2)); - Assert.That(result[0], Is.EqualTo("one")); - Assert.That(result[1], Is.EqualTo("two")); - } - - [Test] - public void TestChooseWithValueType() - { - var input = new int?[] { 1, null, 2 }; - - var result = input.Choose(x => x).ToArray(); - - Assert.That(result.GetType(), Is.EqualTo(typeof(int[]))); - Assert.That(result.GetType(), Is.Not.EqualTo(typeof(int?[]))); - Assert.That(result.Length, Is.EqualTo(2)); - Assert.That(result[0], Is.EqualTo(1)); - Assert.That(result[1], Is.EqualTo(2)); - } -} \ No newline at end of file diff --git a/Base.Tests/Extensions/MaybeExtensionTests.cs b/Base.Tests/Extensions/MaybeExtensionTests.cs deleted file mode 100644 index faa0630..0000000 --- a/Base.Tests/Extensions/MaybeExtensionTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using FruityFoundation.Base.Extensions; -using FruityFoundation.Base.Structures; -using NUnit.Framework; - -namespace Base.Tests.Extensions; - -public class MaybeExtensionTests -{ - [Test] - public void EnumerableFirstOrEmptyTests() - { - Assert.AreEqual(Maybe.Empty(), Array.Empty().FirstOrEmpty()); - Assert.AreEqual(Maybe.Create("banana"), new[] { "banana" }.FirstOrEmpty()); - } - - [Test] - public void TestToMaybe() - { - Assert.AreEqual(Maybe.Empty(), Maybe.Empty()); - Assert.AreEqual(Maybe.Create("banana"), "banana".ToMaybe()); - Assert.AreNotEqual(Maybe.Create(293921), Maybe.Create(2)); - } - - [Test] - public void MaybeNullableTests() - { - Assert.IsNull(Maybe.Empty().ToNullable()); - Assert.IsNull(Maybe.Create(0, _ => false).ToNullable()); - } -} \ No newline at end of file diff --git a/Base.Tests/Extensions/NullableExtensionTests.cs b/Base.Tests/Extensions/NullableExtensionTests.cs deleted file mode 100644 index 07ab065..0000000 --- a/Base.Tests/Extensions/NullableExtensionTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using FruityFoundation.Base.Extensions; -using FruityFoundation.Base.Structures; -using NUnit.Framework; - -namespace Base.Tests.Extensions; - -public class NullableExtensionTests -{ - [Test] - public void TestNullableStructOfNullToMaybe() => - Assert.AreEqual(Maybe.Empty(), ((int?)null).ToMaybe()); - - [Test] - public void TestNullableStructOfValueToMaybe() => - Assert.AreEqual(Maybe.Create(25), ((int?)25).ToMaybe()); - - [Test] - public void TestNullableRefOfNullToMaybe() => - Assert.AreEqual(Maybe.Empty(), ((object?)null).ToMaybe()); - - [Test] - public void TestNullableRefOfValueToMaybe() => - Assert.AreEqual(Maybe.Create(new {}), ((object?)new {}).ToMaybe()); -} \ No newline at end of file diff --git a/Base.Tests/Structures/EnumerableExtensionTests.cs b/Base.Tests/Structures/EnumerableExtensionTests.cs new file mode 100644 index 0000000..8709c9d --- /dev/null +++ b/Base.Tests/Structures/EnumerableExtensionTests.cs @@ -0,0 +1,23 @@ +using System.Linq; +using FruityFoundation.Base.Structures; +using NUnit.Framework; + +namespace Base.Tests.Structures; + +public class EnumerableExtensionTests +{ + [TestCase(new object[] { 0, 1, 2 }, true, new object[] { 85 }, ExpectedResult = new object[] { 0, 1, 2, 85 })] + [TestCase(new object[] { "hi" }, false, new object[] { "there" }, ExpectedResult = new object[] { "hi" })] + public object[] TestConditionalConcat(object[] input, bool condition, object[] second) => + input.ConditionalConcat(condition, second).ToArray(); + + [TestCase(new object[] { 0, 1, 2 }, true, 3, ExpectedResult = new object[] { 0, 1, 2, 3 })] + [TestCase(new object[] { 0, 1, 2 }, false, 3, ExpectedResult = new object[] { 0, 1, 2 })] + public object[] TestConditionalAppend(object[] input, bool condition, object second) => + input.ConditionalAppend(condition, second).ToArray(); + + [TestCase(new object[] { 0, 1, 2 }, true, 85, ExpectedResult = new object[0])] + [TestCase(new object[] { "hi", "there" }, false, "there", ExpectedResult = new object[] { "hi", "there" })] + public object[] TestConditionalWhere(object[] input, bool condition, object valueToKeep) => + input.ConditionalWhere(condition, x => x.Equals(valueToKeep)).ToArray(); +} diff --git a/Base.Tests/Structures/MaybeExtensionTests.cs b/Base.Tests/Structures/MaybeExtensionTests.cs new file mode 100644 index 0000000..cb51573 --- /dev/null +++ b/Base.Tests/Structures/MaybeExtensionTests.cs @@ -0,0 +1,29 @@ +using System; +using FruityFoundation.Base.Structures; +using NUnit.Framework; + +namespace Base.Tests.Structures; + +public class MaybeExtensionTests +{ + [Test] + public void EnumerableFirstOrEmptyTests() + { + Assert.AreEqual(Maybe.Empty(), Array.Empty().FirstOrEmpty()); + Assert.AreEqual(Maybe.Just("banana"), new[] { "banana" }.FirstOrEmpty()); + } + + [Test] + public void TestToMaybe() + { + Assert.AreEqual(Maybe.Empty(), Maybe.Empty()); + Assert.AreNotEqual(Maybe.Just(293921), Maybe.Just(2)); + } + + [Test] + public void MaybeNullableTests() + { + Assert.IsNull(Maybe.Empty().ToNullable()); + Assert.IsNull(Maybe.Just(0, hasValue: _ => false).ToNullable()); + } +} diff --git a/Base.Tests/Structures/NullableExtensionTests.cs b/Base.Tests/Structures/NullableExtensionTests.cs new file mode 100644 index 0000000..a8875b2 --- /dev/null +++ b/Base.Tests/Structures/NullableExtensionTests.cs @@ -0,0 +1,15 @@ +using FruityFoundation.Base.Structures; +using NUnit.Framework; + +namespace Base.Tests.Structures; + +public class NullableExtensionTests +{ + [Test] + public void TestNullableStructOfNullToMaybe() => + Assert.AreEqual(Maybe.Empty(), ((int?)null).ToMaybe()); + + [Test] + public void TestNullableStructOfValueToMaybe() => + Assert.AreEqual(Maybe.Just(25), ((int?)25).ToMaybe()); +} diff --git a/Base.Tests/Extensions/StringExtensionTests.cs b/Base.Tests/Structures/StringExtensionTests.cs similarity index 71% rename from Base.Tests/Extensions/StringExtensionTests.cs rename to Base.Tests/Structures/StringExtensionTests.cs index 51ea0f3..3c83373 100644 --- a/Base.Tests/Extensions/StringExtensionTests.cs +++ b/Base.Tests/Structures/StringExtensionTests.cs @@ -1,37 +1,27 @@ -using FruityFoundation.Base.Extensions; -using FruityFoundation.Base.Structures; -using NUnit.Framework; - -namespace Base.Tests.Extensions; - -public class StringExtensionTests -{ - [Test] - [TestCase("banana", "banana", ExpectedResult = true)] - [TestCase("banana", "baNAnA", ExpectedResult = true)] - [TestCase("tuckerIsMyDog", "tuckerisMYdog", ExpectedResult = true)] - [TestCase("if I were a dog, I'd go insane", "how do dogs not get bored", ExpectedResult = false)] - public bool EqualsIgnoreCaseTests(string inputOne, string inputTwo) => - inputOne.EqualsIgnoreCase(inputTwo); - - [Test] - [TestCase("bananas have potassium", "banana", ExpectedResult = true)] - [TestCase("you can't spell trucker without ucker", "UCKER", ExpectedResult = true)] - public bool ContainsIgnoreCaseTests(string haystack, string needle) => - haystack.ContainsIgnoreCase(needle); - - [Test] - public void StringToCouldBeTests() - { -#pragma warning disable CS8604 - Assert.AreEqual(Maybe.Empty(), (null as string).ToMaybe()); -#pragma warning restore CS8604 - Assert.AreEqual(Maybe.Create("banana"), "banana".ToMaybe()); - } - - [Test] - [TestCase("banana", 1, ExpectedResult = "b")] - [TestCase("This is a longer sentence. I would like it capped at 30 characters.", 30, ExpectedResult = "This is a longer sentence. I w")] - public string StringTruncateTests(string str, int maxLength) => - str.Truncate(maxLength); -} \ No newline at end of file +using FruityFoundation.Base.Structures; +using NUnit.Framework; + +namespace Base.Tests.Structures; + +public class StringExtensionTests +{ + [Test] + [TestCase("banana", "banana", ExpectedResult = true)] + [TestCase("banana", "baNAnA", ExpectedResult = true)] + [TestCase("tuckerIsMyDog", "tuckerisMYdog", ExpectedResult = true)] + [TestCase("if I were a dog, I'd go insane", "how do dogs not get bored", ExpectedResult = false)] + public bool EqualsIgnoreCaseTests(string inputOne, string inputTwo) => + inputOne.EqualsIgnoreCase(inputTwo); + + [Test] + [TestCase("bananas have potassium", "banana", ExpectedResult = true)] + [TestCase("you can't spell trucker without ucker", "UCKER", ExpectedResult = true)] + public bool ContainsIgnoreCaseTests(string haystack, string needle) => + haystack.ContainsIgnoreCase(needle); + + [Test] + [TestCase("banana", 1, ExpectedResult = "b")] + [TestCase("This is a longer sentence. I would like it capped at 30 characters.", 30, ExpectedResult = "This is a longer sentence. I w")] + public string StringTruncateTests(string str, int maxLength) => + str.Truncate(maxLength); +} diff --git a/Base/Base.csproj b/Base/Base.csproj old mode 100755 new mode 100644 index d31477b..0dc7808 --- a/Base/Base.csproj +++ b/Base/Base.csproj @@ -15,6 +15,8 @@ 1.2.2 true LICENSE + FruityFoundation.Base + net6.0;net7.0 diff --git a/Base/Extensions/EnumerableExtensions.cs b/Base/Extensions/EnumerableExtensions.cs deleted file mode 100644 index 5cac99c..0000000 --- a/Base/Extensions/EnumerableExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace FruityFoundation.Base.Extensions; - -public static class EnumerableExtensions -{ - public static IEnumerable ConditionalConcat(this IEnumerable enumerable, bool isConditionValid, IEnumerable second) => - !isConditionValid ? enumerable : enumerable.Concat(second); - - public static IEnumerable ConditionalWhere(this IEnumerable enumerable, bool isConditionValid, Func pred) => - !isConditionValid ? enumerable : enumerable.Where(pred); - - public static IEnumerable Choose(this IEnumerable enumerable, Func mapper) - where TOutput : class? => - enumerable - .Select(mapper) - .Where(x => x is not null) - .Cast(); - - public static IEnumerable Choose(this IEnumerable enumerable, Func mapper) - where TOutput : struct => - enumerable - .Select(mapper) - .Where(x => x.HasValue) - .Select(x => x!.Value); -} \ No newline at end of file diff --git a/Base/Extensions/NullableExtensions.cs b/Base/Extensions/NullableExtensions.cs deleted file mode 100644 index 81eee78..0000000 --- a/Base/Extensions/NullableExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using FruityFoundation.Base.Structures; - -namespace FruityFoundation.Base.Extensions; - -public static class NullableExtensions -{ - public static Maybe ToMaybe(this T? item) where T : struct => - item ?? Maybe.Empty(); - - public static Maybe ToMaybe(this T? item) where T : class => - item ?? Maybe.Empty(); -} \ No newline at end of file diff --git a/Base/Extensions/DataReaderExtensions.cs b/Base/Structures/DataReaderMaybeExtensions.cs similarity index 88% rename from Base/Extensions/DataReaderExtensions.cs rename to Base/Structures/DataReaderMaybeExtensions.cs index 2f31ccc..90cc81d 100644 --- a/Base/Extensions/DataReaderExtensions.cs +++ b/Base/Structures/DataReaderMaybeExtensions.cs @@ -1,8 +1,7 @@ -using System; +using System; using System.Data; -using FruityFoundation.Base.Structures; -namespace FruityFoundation.Base.Extensions; +namespace FruityFoundation.Base.Structures; public static class DataReaderExtensions { @@ -42,6 +41,6 @@ public static class DataReaderExtensions public static Maybe TryGetString(this IDataReader reader, int ord) => TryGet(reader, ord, reader.GetString); - private static Maybe TryGet(IDataReader reader, int ord, Func valueGetter) => - reader.IsDBNull(ord) ? Maybe.Empty() : valueGetter(ord); -} \ No newline at end of file + private static Maybe TryGet(IDataRecord reader, int ord, Func valueGetter) => + reader.IsDBNull(ord) ? Maybe.Empty() : valueGetter(ord); +} diff --git a/Base/Structures/EnumerableExtensions.cs b/Base/Structures/EnumerableExtensions.cs new file mode 100644 index 0000000..1ef735c --- /dev/null +++ b/Base/Structures/EnumerableExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FruityFoundation.Base.Structures; + +public static class EnumerableExtensions +{ + public static IEnumerable ConditionalConcat(this IEnumerable enumerable, bool condition, IEnumerable second) => + !condition ? enumerable : enumerable.Concat(second); + + public static IEnumerable ConditionalAppend(this IEnumerable enumerable, bool condition, T second) => + condition ? enumerable.Append(second) : enumerable; + + public static IEnumerable ConditionalWhere(this IEnumerable enumerable, bool condition, Func pred) => + !condition ? enumerable : enumerable.Where(pred); + + public static IEnumerable Choose(this IEnumerable enumerable, Func> chooser) => + enumerable.Select(chooser).Where(x => x.HasValue).Select(x => x.Value); +} diff --git a/Base/Structures/Maybe.cs b/Base/Structures/Maybe.cs index 98d700f..5b375e3 100644 --- a/Base/Structures/Maybe.cs +++ b/Base/Structures/Maybe.cs @@ -1,18 +1,20 @@ -// Normally we wouldn't want to disable Nullable references, but in this case we want to. -// We're assuming that if you're following Maybe conventions, you won't be hitting null ref exceptions. +namespace FruityFoundation.Base.Structures; using System; -#pragma warning disable CS8601 -namespace FruityFoundation.Base.Structures; +public static class Maybe +{ + public static Maybe Just(T value) => new(value, hasValue: true); + public static Maybe Just(T value, Func hasValue) => new(value, hasValue: hasValue(value)); + public static Maybe Empty() => new(val: default!, hasValue: false); +} -[Serializable] -public struct Maybe +public readonly struct Maybe { private readonly T _value; public readonly bool HasValue; - private Maybe(T val = default!, bool hasValue = true) + internal Maybe(T val = default!, bool hasValue = true) { _value = val; HasValue = hasValue; @@ -29,12 +31,13 @@ public struct Maybe } } - public T OrValue(T orVal) => - HasValue ? Value : orVal; + public T OrValue(T orVal) => HasValue ? Value : orVal; + + public T OrEval(Func valueFactory) => HasValue ? Value : valueFactory(); public bool Try(out T val) { - val = HasValue ? Value : default; + val = HasValue ? Value : default!; return HasValue; } @@ -42,23 +45,34 @@ public struct Maybe public T OrThrow(string msg) => HasValue ? Value : throw new Exception(msg); + public T OrThrow(Func messageFactory) => + HasValue ? Value : throw new Exception(messageFactory()); + + public T OrThrow(Func exFactory) => + HasValue ? Value : throw exFactory(); + public Maybe Map(Func transformer) => - HasValue - ? Maybe.Create(transformer(Value)) - : Maybe.Empty(); + HasValue ? Maybe.Just(transformer(Value)) : Maybe.Empty(); - public object ToDbValue() => - HasValue && Value is not null - ? Value - : DBNull.Value; + public Maybe Bind(Func binder) => + HasValue ? binder(Value) : Maybe.Empty(); - public static Maybe Create(T val, bool hasValue = true) => new(val, hasValue); + public Maybe Cast() + { + if (!HasValue) + return Maybe.Empty(); - public static Maybe Create(T val, Func hasValue) => new(val, hasValue(val)); + try + { + return (TOutput)Convert.ChangeType(Value, typeof(TOutput))!; + } + catch (InvalidCastException) + { + return Maybe.Empty(); + } + } - public static Maybe Empty() => new(default!, hasValue: false); - - public static implicit operator Maybe(T val) => Create(val); + public static implicit operator Maybe(T val) => Maybe.Just(val); public static explicit operator T(Maybe val) => val.Value; diff --git a/Base/Structures/MaybeExtensions.cs b/Base/Structures/MaybeExtensions.cs index 1bd9ef9..039d351 100644 --- a/Base/Structures/MaybeExtensions.cs +++ b/Base/Structures/MaybeExtensions.cs @@ -10,7 +10,7 @@ public static class MaybeExtensions { using var enumerator = collection.GetEnumerator(); - return !enumerator.MoveNext() ? Maybe.Empty() : enumerator.Current; + return !enumerator.MoveNext() ? Maybe.Empty() : enumerator.Current; } public static Maybe FirstOrEmpty(this IEnumerable collection, Func pred) @@ -19,9 +19,12 @@ public static class MaybeExtensions if (pred(item)) return item; - return Maybe.Empty(); + return Maybe.Empty(); } + public static Maybe TryGet(this IReadOnlyDictionary dict, TKey key) => + dict.TryGetValue(key, out var value) ? Maybe.Just(value) : Maybe.Empty(); + public static T? ToNullable(this Maybe item) where T : struct => item.HasValue ? item.Value : null; @@ -30,4 +33,4 @@ public static class MaybeExtensions .Select(mapper) .Where(x => x.HasValue) .Select(x => x.Value); -} \ No newline at end of file +} diff --git a/Base/Structures/NullableExtensions.cs b/Base/Structures/NullableExtensions.cs new file mode 100644 index 0000000..b1d950b --- /dev/null +++ b/Base/Structures/NullableExtensions.cs @@ -0,0 +1,7 @@ +namespace FruityFoundation.Base.Structures; + +public static class NullableExtensions +{ + public static Maybe ToMaybe(this T? item) where T : struct => + item ?? Maybe.Empty(); +} diff --git a/Base/Structures/Result.cs b/Base/Structures/Result.cs new file mode 100644 index 0000000..b4db8a9 --- /dev/null +++ b/Base/Structures/Result.cs @@ -0,0 +1,49 @@ +using System; + +namespace FruityFoundation.Base.Structures; + +public readonly struct Result +{ + private readonly Maybe _successVal; + private readonly Maybe _failureVal; + + private Result(Maybe successVal, Maybe failureVal) + { + _successVal = successVal; + _failureVal = failureVal; + } + + public static Result CreateSuccess(TSuccess val) => + new(successVal: val, failureVal: Maybe.Empty()); + + public static Result CreateFailure(TFailure val) => + new(successVal: Maybe.Empty(), failureVal: val); + + public bool IsSuccess => _successVal.HasValue; + public bool IsFailure => _failureVal.HasValue; + + public bool TrySuccess(out TSuccess output) + { + if (!_successVal.Try(out output)) + { + output = default!; + return false; + } + + return true; + } + + public bool TryFailure(out TFailure output) + { + if (!_failureVal.Try(out output)) + { + output = default!; + return false; + } + + return true; + } + + public T Merge(Func onSuccess, Func onFailure) => + IsSuccess ? onSuccess(_successVal.Value) : onFailure(_failureVal.Value); +} diff --git a/Base/Extensions/StringExtensions.cs b/Base/Structures/StringExtensions.cs similarity index 84% rename from Base/Extensions/StringExtensions.cs rename to Base/Structures/StringExtensions.cs index ab1a013..adbad6c 100644 --- a/Base/Extensions/StringExtensions.cs +++ b/Base/Structures/StringExtensions.cs @@ -1,20 +1,20 @@ -using System; - -namespace FruityFoundation.Base.Extensions; - -public static class StringExtensions -{ - public static bool EqualsIgnoreCase(this string str, string otherString) => - str.Equals(otherString, StringComparison.OrdinalIgnoreCase); - - public static bool ContainsIgnoreCase(this string str, string otherString) => - str.IndexOf(otherString, StringComparison.OrdinalIgnoreCase) != -1; - - /// - /// Truncate a string to exactly characters. - /// - /// - /// The maximum number of characters. If has fewer characters, it will be truncated to the length of . - public static string Truncate(this string str, int maxLength) => - str.Substring(0, Math.Min(str.Length, maxLength)); -} \ No newline at end of file +using System; + +namespace FruityFoundation.Base.Structures; + +public static class StringExtensions +{ + public static bool EqualsIgnoreCase(this string str, string otherString) => + str.Equals(otherString, StringComparison.OrdinalIgnoreCase); + + public static bool ContainsIgnoreCase(this string str, string otherString) => + str.IndexOf(otherString, StringComparison.OrdinalIgnoreCase) != -1; + + /// + /// Truncate a string to exactly characters. + /// + /// + /// The maximum number of characters. If has fewer characters, it will be truncated to the length of . + public static string Truncate(this string str, int maxLength) => + str[..Math.Min(str.Length, maxLength)]; +} diff --git a/Db/Db.fsproj b/Db/Db.fsproj old mode 100755 new mode 100644 diff --git a/FsBase/Extensions.fs b/FsBase/Extensions.fs index 5d66624..a811410 100644 --- a/FsBase/Extensions.fs +++ b/FsBase/Extensions.fs @@ -3,7 +3,7 @@ open FruityFoundation.Base.Structures module Option = let toMaybe : 'a option -> Maybe<'a> = function - | Some x -> x |> Maybe.Create + | Some x -> x |> Maybe.Just | None -> Maybe.Empty () let fromMaybe : Maybe<'a> -> 'a option = function diff --git a/FsBase/FsBase.fsproj b/FsBase/FsBase.fsproj old mode 100755 new mode 100644 index 63eb807..3e819bd --- a/FsBase/FsBase.fsproj +++ b/FsBase/FsBase.fsproj @@ -29,7 +29,7 @@ - +