diff --git a/ErrorHandling/ErrorHandling.csproj b/ErrorHandling/ErrorHandling.csproj deleted file mode 100644 index b593d0829..000000000 --- a/ErrorHandling/ErrorHandling.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - Exe - net8.0 - false - - - - - - - - - - - - - - - - - - - - - diff --git a/ErrorHandling/QuerySyntax/ParseResultExtensions.cs b/ErrorHandling/QuerySyntax/ParseResultExtensions.cs deleted file mode 100644 index 442dc3457..000000000 --- a/ErrorHandling/QuerySyntax/ParseResultExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace ErrorHandling.QuerySyntax; - -public static class ParseResultExtensions -{ - public static Result ParseIntResult(this string s, string error = null) - { - int v; - return int.TryParse(s, out v) - ? v.AsResult() - : Result.Fail(error ?? "Не число " + s); - } - public static Result ParseGuidResult(this string s, string error = null) - { - Guid v; - return Guid.TryParse(s, out v) - ? v.AsResult() - : Result.Fail(error ?? "Не GUID " + s); - } -} \ No newline at end of file diff --git a/ErrorHandling/QuerySyntax/ResultQueryExpressionExtensions.cs b/ErrorHandling/QuerySyntax/ResultQueryExpressionExtensions.cs deleted file mode 100644 index 736c83247..000000000 --- a/ErrorHandling/QuerySyntax/ResultQueryExpressionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace ErrorHandling.QuerySyntax; - -public static class ResultQueryExpressionExtensions -{ - public static Result SelectMany( - this Result input, - Func> continuation) - { - throw new NotImplementedException(); - } - - public static Result SelectMany( - this Result input, - Func> continuation, - Func resultSelector) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/ErrorHandling/QuerySyntax/ResultQueryExpression_Should.cs b/ErrorHandling/QuerySyntax/ResultQueryExpression_Should.cs deleted file mode 100644 index 2f65b6d86..000000000 --- a/ErrorHandling/QuerySyntax/ResultQueryExpression_Should.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using FluentAssertions; -using NUnit.Framework; - -namespace ErrorHandling.QuerySyntax; - -[TestFixture] -public class ResultQueryExpression_Should -{ - [Test] - public void SupportLinqMethodChaining() - { - var res = - "1358571172".ParseIntResult() - .SelectMany(i => Convert.ToString(i, 16).AsResult()) - .SelectMany(hex => (hex + hex + hex + hex).ParseGuidResult()); - res.Should().BeEquivalentTo(Result.Ok(Guid.Parse("50FA26A450FA26A450FA26A450FA26A4"))); - } - - [Test] - public void SupportLinqMethodChaining_WithResultSelector() - { - var res = - "1358571172".ParseIntResult() - .SelectMany(i => Convert.ToString(i, 16).AsResult(), (i, hex) => new { i, hex }) - .SelectMany(t => (t.hex + t.hex + t.hex + t.hex).ParseGuidResult()); - res.Should().BeEquivalentTo(Result.Ok(Guid.Parse("50FA26A450FA26A450FA26A450FA26A4"))); - } - - [Test] - public void SupportQueryExpressions() - { - var res = - from i in "1358571172".ParseIntResult() - from hex in Convert.ToString(i, 16).AsResult() - from guid in (hex + hex + hex + hex).ParseGuidResult() - select guid; - res.Should().BeOfType>(); - res.Should().BeEquivalentTo(Result.Ok(Guid.Parse("50FA26A450FA26A450FA26A450FA26A4"))); - } - - [Test] - public void SupportQueryExpressions_WithComplexSelect() - { - // ��� ��������, ���� �������� ������ ������ Query Expressions ��� Result - // �������� �������� �� ������������� i � select - var res = - from i in "1358571172".ParseIntResult() - from hex in Convert.ToString(i, 16).AsResult() - from guid in (hex + hex + hex + hex).ParseGuidResult() - select i + " -> " + guid; - res.Should().BeOfType>(); - res.Should().BeEquivalentTo(Result.Ok("1358571172 -> 50fa26a4-50fa-26a4-50fa-26a450fa26a4")); - } - - [Test] - public void ReturnFail_FromSelectMany_WhenErrorAtTheEnd() - { - var res = - from i in "0".ParseIntResult() - from hex in Convert.ToString(i, 16).AsResult() - from guid in (hex + hex + hex + hex).ParseGuidResult("error is here") - select guid; - res.Should().BeEquivalentTo(Result.Fail("error is here")); - } - - [Test] - public void ReturnFail_FromSelectMany_WhenExceptionOnSomeStage() - { - var res = - from i in "1358571172".ParseIntResult() - from hex in Result.Of(() => Convert.ToString(i, 100500), "error is here") - from guid in (hex + hex + hex + hex).ParseGuidResult() - select guid; - res.Should().BeEquivalentTo(Result.Fail("error is here")); - } - - [Test] - public void ReturnFail_FromSelectMany_WhenErrorAtTheBeginning() - { - var res = - from i in "UNPARSABLE".ParseIntResult("error is here") - from hex in Convert.ToString(i, 16).AsResult() - from guid in (hex + hex + hex + hex).ParseGuidResult() - select guid; - res.Should().BeEquivalentTo(Result.Fail("error is here")); - } -} \ No newline at end of file diff --git a/ErrorHandling/Result.cs b/ErrorHandling/Result.cs deleted file mode 100644 index 2e8db379b..000000000 --- a/ErrorHandling/Result.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; - -namespace ErrorHandling; - -public class None -{ - private None() - { - } -} - -public struct Result -{ - public Result(string error, T value = default(T)) - { - Error = error; - Value = value; - } - - public string Error { get; } - internal T Value { get; } - - public T GetValueOrThrow() - { - if (IsSuccess) return Value; - throw new InvalidOperationException($"No value. Only Error {Error}"); - } - - public bool IsSuccess => Error == null; -} - -public static class Result -{ - public static Result AsResult(this T value) - { - return Ok(value); - } - - public static Result Ok(T value) - { - return new Result(null, value); - } - - public static Result Fail(string e) - { - return new Result(e); - } - - public static Result Of(Func f, string error = null) - { - try - { - return Ok(f()); - } - catch (Exception e) - { - return Fail(error ?? e.Message); - } - } - - public static Result Then( - this Result input, - Func continuation) - { - throw new NotImplementedException(); - } - - public static Result Then( - this Result input, - Func> continuation) - { - throw new NotImplementedException(); - } - - public static Result OnFail( - this Result input, - Action handleError) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/ErrorHandling/Result_should.cs b/ErrorHandling/Result_should.cs deleted file mode 100644 index 45930f67d..000000000 --- a/ErrorHandling/Result_should.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using FakeItEasy; -using FluentAssertions; -using NUnit.Framework; - -namespace ErrorHandling; - -[TestFixture] -public class Result_Should -{ - [Test] - public void Create_Ok() - { - var r = Result.Ok(42); - r.IsSuccess.Should().BeTrue(); - r.GetValueOrThrow().Should().Be(42); - } - - [Test] - public void Create_Fail() - { - var r = Result.Fail("123"); - - r.IsSuccess.Should().BeFalse(); - r.Error.Should().Be("123"); - } - - [Test] - public void ReturnsFail_FromResultOf_OnException() - { - var res = Result.Of(() => { throw new Exception("123"); }); - - res.Should().BeEquivalentTo(Result.Fail("123")); - } - - [Test] - public void ReturnsFailWithCustomMessage_FromResultOf_OnException() - { - var res = Result.Of(() => { throw new Exception("123"); }, "42"); - - res.Should().BeEquivalentTo(Result.Fail("42")); - } - - [Test] - public void ReturnsOk_FromResultOf_WhenNoException() - { - var res = Result.Of(() => 42); - - res.Should().BeEquivalentTo(Result.Ok(42)); - } - - [Test] - public void RunThen_WhenOk() - { - var res = Result.Ok(42) - .Then(n => n + 10); - res.Should().BeEquivalentTo(Result.Ok(52)); - } - - [Test] - public void RunThen_WhenContinuationIsOk() - { - var res = Result.Ok(42) - .Then(n => Result.Ok(n + 10)); - res.Should().BeEquivalentTo(Result.Ok(52)); - } - - [Test] - public void SkipThen_WhenFail() - { - var fail = Result.Fail("������"); - var called = false; - fail.Then(n => - { - called = true; - return n; - }); - called.Should().BeFalse(); - } - - [Test] - public void Then_ReturnsFail_OnException() - { - Func continuation = n => { throw new Exception("123"); }; - var res = Result.Ok(42) - .Then(continuation); - res.Should().BeEquivalentTo(Result.Fail("123")); - } - - [Test] - public void Then_ReturnsFail_OnFailedContinuation() - { - Func> continuation = n => Result.Fail("123"); - var res = Result.Ok(42) - .Then(continuation); - res.Should().BeEquivalentTo(Result.Fail("123")); - } - - [Test] - public void RunOnFail_WhenFail() - { - var fail = Result.Fail("������"); - var errorHandler = A.Fake>(); - - var res = fail.OnFail(errorHandler); - - A.CallTo(() => errorHandler(null)).WithAnyArguments().MustHaveHappened(); - res.Should().BeEquivalentTo(fail); - } - - [Test] - public void SkipOnFail_WhenOk() - { - var ok = Result.Ok(42); - - var res = ok.OnFail(v => { Assert.Fail("Should not be called"); }); - - res.Should().BeEquivalentTo(ok); - } - - [Test] - public void RunThen_WhenOk_Scenario() - { - var res = - Result.Ok("1358571172") - .Then(int.Parse) - .Then(i => Convert.ToString(i, 16)) - .Then(hex => Guid.Parse(hex + hex + hex + hex)); - res.Should().BeEquivalentTo(Result.Ok(Guid.Parse("50FA26A450FA26A450FA26A450FA26A4"))); - } - - [Test] - public void RunThen_WhenOk_ComplexScenario() - { - var parsed = Result.Ok("1358571172").Then(int.Parse); - var res = parsed - .Then(i => Convert.ToString(i, 16)) - .Then(hex => parsed.GetValueOrThrow() + " -> " + Guid.Parse(hex + hex + hex + hex)); - res.Should().BeEquivalentTo(Result.Ok("1358571172 -> 50fa26a4-50fa-26a4-50fa-26a450fa26a4")); - } - - // [Test] - // public void ReplaceError_IfFail() - // { - // Result.Fail("error") - // .ReplaceError(e => "replaced") - // .Should().BeEquivalentTo(Result.Fail("replaced")); - // } - // - // [Test] - // public void ReplaceError_DoNothing_IfSuccess() - // { - // Result.Ok(42) - // .ReplaceError(e => "replaced") - // .Should().BeEquivalentTo(Result.Ok(42)); - // } - // - // [Test] - // public void ReplaceError_DontReplace_IfCalledBeforeError() - // { - // Result.Ok(42) - // .ReplaceError(e => "replaced") - // .Then(n => Result.Fail("error")) - // .Should().BeEquivalentTo(Result.Fail("error")); - // } - // - // [Test] - // public void RefineError_AddErrorMessageBeforePreviousErrorText() - // { - // var calculation = Result.Fail("No connection"); - // calculation - // .RefineError("Posting results to db") - // .Should().BeEquivalentTo(Result.Fail("Posting results to db. No connection")); - // } -} \ No newline at end of file diff --git a/ErrorHandling/Solved/ParseResultExtensions.cs b/ErrorHandling/Solved/ParseResultExtensions.cs deleted file mode 100644 index 0eb2dda6b..000000000 --- a/ErrorHandling/Solved/ParseResultExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace ResultOf -{ - public static class ParseResultExtensions - { - public static Result ParseIntResult(this string s, string error = null) - { - int v; - return int.TryParse(s, out v) - ? v.AsResult() - : Result.Fail(error ?? "Не число " + s); - } - public static Result ParseGuidResult(this string s, string error = null) - { - Guid v; - return Guid.TryParse(s, out v) - ? v.AsResult() - : Result.Fail(error ?? "Не GUID " + s); - } - } -} \ No newline at end of file diff --git a/ErrorHandling/Solved/Result.cs b/ErrorHandling/Solved/Result.cs deleted file mode 100644 index 3b5be71d8..000000000 --- a/ErrorHandling/Solved/Result.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; - -namespace ResultOf -{ - public class None - { - private None() - { - } - } - - public struct Result - { - public Result(string error, T value = default(T)) - { - Error = error; - Value = value; - } - public static implicit operator Result(T v) - { - return Result.Ok(v); - } - - public string Error { get; } - internal T Value { get; } - public T GetValueOrThrow() - { - if (IsSuccess) return Value; - throw new InvalidOperationException($"No value. Only Error {Error}"); - } - public bool IsSuccess => Error == null; - } - - public static class Result - { - public static Result AsResult(this T value) - { - return Ok(value); - } - - public static Result Ok(T value) - { - return new Result(null, value); - } - public static Result Ok() - { - return Ok(null); - } - - public static Result Fail(string e) - { - return new Result(e); - } - - public static Result Of(Func f, string error = null) - { - try - { - return Ok(f()); - } - catch (Exception e) - { - return Fail(error ?? e.Message); - } - } - - public static Result OfAction(Action f, string error = null) - { - try - { - f(); - return Ok(); - } - catch (Exception e) - { - return Fail(error ?? e.Message); - } - } - - public static Result Then( - this Result input, - Func continuation) - { - return input.Then(inp => Of(() => continuation(inp))); - } - - public static Result Then( - this Result input, - Action continuation) - { - return input.Then(inp => OfAction(() => continuation(inp))); - } - - public static Result Then( - this Result input, - Func> continuation) - { - return input.IsSuccess - ? continuation(input.Value) - : Fail(input.Error); - } - - public static Result OnFail( - this Result input, - Action handleError) - { - if (!input.IsSuccess) handleError(input.Error); - return input; - } - - public static Result ReplaceError( - this Result input, - Func replaceError) - { - if (input.IsSuccess) return input; - return Fail(replaceError(input.Error)); - } - - public static Result RefineError( - this Result input, - string errorMessage) - { - return input.ReplaceError(err => errorMessage + ". " + err); - } - } -} \ No newline at end of file diff --git a/ErrorHandling/Solved/ResultQueryExpressionExtensions.cs b/ErrorHandling/Solved/ResultQueryExpressionExtensions.cs deleted file mode 100644 index 88d3d7b09..000000000 --- a/ErrorHandling/Solved/ResultQueryExpressionExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace ResultOf -{ - public static class ResultQueryExpressionExtensions - { - public static Result SelectMany( - this Result input, - Func> continuation) - { - return input.Then(continuation); - } - - public static Result SelectMany( - this Result input, - Func> continuation, - Func resultSelector) - { - return input.Then(continuation) - .Then(o => resultSelector(input.Value, o)); - } - } -} \ No newline at end of file diff --git a/ErrorHandling/Solved/ResultQueryExpression_Should.cs b/ErrorHandling/Solved/ResultQueryExpression_Should.cs deleted file mode 100644 index 7d1dcc4c4..000000000 --- a/ErrorHandling/Solved/ResultQueryExpression_Should.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using FluentAssertions; -using NUnit.Framework; - -namespace ResultOf -{ - [TestFixture] - public class ResultQueryExpression_Should - { - [Test] - public void SupportLinqMethodChaining() - { - var res = - "1358571172".ParseIntResult() - .SelectMany(i => Convert.ToString(i, 16).AsResult()) - .SelectMany(hex => (hex + hex + hex + hex).ParseGuidResult()); - res.Should().BeEquivalentTo(Result.Ok(Guid.Parse("50FA26A450FA26A450FA26A450FA26A4"))); - } - - [Test] - public void SupportLinqMethodChaining_WithResultSelector() - { - var res = - "1358571172".ParseIntResult() - .SelectMany(i => Convert.ToString(i, 16).AsResult(), (i, hex) => new { i, hex }) - .SelectMany(t => (t.hex + t.hex + t.hex + t.hex).ParseGuidResult()); - res.Should().BeEquivalentTo(Result.Ok(Guid.Parse("50FA26A450FA26A450FA26A450FA26A4"))); - } - - [Test] - public void SupportQueryExpressions() - { - var res = - from i in "1358571172".ParseIntResult() - from hex in Convert.ToString(i, 16).AsResult() - from guid in (hex + hex + hex + hex).ParseGuidResult() - select guid; - res.Should().BeOfType>(); - res.Should().BeEquivalentTo(Result.Ok(Guid.Parse("50FA26A450FA26A450FA26A450FA26A4"))); - } - - [Test] - public void SupportQueryExpressions_WithComplexSelect() - { - // ��� ��������, ���� �������� ������ ������ Query Expressions ��� Result - // �������� �������� �� ������������� i � select - var res = - from i in "1358571172".ParseIntResult() - from hex in Convert.ToString(i, 16).AsResult() - from guid in (hex + hex + hex + hex).ParseGuidResult() - select i + " -> " + guid; - res.Should().BeOfType>(); - res.Should().BeEquivalentTo(Result.Ok("1358571172 -> 50fa26a4-50fa-26a4-50fa-26a450fa26a4")); - } - - [Test] - public void ReturnFail_FromSelectMany_WhenErrorAtTheEnd() - { - var res = - from i in "0".ParseIntResult() - from hex in Convert.ToString(i, 16).AsResult() - from guid in (hex + hex + hex + hex).ParseGuidResult("error is here") - select guid; - res.Should().BeEquivalentTo(Result.Fail("error is here")); - } - - [Test] - public void ReturnFail_FromSelectMany_WhenExceptionOnSomeStage() - { - var res = - from i in "1358571172".ParseIntResult() - from hex in Result.Of(() => Convert.ToString(i, 100500), "error is here") - from guid in (hex + hex + hex + hex).ParseGuidResult() - select guid; - res.Should().BeEquivalentTo(Result.Fail("error is here")); - } - - [Test] - public void ReturnFail_FromSelectMany_WhenErrorAtTheBeginning() - { - var res = - from i in "UNPARSABLE".ParseIntResult("error is here") - from hex in Convert.ToString(i, 16).AsResult() - from guid in (hex + hex + hex + hex).ParseGuidResult() - select guid; - res.Should().BeEquivalentTo(Result.Fail("error is here")); - } - } -} \ No newline at end of file diff --git a/ErrorHandling/Solved/Result_Should.cs b/ErrorHandling/Solved/Result_Should.cs deleted file mode 100644 index db932e32f..000000000 --- a/ErrorHandling/Solved/Result_Should.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using FakeItEasy; -using FluentAssertions; -using NUnit.Framework; - -namespace ResultOf -{ - [TestFixture] - public class Result_Should - { - [Test] - public void Create_Ok() - { - var r = Result.Ok(42); - r.IsSuccess.Should().BeTrue(); - r.GetValueOrThrow().Should().Be(42); - } - - [Test] - public void Create_Fail() - { - var r = Result.Fail("123"); - - r.IsSuccess.Should().BeFalse(); - r.Error.Should().Be("123"); - } - - [Test] - public void ReturnsFail_FromResultOf_OnException() - { - var res = Result.Of(() => { throw new Exception("123"); }); - - res.Should().BeEquivalentTo(Result.Fail("123")); - } - - [Test] - public void ReturnsFailWithCustomMessage_FromResultOf_OnException() - { - var res = Result.Of(() => { throw new Exception("123"); }, "42"); - - res.Should().BeEquivalentTo(Result.Fail("42")); - } - - [Test] - public void ReturnsOk_FromResultOf_WhenNoException() - { - var res = Result.Of(() => 42); - - res.Should().BeEquivalentTo(Result.Ok(42)); - } - - [Test] - public void RunThen_WhenOk() - { - var res = Result.Ok(42) - .Then(n => n + 10); - res.Should().BeEquivalentTo(Result.Ok(52)); - } - - [Test] - public void SkipThen_WhenFail() - { - var fail = Result.Fail("������"); - var called = false; - fail.Then(n => - { - called = true; - return n; - }); - called.Should().BeFalse(); - } - - [Test] - public void Then_ReturnsFail_OnException() - { - Func continuation = n => - { - throw new Exception("123"); - }; - var res = Result.Ok(42) - .Then(continuation); - res.Should().BeEquivalentTo(Result.Fail("123")); - } - - [Test] - public void RunOnFail_WhenFail() - { - var fail = Result.Fail("������"); - var errorHandler = A.Fake>(); - - var res = fail.OnFail(errorHandler); - - A.CallTo(() => errorHandler(null)).WithAnyArguments().MustHaveHappened(); - res.Should().BeEquivalentTo(fail); - } - - [Test] - public void SkipOnFail_WhenOk() - { - var ok = Result.Ok(42); - - var res = ok.OnFail(v => { Assert.Fail("Should not be called"); }); - - res.Should().BeEquivalentTo(ok); - } - - [Test] - public void RunThen_WhenOk_Scenario() - { - var res = - Result.Ok("1358571172") - .Then(int.Parse) - .Then(i => Convert.ToString(i, 16)) - .Then(hex => Guid.Parse(hex + hex + hex + hex)); - res.Should().BeEquivalentTo(Result.Ok(Guid.Parse("50FA26A450FA26A450FA26A450FA26A4"))); - } - - [Test] - public void RunThen_WhenOk_ComplexScenario() - { - var parsed = Result.Ok("1358571172").Then(int.Parse); - var res = parsed - .Then(i => Convert.ToString(i, 16)) - .Then(hex => parsed.GetValueOrThrow() + " -> " + Guid.Parse(hex + hex + hex + hex)); - res.Should().BeEquivalentTo(Result.Ok("1358571172 -> 50fa26a4-50fa-26a4-50fa-26a450fa26a4")); - } - - [Test] - public void ReplaceError_IfFail() - { - Result.Fail("error") - .ReplaceError(e => "replaced") - .Should().BeEquivalentTo(Result.Fail("replaced")); - } - - [Test] - public void ReplaceError_DoNothing_IfSuccess() - { - Result.Ok(42) - .ReplaceError(e => "replaced") - .Should().BeEquivalentTo(Result.Ok(42)); - } - - [Test] - public void ReplaceError_DontReplace_IfCalledBeforeError() - { - Result.Ok(42) - .ReplaceError(e => "replaced") - .Then(n => Result.Fail("error")) - .Should().BeEquivalentTo(Result.Fail("error")); - } - - [Test] - public void RefineError_AddErrorMessageBeforePreviousErrorText() - { - var calculation = Result.Fail("No connection"); - calculation - .RefineError("Posting results to db") - .Should().BeEquivalentTo(Result.Fail("Posting results to db. No connection")); - } - } -} \ No newline at end of file diff --git a/FileSenderRailway/Dependencies.cs b/FileSenderRailway/Dependencies.cs deleted file mode 100644 index 43768227b..000000000 --- a/FileSenderRailway/Dependencies.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Security.Cryptography.X509Certificates; - -namespace FileSenderRailway; - -public interface ICryptographer -{ - byte[] Sign(byte[] content, X509Certificate certificate); -} - -public interface IRecognizer -{ - /// Not recognized - Document Recognize(FileContent file); -} - -public interface ISender -{ - /// Can't send - void Send(Document document); -} - -public class Document -{ - public Document(string name, byte[] content, DateTime created, string format) - { - Name = name; - Created = created; - Format = format; - Content = content; - } - - public string Name { get; set; } - public DateTime Created { get; set; } - public string Format { get; set; } - public byte[] Content { get; set; } -} - -public class FileContent -{ - public FileContent(string name, byte[] content) - { - Name = name; - Content = content; - } - - public string Name { get; } - public byte[] Content { get; } -} - -public class FileSendResult -{ - public FileSendResult(FileContent file, string error = null) - { - File = file; - Error = error; - } - - public FileContent File { get; } - public string Error { get; } - public bool IsSuccess => Error == null; -} \ No newline at end of file diff --git a/FileSenderRailway/FileSender.cs b/FileSenderRailway/FileSender.cs deleted file mode 100644 index f7159a7ec..000000000 --- a/FileSenderRailway/FileSender.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; - -namespace FileSenderRailway; - -public class FileSender -{ - private readonly ICryptographer cryptographer; - private readonly IRecognizer recognizer; - private readonly Func now; - private readonly ISender sender; - - public FileSender( - ICryptographer cryptographer, - ISender sender, - IRecognizer recognizer, - Func now) - { - this.cryptographer = cryptographer; - this.sender = sender; - this.recognizer = recognizer; - this.now = now; - } - - public IEnumerable SendFiles(FileContent[] files, X509Certificate certificate) - { - foreach (var file in files) - { - string errorMessage = null; - try - { - Document doc = recognizer.Recognize(file); - if (!IsValidFormatVersion(doc)) - throw new FormatException("Invalid format version"); - if (!IsValidTimestamp(doc)) - throw new FormatException("Too old document"); - doc.Content = cryptographer.Sign(doc.Content, certificate); - sender.Send(doc); - } - catch (FormatException e) - { - errorMessage = "Can't prepare file to send. " + e.Message; - } - catch (InvalidOperationException e) - { - errorMessage = "Can't send. " + e.Message; - } - yield return new FileSendResult(file, errorMessage); - } - } - - private bool IsValidFormatVersion(Document doc) - { - return doc.Format == "4.0" || doc.Format == "3.1"; - } - - private bool IsValidTimestamp(Document doc) - { - var oneMonthBefore = now().AddMonths(-1); - return doc.Created > oneMonthBefore; - } -} \ No newline at end of file diff --git a/FileSenderRailway/FileSenderRailway.csproj b/FileSenderRailway/FileSenderRailway.csproj deleted file mode 100644 index 8c6447891..000000000 --- a/FileSenderRailway/FileSenderRailway.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - Exe - net8.0 - false - - - - - - - - - - - - - - - - - - - - - - diff --git a/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.1.0.0.approved.txt b/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.1.0.0.approved.txt deleted file mode 100644 index 1a1340f6b..000000000 --- a/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.1.0.0.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Invalid format version \ No newline at end of file diff --git a/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.3.1.32.approved.txt b/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.3.1.32.approved.txt deleted file mode 100644 index c0d889604..000000000 --- a/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.3.1.32.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Too old document \ No newline at end of file diff --git a/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.4.0.32.approved.txt b/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.4.0.32.approved.txt deleted file mode 100644 index c0d889604..000000000 --- a/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.4.0.32.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Too old document \ No newline at end of file diff --git a/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.wrong.32.approved.txt b/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.wrong.32.approved.txt deleted file mode 100644 index 1a1340f6b..000000000 --- a/FileSenderRailway/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.wrong.32.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Invalid format version \ No newline at end of file diff --git a/FileSenderRailway/FileSender_Should.Fail_WhenNotRecognized.approved.txt b/FileSenderRailway/FileSender_Should.Fail_WhenNotRecognized.approved.txt deleted file mode 100644 index 3d51001d8..000000000 --- a/FileSenderRailway/FileSender_Should.Fail_WhenNotRecognized.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Can't recognize \ No newline at end of file diff --git a/FileSenderRailway/FileSender_Should.cs b/FileSenderRailway/FileSender_Should.cs deleted file mode 100644 index 2a2031714..000000000 --- a/FileSenderRailway/FileSender_Should.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using ApprovalTests; -using ApprovalTests.Namers; -using ApprovalTests.Reporters; -using FakeItEasy; -using FluentAssertions; -using NUnit.Framework; - -namespace FileSenderRailway; - -[TestFixture] -[UseReporter(typeof(DiffReporter))] -public class FileSender_Should -{ - private FileSender fileSender; - private ICryptographer cryptographer; - private ISender sender; - private IRecognizer recognizer; - - private readonly FileContent file = new(Guid.NewGuid().ToString("N"), Guid.NewGuid().ToByteArray()); - private readonly DateTime now = new(2000, 01, 01); - private readonly X509Certificate certificate = new(); - - [SetUp] - public void SetUp() - { - cryptographer = A.Fake(); - sender = A.Fake(); - recognizer = A.Fake(); - fileSender = new FileSender(cryptographer, sender, recognizer, () => now); - } - - [Test] - public void BeOk_WhenGoodFormat( - [Values("4.0", "3.1")] string format, - [Values(0, 30)] int daysBeforeNow) - { - var signed = SomeByteArray(); - PrepareDocument(file, signed, now.AddDays(-daysBeforeNow), format); - - fileSender.SendFiles(new[] { file }, certificate) - .Should().BeEquivalentTo(new[] { new FileSendResult(file) }); - A.CallTo(() => sender.Send(A.That.Matches(d => d.Content == signed))) - .MustHaveHappened(); - } - - - [Test] - public void Fail_WhenNotRecognized() - { - A.CallTo(() => recognizer.Recognize(file)) - .Throws(new FormatException("Can't recognize")); - - VerifyErrorOnPrepareFile(file, certificate); - } - - [TestCase("1.0", 0)] - [TestCase("4.0", 32)] - [TestCase("3.1", 32)] - [TestCase("wrong", 32)] - [Test] - public void Fail_WhenBadFormatOrTimestamp(string format, int daysBeforeNow) - { - PrepareDocument(file, null, now.AddDays(-daysBeforeNow), format); - using (ApprovalResults.ForScenario(format, daysBeforeNow)) - VerifyErrorOnPrepareFile(file, certificate); - } - - private void PrepareDocument(FileContent content, byte[] signedContent, DateTime created, string format) - { - var document = new Document(content.Name, content.Content, created, format); - A.CallTo(() => recognizer.Recognize(content)).Returns(document); - A.CallTo(() => cryptographer.Sign(content.Content, certificate)).Returns(signedContent); - } - - private void VerifyErrorOnPrepareFile(FileContent fileContent, X509Certificate x509Certificate) - { - var res = fileSender - .SendFiles(new[] { fileContent }, x509Certificate) - .Single(); - res.IsSuccess.Should().BeFalse(); - Approvals.Verify(res.Error); - } - - private static byte[] SomeByteArray() - { - return Guid.NewGuid().ToByteArray(); - } -} \ No newline at end of file diff --git a/FileSenderRailway/README.md b/FileSenderRailway/README.md deleted file mode 100644 index 8fcde6c69..000000000 --- a/FileSenderRailway/README.md +++ /dev/null @@ -1,10 +0,0 @@ -## Задание FileSenderRailway - -1. Сделать класс Document неизменяемым -2. Выделить функцию PrepareFileToSend из SendFiles -3. Добавить дополнительную информацию в тексты ошибок: - * Версию формата, если он не поддерживается - * Дату создания, если документ слишком старый -4. Переделать логику на исключениях на Result. -Можно и нужно менять интерфейсы зависимостей! -5. Сделать метод PrepareFileToSend декларативным diff --git a/FileSenderRailway/Solved/Dependencies.cs b/FileSenderRailway/Solved/Dependencies.cs deleted file mode 100644 index 76d1f87ea..000000000 --- a/FileSenderRailway/Solved/Dependencies.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Security.Cryptography.X509Certificates; -using ResultOf; - -namespace FileSenderRailway.Solved -{ - public class Document - { - public Document(string name, byte[] content, DateTime created, string format) - { - Name = name; - Created = created; - Format = format; - Content = content; - } - - public string Name { get; } - public DateTime Created { get; } - public string Format { get; } - public byte[] Content { get; } - public Document WithContent(byte[] newContent) - => new Document(Name, newContent, Created, Format); - } - - public class FileContent - { - public FileContent(string name, byte[] content) - { - Name = name; - Content = content; - } - - public string Name { get; } - public byte[] Content { get; } - } - - public class FileSendResult - { - public FileSendResult(FileContent file, string error) - { - File = file; - Error = error; - } - - public FileContent File { get; } - public string Error { get; } - } - - public interface ICryptographer - { - byte[] Sign(byte[] content, X509Certificate certificate); - } - - public interface IRecognizer - { - Result Recognize(FileContent file); - } - - public interface ISender - { - Result Send(Document content); - } -} \ No newline at end of file diff --git a/FileSenderRailway/Solved/FileSender.cs b/FileSenderRailway/Solved/FileSender.cs deleted file mode 100644 index c039fbdcb..000000000 --- a/FileSenderRailway/Solved/FileSender.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using ResultOf; - -//using CSharpFunctionalExtensions; - -namespace FileSenderRailway.Solved -{ - public class FileSender - { - private readonly ICryptographer cryptographer; - private readonly IRecognizer recognizer; - private readonly Func now; - private readonly ISender sender; - - public FileSender( - ICryptographer cryptographer, - ISender sender, - IRecognizer recognizer, - Func now) - { - this.cryptographer = cryptographer; - this.sender = sender; - this.recognizer = recognizer; - this.now = now; - } - - public IEnumerable SendFiles(FileContent[] files, X509Certificate certificate) - { - return files.Select(file => new FileSendResult(file, - PrepareFileToSend(file, certificate).Then(doc => sender.Send(doc)).Error)); - } - - public Result PrepareFileToSend(FileContent file, X509Certificate certificate) - { - return recognizer.Recognize(file) - .Then(ValidateFormatIsSupported) - .Then(ValidateIsNotTooOld) - .Then(doc => doc.WithContent(cryptographer.Sign(doc.Content, certificate))) - .RefineError("Can't prepare file to send"); - } - - private Result ValidateFormatIsSupported(Document doc) - { - return Validate(doc, d => d.Format == "4.0" || d.Format == "3.1", $"Invalid format version '{doc.Format}'"); - } - - private Result ValidateIsNotTooOld(Document doc) - { - var oneMonthBefore = now().AddMonths(-1); - return Validate(doc, d => d.Created > oneMonthBefore, $"Too old document. CreationDate: {doc.Created} "); - } - - private Result Validate(T obj, Func predicate, string errorMessage) - { - return predicate(obj) - ? Result.Ok(obj) - : Result.Fail(errorMessage); - } - } -} \ No newline at end of file diff --git a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.1.0.0.approved.txt b/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.1.0.0.approved.txt deleted file mode 100644 index 3ea9f2ca2..000000000 --- a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.1.0.0.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Invalid format version '1.0' \ No newline at end of file diff --git a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.3.1.32.approved.txt b/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.3.1.32.approved.txt deleted file mode 100644 index 4073d2488..000000000 --- a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.3.1.32.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Too old document. CreationDate: 30.11.1999 0:00:00 \ No newline at end of file diff --git a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.4.0.32.approved.txt b/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.4.0.32.approved.txt deleted file mode 100644 index 4073d2488..000000000 --- a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.4.0.32.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Too old document. CreationDate: 30.11.1999 0:00:00 \ No newline at end of file diff --git a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.wrong.32.approved.txt b/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.wrong.32.approved.txt deleted file mode 100644 index 366351f8c..000000000 --- a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenBadFormatOrTimestamp.ForScenario.wrong.32.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Invalid format version 'wrong' \ No newline at end of file diff --git a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenNotRecognized.approved.txt b/FileSenderRailway/Solved/FileSender_Should.Fail_WhenNotRecognized.approved.txt deleted file mode 100644 index 3d51001d8..000000000 --- a/FileSenderRailway/Solved/FileSender_Should.Fail_WhenNotRecognized.approved.txt +++ /dev/null @@ -1 +0,0 @@ -Can't prepare file to send. Can't recognize \ No newline at end of file diff --git a/FileSenderRailway/Solved/FileSender_Should.cs b/FileSenderRailway/Solved/FileSender_Should.cs deleted file mode 100644 index 12e0a4a21..000000000 --- a/FileSenderRailway/Solved/FileSender_Should.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.IO; -using System.Security.Cryptography.X509Certificates; -using ApprovalTests; -using ApprovalTests.Namers; -using ApprovalTests.Reporters; -using FakeItEasy; -using FluentAssertions; -using NUnit.Framework; -using ResultOf; - -namespace FileSenderRailway.Solved -{ - [TestFixture] - [UseReporter(typeof(DiffReporter))] - public class FileSender_Should - { - private FileSender fileSender; - private ICryptographer cryptographer; - private IRecognizer recognizer; - - [SetUp] - public void SetUp() - { - Directory.SetCurrentDirectory(TestContext.CurrentContext.TestDirectory); - cryptographer = A.Fake(); - recognizer = A.Fake(); - fileSender = new FileSender(cryptographer, A.Fake(), recognizer, () => now); - } - - private readonly FileContent file = new FileContent(Guid.NewGuid().ToString("N"), Guid.NewGuid().ToByteArray()); - private readonly DateTime now = new DateTime(2000, 01, 01); - private readonly X509Certificate certificate = new X509Certificate(); - - [Test] - public void BeOk_WhenGoodFormat( - [Values("4.0", "3.1")]string format, - [Values(0, 30)]int daysBeforeNow) - { - var signedContent = SomeByteArray(); - var document = PrepareDocument(file, signedContent, now.AddDays(-daysBeforeNow), format); - var expectedDocument = document.WithContent(signedContent); - var result = fileSender.PrepareFileToSend(file, certificate); - - result.IsSuccess.Should().BeTrue(); - result.Value.Should().BeEquivalentTo(expectedDocument); - } - - [Test] - public void Fail_WhenNotRecognized() - { - A.CallTo(() => recognizer.Recognize(file)) - .Returns(Result.Fail("Can't recognize")); - - VerifyErrorOnPrepareFile(file, certificate); - } - - [TestCase("1.0", 0)] - [TestCase("4.0", 32)] - [TestCase("3.1", 32)] - [TestCase("wrong", 32)] - [Test] - public void Fail_WhenBadFormatOrTimestamp(string format, int daysBeforeNow) - { - PrepareDocument(file, null, now.AddDays(-daysBeforeNow), format); - using (ApprovalResults.ForScenario(format, daysBeforeNow)) - VerifyErrorOnPrepareFile(file, certificate); - } - - private Document PrepareDocument(FileContent fileToPrepare, byte[] signed, DateTime created, string format) - { - var document = new Document(fileToPrepare.Name, fileToPrepare.Content, created, format); - A.CallTo(() => recognizer.Recognize(fileToPrepare)).Returns(Result.Ok(document)); - A.CallTo(() => cryptographer.Sign(fileToPrepare.Content, certificate)).Returns(signed); - return document; - } - - private void VerifyErrorOnPrepareFile(FileContent fileContent, X509Certificate x509Certificate) - { - var res = fileSender.PrepareFileToSend(fileContent, x509Certificate); - res.IsSuccess.Should().BeFalse(); - Approvals.Verify(res.Error); - } - - private static byte[] SomeByteArray() - { - return Guid.NewGuid().ToByteArray(); - } - } -} \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/ConsoleUi.cs b/Samples/ConwaysGameOfLife/ConsoleUi.cs deleted file mode 100644 index 155904254..000000000 --- a/Samples/ConwaysGameOfLife/ConsoleUi.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace ConwaysGameOfLife -{ - public class ConsoleUi : IGameUi - { - public void UpdateAll(IReadonlyField field) - { - Console.SetCursorPosition(0, 0); - for (int y = 0; y < field.Height; y++) - { - for (int x = 0; x < field.Width; x++) - { - var symbol = field.IsAlive(x, y) ? '#' : ' '; - Console.Write(symbol); - } - Console.WriteLine(); - } - } - - public void UpdateCell(int x, int y, bool alive) - { - Console.SetCursorPosition(x, y); - Console.Write(alive ? '#' : ' '); - - } - } -} \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/ConwaysGameOfLife.csproj b/Samples/ConwaysGameOfLife/ConwaysGameOfLife.csproj deleted file mode 100644 index 7aec55587..000000000 --- a/Samples/ConwaysGameOfLife/ConwaysGameOfLife.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - Exe - net8.0 - false - - - ConwaysGameOfLife.Program - - - - - - - - diff --git a/Samples/ConwaysGameOfLife/Game.cs b/Samples/ConwaysGameOfLife/Game.cs deleted file mode 100644 index e267ec079..000000000 --- a/Samples/ConwaysGameOfLife/Game.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace ConwaysGameOfLife -{ - public class Game : IReadonlyField - { - public int Width { get; } - public int Height { get; } - - private bool[,] isAlive; - - public Game(Size size) - { - Width = size.Width; - Height = size.Height; - isAlive = new bool[Width, Height]; - } - - private Game(bool[,] alive) - { - Width = alive.GetLength(0); - Height = alive.GetLength(1); - isAlive = alive; - } - - public void Revive(params Point[] cells) - { - foreach (var pos in cells) - isAlive[(pos.X + Width) % Width, (pos.Y + Height) % Height] = true; - } - - public StepResult Step() - { - var willBeAlive = new bool[Width, Height]; - var changes = new List(); - for (int y = 0; y < Height; y++) - for (int x = 0; x < Width; x++) - { - willBeAlive[x, y] = WillBeAlive(x, y); - if (willBeAlive[x, y] != isAlive[x, y]) - { - var change = new ChangedCell(x, y, willBeAlive[x, y]); - changes.Add(change); - } - } - isAlive = willBeAlive; - return new StepResult(new Game(willBeAlive), changes); - } - - private bool WillBeAlive(int x, int y) - { - var aliveCount = GetNeighbours(x, y).Count(IsAlive); - return WillBeAlive(isAlive[x, y], aliveCount); - } - - private bool WillBeAlive(bool alive, int aliveNeighbours) - { - return aliveNeighbours == 3 || aliveNeighbours == 2 && alive; - } - - private IEnumerable GetNeighbours(int x, int y) - { - return - from nx in new[] { x - 1, x, x + 1 } - from ny in new[] { y - 1, y, y + 1 } - where nx != x || ny != y - select new Point(nx, ny); - } - - public bool IsAlive(Point pos) - { - return IsAlive(pos.X, pos.Y); - } - - public bool IsAlive(int x, int y) - { - return isAlive[(x + Width) % Width, (y + Height) % Height]; - } - - public override string ToString() - { - - var rows = Enumerable.Range(0, Height) - .Select(y => - string.Join("", - Enumerable.Range(0, Width).Select(x => isAlive[x, y] ? "#" : " ") - )); - return string.Join("\n", rows); - } - } - - public class StepResult - { - public List ChangedCells { get; set; } - public readonly Game NextState; - - public StepResult(Game nextState, List changes) - { - ChangedCells = changes; - NextState = nextState; - } - - } - - public class ChangedCell - { - public ChangedCell(int x, int y, bool isAlive) - { - X = x; - Y = y; - IsAlive = isAlive; - } - - public int X { get; private set; } - public int Y { get; private set; } - public bool IsAlive { get; private set; } - } -} \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/Game_Ui_Interaction_Tests.cs b/Samples/ConwaysGameOfLife/Game_Ui_Interaction_Tests.cs deleted file mode 100644 index 44414bd17..000000000 --- a/Samples/ConwaysGameOfLife/Game_Ui_Interaction_Tests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// using FakeItEasy; -// using NUnit.Framework; -// -// namespace ConwaysGameOfLife -// { -// [TestFixture] -// public class Game_Ui_Interaction_Tests -// { -// private IGameUi ui; -// private Game game; -// -// [SetUp] -// public void SetUp() -// { -// ui = A.Fake(); -// game = new Game(new Size(2, 2)); -// } -// -// [Test] -// public void Revive_UpdatesAllUi() -// { -// game.Revive(new Point(1, 1)); -// -// A.CallTo(() => ui.UpdateAll(game)) -// .MustHaveHappened(1, Times.Exactly); -// } -// -// [Test] -// public void Step_UpdatesOnlyChangedCellsInUi() -// { -// game.Revive(new Point(0, 0)); -// game.Step(); -// -// A.CallTo(() => ui.UpdateCell(0, 0, false)) -// .MustHaveHappened(1, Times.Exactly); -// A.CallTo(() => ui.UpdateCell(A.Ignored, A.Ignored, false)) -// .MustHaveHappened(1, Times.Exactly); -// } -// } -// } \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/IGameUi.cs b/Samples/ConwaysGameOfLife/IGameUi.cs deleted file mode 100644 index 957eed4a1..000000000 --- a/Samples/ConwaysGameOfLife/IGameUi.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace ConwaysGameOfLife -{ - public interface IGameUi - { - void UpdateAll(IReadonlyField field); - void UpdateCell(int x, int y, bool alive); - } -} \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/IReadonlyField.cs b/Samples/ConwaysGameOfLife/IReadonlyField.cs deleted file mode 100644 index 0281e0622..000000000 --- a/Samples/ConwaysGameOfLife/IReadonlyField.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace ConwaysGameOfLife -{ - public interface IReadonlyField - { - int Width { get; } - int Height { get; } - bool IsAlive(int x, int y); - } -} \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/Patterns.cs b/Samples/ConwaysGameOfLife/Patterns.cs deleted file mode 100644 index 8989e807f..000000000 --- a/Samples/ConwaysGameOfLife/Patterns.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Linq; - -namespace ConwaysGameOfLife -{ - public class Patterns - { - public static Point[] GetHorizontalStick(Point topLeft) - { - return new[] { P(0, 0), P(1, 0), P(2, 0) }.Select(p => p.Add(topLeft)).ToArray(); - } - - public static Point[] GetGlider(Point topLeft) - { - return new[] { - new Point(0, 0), new Point(0, 2), - new Point(1, 1), new Point(1, 2), - new Point(2, 1) - }.Select(p => p.Add(topLeft)).ToArray(); - - } - - public static Point[] GetR(Point topLeft) - { - return new[] { - P(1, 0), P(2, 0), - P(0, 1), P(1, 1), - P(1, 2) - }.Select(p => p.Add(topLeft)).ToArray(); - } - private static Point P(int x, int y) - { - return new Point(x, y); - } - } -} \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/Point.cs b/Samples/ConwaysGameOfLife/Point.cs deleted file mode 100644 index 2278955de..000000000 --- a/Samples/ConwaysGameOfLife/Point.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace ConwaysGameOfLife -{ - public class Point - { - public Point(int x, int y) - { - X = x; - Y = y; - } - - public readonly int X, Y; - - public Point Add(Point p) - { - return new Point(p.X + X, p.Y + Y); - } - - protected bool Equals(Point other) - { - return X == other.X && Y == other.Y; - } - - public override string ToString() - { - return string.Format("X: {0}, Y: {1}", X, Y); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((Point) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (X*397) ^ Y; - } - } - } -} \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/Program.cs b/Samples/ConwaysGameOfLife/Program.cs deleted file mode 100644 index 7398b28ce..000000000 --- a/Samples/ConwaysGameOfLife/Program.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Ninject; -using System; - -namespace ConwaysGameOfLife -{ - public class Program - { - public Program(Game game, IGameUi ui) - { - this.game = game; - this.ui = ui; - } - - private Game game; - private IGameUi ui; - - private static void Main() - { - var container = new StandardKernel(); - container.Bind().To(); - container.Bind().ToConstant(new Size(60, 20)); - container.Bind().ToSelf() - .OnActivation(g => g.Revive(Patterns.GetGlider(new Point(25, 8)))); - - var program = container.Get(); - program.PlayGame(); - } - - public void PlayGame() - { - ui.UpdateAll(game); - while (true) - { - var key = Console.ReadKey(intercept: true); - if (key.Key == ConsoleKey.Escape) break; - game = DoGameStep(game, ui); - } - } - - public static Game DoGameStep(Game game, IGameUi ui) - { - var stepResult = game.Step(); - var newGame = stepResult.NextState; - foreach (var changedCell in stepResult.ChangedCells) - ui.UpdateCell(changedCell.X, changedCell.Y, changedCell.IsAlive); - return newGame; - } - } -} \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/README.md b/Samples/ConwaysGameOfLife/README.md deleted file mode 100644 index 5086516da..000000000 --- a/Samples/ConwaysGameOfLife/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## Задача Game of Life - -1. Уберите зависимость Game от IGameUi. - -2. Сделайте Game неизменяемым классом. \ No newline at end of file diff --git a/Samples/ConwaysGameOfLife/Size.cs b/Samples/ConwaysGameOfLife/Size.cs deleted file mode 100644 index b58632be1..000000000 --- a/Samples/ConwaysGameOfLife/Size.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace ConwaysGameOfLife -{ - public class Size - { - public int Width { get; } - public int Height { get; } - - public Size(int width, int height) - { - Width = width; - Height = height; - } - } -} \ No newline at end of file diff --git a/Samples/Summator/DataSource.cs b/Samples/Summator/DataSource.cs deleted file mode 100644 index c51e262a4..000000000 --- a/Samples/Summator/DataSource.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.IO; - -namespace FP -{ - ///Этот класс нельзя менять. Считайте, что он вам дан в виде бинарной зависимости. - public class DataSource : IDisposable - { - private readonly StreamReader reader; - public DataSource(string filename) - { - reader = new StreamReader(filename); - } - - ///null if no more data - public string[] NextRecord() - { - var line = reader.ReadLine(); - return line == null ? null : line.Split(' '); - } - - public void Dispose() - { - reader.Close(); - } - } -} \ No newline at end of file diff --git a/Samples/Summator/ISumFormatter.cs b/Samples/Summator/ISumFormatter.cs deleted file mode 100644 index 4fbeca64f..000000000 --- a/Samples/Summator/ISumFormatter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Linq; - -namespace FP -{ - public interface ISumFormatter - { - string Format(int[] nums, int sum); - } - - public class HexSumFormatter : ISumFormatter - { - public string Format(int[] nums, int sum) - { - return string.Format("Sum({0}) = {1}", - string.Join(" ", nums.Select(n => Convert.ToString(n, 16))), - Convert.ToString(sum, 16)); - } - } -} \ No newline at end of file diff --git a/Samples/Summator/README.md b/Samples/Summator/README.md deleted file mode 100644 index 200e59470..000000000 --- a/Samples/Summator/README.md +++ /dev/null @@ -1,12 +0,0 @@ -## Задача Functional Style - -Проект Summator написан в стиле SOLID. - -Перепишите его в функциональном стиле: - -1. Отделите максимум логики от побочных эффектов. -2. Создайте нужные вам методы. Старайтесь, чтобы методы были самодостаточными и потенциально полезными вне контекста этой задачи. -3. Тестируемость не должна пострадать. - - -Проанализируйте гибкость финального решения. Стало ли оно гибче, чем исходное или наоборот? \ No newline at end of file diff --git a/Samples/Summator/Summator.cs b/Samples/Summator/Summator.cs deleted file mode 100644 index bab9214f6..000000000 --- a/Samples/Summator/Summator.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace FP -{ - public class Summator - { - private readonly ISumFormatter formatter; - private readonly Func openDatasource; - private readonly string outputFilename; - /* - Отрефакторите код. - 1. Отделите максимум логики от побочных эффектов. - 2. Создайте нужные вам методы. - 3. Сделайте так, чтобы максимум кода оказалось внутри универсальных методов, потенциально полезных в других местах программы. - */ - - public Summator(Func openDatasource, ISumFormatter formatter, string outputFilename) - { - this.openDatasource = openDatasource; - this.formatter = formatter; - this.outputFilename = outputFilename; - } - - - public void ProcessOld() - { - using (var input = openDatasource()) - using (var writer = new StreamWriter(outputFilename)) - { - var c = 0; - while (true) - { - string[] record = input.NextRecord(); - if (record == null) break; - c++; - var nums = record.Select(part => Convert.ToInt32(part, 16)).ToArray(); - var sum = nums.Sum(); - var text = formatter.Format(nums, sum); - writer.WriteLine(text); - if (c % 100 == 0) - Console.WriteLine("processed {0} items", c); - } - } - } - - public void ProcessRefactored() - { - var res = SumRecords(openDatasource(), formatter) - .AfterEvery(100, c => Console.WriteLine("processed {0} items", c)); - File.WriteAllLines(outputFilename, res); - } - - public static IEnumerable SumRecords( - DataSource dataSource, - ISumFormatter formatter) - { - return dataSource.ReadIntRecords(16) - .Select(args => formatter.Format(args, args.Sum())); - } - } - - public static class DataSourceExtensions - { - public static IEnumerable ReadRecords(this DataSource data) - { - return Enumeration.RepeatUntilNull(data.NextRecord); - } - - public static IEnumerable ReadIntRecords(this DataSource data, int radix) - { - return data.ReadRecords() - .Select(record => record.Select(f => Convert.ToInt32(f, radix)) - .ToArray()); - } - } - - public static class Enumeration - { - public static IEnumerable RepeatUntilNull(Func get) - { - return Repeat(get).TakeWhile(i => i != null); - } - - public static IEnumerable Repeat(Func get) - { - while (true) yield return get(); - // ReSharper disable once IteratorNeverReturns - } - - public static IEnumerable AfterEvery( - this IEnumerable items, - int period, - Action afterNth) - { - var n = 0; - foreach (var item in items) - { - n++; - yield return item; - if (n % period == 0) afterNth(n); - } - } - } -} \ No newline at end of file diff --git a/Samples/Summator/Summator.csproj b/Samples/Summator/Summator.csproj deleted file mode 100644 index c024e602c..000000000 --- a/Samples/Summator/Summator.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - Exe - net8.0 - false - FP - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - diff --git a/Samples/Summator/SummatorTests.cs b/Samples/Summator/SummatorTests.cs deleted file mode 100644 index 412b58b4e..000000000 --- a/Samples/Summator/SummatorTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using NUnit.Framework; -using System; -using System.IO; -using System.Linq; - -namespace FP -{ - [TestFixture] - public class SummatorTests - { - private Summator summator; - private const string LargeInputFilename = "process-large-file.txt"; - private const string OutputFilename = "process-result.txt"; - private const string ExpectedOutputFilename = "expected-process-result.txt"; - - [SetUp] - public void SetUp() - { - Directory.SetCurrentDirectory( - TestContext.CurrentContext.WorkDirectory); - summator = new Summator( - () => new DataSource(LargeInputFilename), - new HexSumFormatter(), - OutputFilename); - } - - - [Test] - public void Process_GeneratesCorrectOutputFile() - { - var actualResultFile = new FileInfo(OutputFilename); - if (actualResultFile.Exists) actualResultFile.Delete(); - - summator.ProcessOld(); - - CollectionAssert.AreEqual( - File.ReadAllLines(ExpectedOutputFilename), - File.ReadAllLines(actualResultFile.FullName)); - } - - [Test] - public void Process_ShowProgressOnConsole() - { - var stdOut = Console.Out; - try - { - var consoleOutput = new StringWriter(); - Console.SetOut(consoleOutput); - - summator.ProcessOld(); - - var actualOutput = consoleOutput.ToString() - .TrimEnd() - .Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); - Assert.AreEqual("processed 100 items", actualOutput.First()); - Assert.AreEqual("processed 1000 items", actualOutput.Last()); - Assert.AreEqual(10, actualOutput.Length); - } - finally - { - Console.SetOut(stdOut); - } - } - - [Test] - [Explicit("Генератор данных. Не нужен для выполнения задания")] - public void GenerateInput() - { - var r = new Random(); - File.WriteAllLines(LargeInputFilename, - Enumerable.Range(0, 1000).Select(i => - string.Join( - " ", - Enumerable.Range(0, 4).Select(j => Convert.ToString(r.Next(1000), 16)) - ))); - } - } -} \ No newline at end of file diff --git a/Samples/Summator/expected-process-result.txt b/Samples/Summator/expected-process-result.txt deleted file mode 100644 index d4b89e1a9..000000000 --- a/Samples/Summator/expected-process-result.txt +++ /dev/null @@ -1,1000 +0,0 @@ -Sum(34f 21f 2d9 355) = b9c -Sum(318 2a9 2a4 161) = 9c6 -Sum(2e3 82 1a6 231) = 73c -Sum(162 2c7 36f 3c2) = b5a -Sum(9d d4 20c 3af) = 72c -Sum(9a 3ab 2df 163) = 887 -Sum(170 3e7 1 2f9) = 851 -Sum(3cc 185 30f 16a) = 9ca -Sum(390 8b 127 1a2) = 6e4 -Sum(8 f9 36b 194) = 600 -Sum(68 2cb 1c5 1a1) = 699 -Sum(7b 35 69 321) = 43a -Sum(e5 178 24b 26b) = 713 -Sum(8f 235 145 38c) = 795 -Sum(327 176 1e4 319) = 99a -Sum(2a8 393 17c 15e) = 915 -Sum(15a 3b 288 d7) = 4f4 -Sum(1a0 1cc 3b9 38b) = ab0 -Sum(151 78 346 1b7) = 6c6 -Sum(1e6 13d e7 13a) = 544 -Sum(37d c8 214 253) = 8ac -Sum(322 a4 da 15a) = 5fa -Sum(32d 182 262 27a) = 98b -Sum(2fc 52 2d4 bd) = 6df -Sum(14e 67 47 3f) = 23b -Sum(194 37a 180 300) = 98e -Sum(1a7 2a7 11a 17) = 57f -Sum(2d7 376 1a6 1ea) = 9dd -Sum(8e a9 383 1df) = 699 -Sum(17f 311 223 b6) = 769 -Sum(348 12e 191 1d) = 624 -Sum(352 13e 8f ff) = 61e -Sum(18c 289 68 17f) = 5fc -Sum(f6 a8 38d 3) = 52e -Sum(32f 2fb ac 7a) = 750 -Sum(371 c3 26b 39f) = a3e -Sum(3c4 77 1ec 252) = 879 -Sum(b7 f4 325 225) = 6f5 -Sum(22b 378 df 66) = 6e8 -Sum(1e8 163 395 68) = 748 -Sum(218 1a 272 4d) = 4f1 -Sum(30d 27 f3 3cd) = 7f4 -Sum(103 37f 298 26b) = 985 -Sum(264 1a8 12c 284) = 7bc -Sum(2ab 1da 41 2da) = 7a0 -Sum(33a 224 48 98) = 63e -Sum(34b 22b 33b 166) = a17 -Sum(3b1 327 203 1cc) = aa7 -Sum(34e 43 24a 356) = 931 -Sum(239 21d 92 39d) = 885 -Sum(36c 206 14f 11f) = 7e0 -Sum(243 ba 3a8 82) = 727 -Sum(24c 242 1a3 e) = 63f -Sum(121 ba 116 3dc) = 6cd -Sum(3ba 1da ea e1) = 75f -Sum(d3 375 1c 1c4) = 628 -Sum(17c cc 304 220) = 76c -Sum(346 1bd 4b 8d) = 5db -Sum(148 8b 356 133) = 65c -Sum(d5 310 23d 290) = 8b2 -Sum(26a 2b8 301 16f) = 992 -Sum(184 1f4 aa 294) = 6b6 -Sum(315 256 384 5f) = 94e -Sum(132 398 2c9 3df) = b72 -Sum(1ea 372 38b 176) = a5d -Sum(19d 187 124 8e) = 4d6 -Sum(1f6 115 2ae 96) = 64f -Sum(14b 3cb 307 aa) = 8c7 -Sum(384 241 25e 202) = a25 -Sum(73 295 40 36f) = 6b7 -Sum(22 70 2f3 ef) = 474 -Sum(221 3d4 343 197) = acf -Sum(1a9 35f 331 14a) = 983 -Sum(36c c6 f2 e3) = 607 -Sum(193 272 360 6e) = 7d3 -Sum(32e 14 148 331) = 7bb -Sum(337 2d9 c2 2d8) = 9aa -Sum(139 c7 36b 33c) = 8a7 -Sum(332 1c 154 12d) = 5cf -Sum(27c 35 255 209) = 70f -Sum(1ba 116 2d4 1d4) = 778 -Sum(9a 1bc 125 2be) = 639 -Sum(14f 17b 6e 1e8) = 520 -Sum(31c 3b9 40 d) = 722 -Sum(30f 3a9 1a0 32f) = b87 -Sum(312 6a 5d e2) = 4bb -Sum(3db 3dc 1ff 350) = d06 -Sum(359 3ad 24f 15e) = ab3 -Sum(1d 157 29c 217) = 627 -Sum(128 35c 297 17a) = 895 -Sum(1b3 1eb 189 3a6) = 8cd -Sum(58 182 20 361) = 55b -Sum(3c3 146 206 2ba) = 9c9 -Sum(27c 30d 2df 14f) = 9b7 -Sum(b9 1f1 1e0 a6) = 530 -Sum(3a5 1bd 34d 18a) = a39 -Sum(314 2f7 1ca 1c5) = 99a -Sum(11c 211 2cd 39b) = 995 -Sum(112 25 17c 126) = 3d9 -Sum(2c9 9f 8a 258) = 64a -Sum(3bc 285 375 212) = bc8 -Sum(1a6 37c 2ca f8) = 8e4 -Sum(25f 14d 15e 31e) = 828 -Sum(17b 1f1 1bc 3de) = 906 -Sum(286 3e7 91 148) = 846 -Sum(39f 242 244 1b1) = 9d6 -Sum(261 253 1a8 256) = 8b2 -Sum(191 1b8 26e 19) = 5d0 -Sum(167 36f d1 120) = 6c7 -Sum(22f 13a 3cb 37f) = ab3 -Sum(2d d2 34d 3b7) = 803 -Sum(b3 397 21f 308) = 971 -Sum(251 185 288 2ca) = 928 -Sum(d 329 2a1 179) = 750 -Sum(40 1c3 399 33b) = 8d7 -Sum(1d4 73 34f a6) = 63c -Sum(2c7 144 1b6 1f3) = 7b4 -Sum(11f 9c 1ae 14b) = 4b4 -Sum(1b f9 11a 2cd) = 4fb -Sum(2dc 275 fe 2b2) = 901 -Sum(33 287 5 b) = 2ca -Sum(318 38b c 225) = 8d4 -Sum(ce 33f df 6c) = 558 -Sum(189 8f 1ab 3a2) = 765 -Sum(16b 2a7 f0 357) = 859 -Sum(1e0 2c 1e8 1b4) = 5a8 -Sum(3bf 13c 2ae 2fb) = aa4 -Sum(20f 3bb 285 1b3) = a02 -Sum(c4 e7 308 334) = 7e7 -Sum(6d 343 176 323) = 849 -Sum(137 379 124 1c7) = 79b -Sum(3a5 141 3aa 373) = c03 -Sum(336 160 161 35) = 62c -Sum(184 18f d1 6) = 3ea -Sum(9f 239 34 1db) = 4e7 -Sum(dd f8 16 fb) = 2e6 -Sum(ba 314 38f a2) = 7ff -Sum(16f 25f 334 20f) = 911 -Sum(130 1b5 21f 24f) = 753 -Sum(2ef 20 1f7 27d) = 783 -Sum(17a 322 3ad af) = 8f8 -Sum(259 24f 2e 31d) = 7f3 -Sum(15 301 295 21c) = 7c7 -Sum(30f 382 245 3e) = 914 -Sum(362 29 10d 91) = 529 -Sum(19a 308 204 d6) = 77c -Sum(213 33a 2ec 3b7) = bf0 -Sum(358 26a ff 7e) = 73f -Sum(41 19e 61 1a2) = 3e2 -Sum(133 34f 289 252) = 95d -Sum(2e5 72 8c 255) = 638 -Sum(3b2 118 2e8 196) = 948 -Sum(357 1e0 2d5 2c5) = ad1 -Sum(2fb 166 e2 25a) = 79d -Sum(11e 2d6 283 307) = 97e -Sum(34a 5f 3ad 193) = 8e9 -Sum(202 216 1db 170) = 763 -Sum(320 a2 2f3 12) = 6c7 -Sum(188 2a2 193 128) = 6e5 -Sum(27b 396 10c fa) = 817 -Sum(52 156 5f 177) = 37e -Sum(37d 221 16b 2c3) = 9cc -Sum(bc 1ef 84 244) = 573 -Sum(cb 369 2f0 285) = 9a9 -Sum(ac 2e1 53 19b) = 57b -Sum(325 177 25d 2b4) = 9ad -Sum(2d4 2c2 13d 58) = 72b -Sum(3ba 3a2 3c6 16f) = c91 -Sum(1ca 189 2b5 1f4) = 7fc -Sum(3e7 235 1fd 3c8) = be1 -Sum(ab 2ff b4 131) = 58f -Sum(1e ae 32e 207) = 601 -Sum(39a 2f5 7b 381) = a8b -Sum(342 358 1bd 2c2) = b19 -Sum(2e1 192 28a ba) = 7b7 -Sum(e3 112 2f0 101) = 5e6 -Sum(210 2fd 7a 32a) = 8b1 -Sum(b3 fc 286 25f) = 694 -Sum(e4 72 f0 25) = 26b -Sum(c2 12c 296 226) = 6aa -Sum(37b 31e a6 c4) = 803 -Sum(327 45 214 25a) = 7da -Sum(3b3 3db 2fa 254) = cdc -Sum(392 142 2e5 380) = b39 -Sum(f 39b 308 108) = 7ba -Sum(34 4 153 29e) = 429 -Sum(6f 203 29d 2cf) = 7de -Sum(267 198 1ff 1b5) = 7b3 -Sum(2e4 64 127 1ad) = 61c -Sum(6b 22b 1c1 1cb) = 622 -Sum(e8 208 35e c0) = 70e -Sum(1e9 31 2b5 389) = 858 -Sum(175 157 16 10f) = 3f1 -Sum(20b 18d 1f2 377) = 901 -Sum(1cb 1f0 55 123) = 533 -Sum(18f 32c 3da 21b) = ab0 -Sum(1db 145 358 32d) = 9a5 -Sum(1b 137 315 1cb) = 632 -Sum(1d9 13d 20 213) = 549 -Sum(8d 92 b4 3d) = 210 -Sum(336 2fc 15a 10f) = 89b -Sum(181 ef 391 156) = 757 -Sum(25a 197 3ca 263) = a1e -Sum(1d6 9e 3b 1a3) = 452 -Sum(215 143 2cc a3) = 6c7 -Sum(c1 36f 36d 3e) = 7db -Sum(22a 232 31e ce) = 848 -Sum(16d 3c6 2e9 138) = 954 -Sum(13b 17 1d4 27b) = 5a1 -Sum(1d2 2ca 99 eb) = 620 -Sum(19c 202 d3 26c) = 6dd -Sum(155 b2 3dc 13a) = 71d -Sum(101 265 3e5 332) = a7d -Sum(1ae 11 364 3af) = 8d2 -Sum(376 11c 2cd 337) = a96 -Sum(1b9 2c2 3d1 27b) = ac7 -Sum(295 291 127 1be) = 80b -Sum(2b1 1a7 3b8 332) = b42 -Sum(a9 1e1 2ef 2a6) = 81f -Sum(29b 101 2d0 178) = 7e4 -Sum(23d 1e3 3b5 2f0) = ac5 -Sum(3c8 39f 372 12a) = c03 -Sum(9a 20d 2ed b6) = 64a -Sum(3e5 14a 319 2c1) = b09 -Sum(219 23f 3be 2ab) = ac1 -Sum(372 21d 271 341) = b41 -Sum(294 b3 289 3b4) = 984 -Sum(109 bd 113 274) = 54d -Sum(233 154 f9 3be) = 83e -Sum(2f5 e0 29b 2de) = 94e -Sum(307 3e5 123 a4) = 8b3 -Sum(2a2 302 335 346) = c1f -Sum(2af 2e 8d 5c) = 3c6 -Sum(12b 25 1ee 20) = 35e -Sum(359 73 5b 315) = 73c -Sum(116 3a5 20 3c5) = 8a0 -Sum(f9 78 26b 291) = 66d -Sum(51 343 3b3 138) = 87f -Sum(38c e0 38b 374) = b6b -Sum(f9 1cd 9e 37a) = 6de -Sum(165 32a 105 db) = 66f -Sum(2f b7 149 20e) = 43d -Sum(34e d9 64 282) = 70d -Sum(85 36e 1c7 34a) = 904 -Sum(3c4 146 392 22a) = ac6 -Sum(96 a3 2f4 353) = 780 -Sum(62 33d b8 4b) = 4a2 -Sum(81 261 30e 18c) = 77c -Sum(3bc c9 1af 1d3) = 807 -Sum(29f 32c 395 42) = 9a2 -Sum(316 5d 32b 360) = 9fe -Sum(2df 131 b2 1d) = 4df -Sum(300 41 397 39b) = a73 -Sum(222 25b 2e3 c2) = 822 -Sum(35 19a 79 da) = 322 -Sum(3e7 fe 18c ec) = 75d -Sum(1b3 2bf 2ad 3a3) = ac2 -Sum(1be 3db ab 98) = 6dc -Sum(199 350 2fc 39) = 81e -Sum(160 37c 74 319) = 869 -Sum(28a 9a 132 40) = 496 -Sum(2b1 35a 352 160) = abd -Sum(1cd ed 19e 105) = 55d -Sum(2b3 2ba 42 217) = 7c6 -Sum(2b8 23e 1ad 1f) = 6c2 -Sum(26b f6 247 30c) = 8b4 -Sum(37e 2ff 201 2ba) = b38 -Sum(347 2aa 349 a1) = 9db -Sum(126 147 237 34d) = 7f1 -Sum(3b3 14b 223 240) = 961 -Sum(355 14c 1d6 d1) = 748 -Sum(23d 38d 1cc 2c6) = a5c -Sum(30d e4 168 30a) = 863 -Sum(1ce 54 ae 193) = 463 -Sum(23a 20d aa 399) = 88a -Sum(159 98 201 30b) = 6fd -Sum(18b fc 3c7 16c) = 7ba -Sum(83 dd 24c 16c) = 518 -Sum(1b 355 b2 11e) = 540 -Sum(308 175 cf 3d8) = 924 -Sum(133 323 394 262) = a4c -Sum(141 3f 2ba d1) = 50b -Sum(99 a3 112 1a6) = 3f4 -Sum(a1 271 e7 2bd) = 6b6 -Sum(333 2b2 32d 140) = a52 -Sum(3ae 5f 243 230) = 880 -Sum(280 3e3 2ee 261) = bb2 -Sum(389 377 273 c5) = a38 -Sum(13e ba 266 21d) = 67b -Sum(ec 386 1c9 14c) = 787 -Sum(22a 2f5 d2 3c7) = 9b8 -Sum(3b2 a5 cb 192) = 6b4 -Sum(26e 1dd 1d3 55) = 673 -Sum(22f 376 29 d3) = 6a1 -Sum(e0 163 3ca 146) = 753 -Sum(138 59 12f 109) = 3c9 -Sum(218 4d 67 3cf) = 69b -Sum(a 2b1 1d1 394) = 820 -Sum(e9 1e1 2fe b9) = 681 -Sum(374 263 265 1da) = a16 -Sum(318 15c 3db 1ac) = 9fb -Sum(1a3 21d 27d 1af) = 7ec -Sum(90 193 13d 370) = 6d0 -Sum(3a4 83 13 1d0) = 60a -Sum(1c5 3b1 1af 365) = a8a -Sum(3e e4 187 3a4) = 64d -Sum(b 226 354 4e) = 5d3 -Sum(78 358 146 264) = 77a -Sum(2e5 1ef 216 3c3) = aad -Sum(66 36b 34d 3e2) = b00 -Sum(1b7 2a0 33f 25f) = 9f5 -Sum(173 248 3d8 65) = 7f8 -Sum(1ce 12d 2db 3ab) = 981 -Sum(b3 2e7 19c 234) = 76a -Sum(3bd 36 1a0 365) = 8f8 -Sum(2b0 22f 136 11f) = 734 -Sum(4c 2c1 8a 1b4) = 54b -Sum(255 37 16a 242) = 638 -Sum(63 1b4 1ae 186) = 54b -Sum(18c 30f 231 1ab) = 877 -Sum(236 2ee 267 333) = abe -Sum(7d 182 1b1 3e6) = 796 -Sum(53 3ca 6 2f) = 452 -Sum(1ca 3d0 131 370) = a3b -Sum(169 21f 212 29a) = 834 -Sum(1bd 223 1f8 196) = 76e -Sum(3aa 99 348 2e6) = a71 -Sum(139 16 a7 ad) = 2a3 -Sum(1ec 3dd 12e 3a9) = aa0 -Sum(3af 1eb a2 2b2) = 8ee -Sum(110 8c 160 273) = 56f -Sum(31 13b 78 7a) = 25e -Sum(83 225 1c 355) = 619 -Sum(fd 37f 3d5 12) = 863 -Sum(f6 d1 371 3cb) = 903 -Sum(221 2b2 2ac 3d) = 7bc -Sum(322 340 22b 1d4) = a61 -Sum(2a2 16f 3a8 366) = b1f -Sum(17d 348 189 130) = 77e -Sum(c3 36c 165 26f) = 803 -Sum(21 2ce 263 2fb) = 84d -Sum(3e1 139 398 255) = b07 -Sum(7 11b 2b3 2de) = 6b3 -Sum(262 be 90 245) = 5f5 -Sum(1c8 123 338 d8) = 6fb -Sum(2f8 28b 1bf 2fb) = a3d -Sum(265 9e 1d7 19c) = 676 -Sum(1f5 2a4 337 171) = 941 -Sum(354 102 3aa 33b) = b3b -Sum(4e 398 43 1e8) = 611 -Sum(390 3bd 1cd 186) = aa0 -Sum(2a4 ca 104 be) = 530 -Sum(2b7 32c 350 388) = cbb -Sum(34 8c 35e 17d) = 59b -Sum(10e 350 96 343) = 837 -Sum(34a 1fb 5f 14a) = 6ee -Sum(1cc 141 371 15f) = 7dd -Sum(fb 13d 1f7 217) = 646 -Sum(df 14f 12f 322) = 67f -Sum(be 39 56 19a) = 2e7 -Sum(381 118 2cd 2b1) = a17 -Sum(33c 397 13d 2c7) = ad7 -Sum(18b 22d 22c e6) = 6ca -Sum(301 e8 286 195) = 804 -Sum(35e 82 3a1 163) = 8e4 -Sum(141 38c 3ab 179) = 9f1 -Sum(134 139 2a8 2cc) = 7e1 -Sum(345 3c bf d5) = 515 -Sum(2f9 2e4 3a9 231) = bb7 -Sum(315 196 1f6 21d) = 8be -Sum(3 a9 3bc 2b6) = 71e -Sum(2f9 110 2fd 3df) = ae5 -Sum(2b0 381 3ab 3b) = a17 -Sum(7f 2db 59 3dd) = 790 -Sum(155 153 17c 203) = 627 -Sum(ee 258 36a 14e) = 7fe -Sum(16d 157 1d4 1d) = 4b5 -Sum(126 55 267 1c8) = 5aa -Sum(273 183 15f 97) = 5ec -Sum(3b7 f4 268 23) = 736 -Sum(223 2b1 1b 3ab) = 89a -Sum(78 83 55 1e) = 16e -Sum(28b 3b4 db 1c2) = 8dc -Sum(19 3db 12f 192) = 6b5 -Sum(126 26d 149 1a2) = 67e -Sum(324 2e0 2b8 25b) = b17 -Sum(11a 1e3 1af 2e3) = 78f -Sum(17c 3a9 25 61) = 5ab -Sum(2d2 158 3c7 224) = a15 -Sum(23c 3a7 d6 e9) = 7a2 -Sum(101 1b6 179 15a) = 58a -Sum(361 a4 1af 280) = 834 -Sum(54 3b4 358 149) = 8a9 -Sum(393 c6 e7 32b) = 86b -Sum(3e1 1a8 290 27d) = a96 -Sum(2bb 159 28a 2ca) = 968 -Sum(38e 7a 2d8 2a8) = 988 -Sum(13 2f2 33d 167) = 7a9 -Sum(197 5f a0 16e) = 404 -Sum(166 15d 2e1 75) = 619 -Sum(34 317 28f 1af) = 789 -Sum(50 2c4 b6 20c) = 5d6 -Sum(71 9c 22b 94) = 3cc -Sum(116 240 216 38f) = 8fb -Sum(314 297 1b4 2e7) = a46 -Sum(1b4 2ee 26c 149) = 857 -Sum(76 1f1 399 333) = 933 -Sum(25 361 1c5 283) = 7ce -Sum(2af 21b 249 38c) = a9f -Sum(2b2 da 279 c9) = 6ce -Sum(3ab cb 237 35f) = a0c -Sum(c7 2a9 50 33f) = 6ff -Sum(231 f8 242 2b6) = 821 -Sum(391 3e1 2a8 12d) = b47 -Sum(aa 145 138 247) = 56e -Sum(312 16a 1e6 f8) = 75a -Sum(2be 390 1cd 2e3) = afe -Sum(3df 272 dd 239) = 967 -Sum(1e ea 323 e9) = 514 -Sum(b6 2f4 82 1b5) = 5e1 -Sum(1a0 5a 102 314) = 610 -Sum(11f 2f7 2d9 2b5) = 9a4 -Sum(35a 11b 2e1 16c) = 8c2 -Sum(389 31 19f 241) = 79a -Sum(2cc 13f a0 11f) = 5ca -Sum(dc 154 347 3a6) = 91d -Sum(1b 8 297 2a6) = 560 -Sum(bd 1bf 77 19e) = 491 -Sum(c3 36b a6 2b7) = 78b -Sum(67 2ba 3c0 3af) = a90 -Sum(b6 2ff 370 2c4) = 9e9 -Sum(378 359 297 3d5) = d3d -Sum(332 e2 204 35f) = 977 -Sum(253 34a d8 4c) = 6c1 -Sum(fb 143 2f9 116) = 64d -Sum(5f 32 1f7 19b) = 423 -Sum(a3 143 a8 1e) = 2ac -Sum(373 2d0 1d9 12) = 82e -Sum(166 d9 68 1c4) = 46b -Sum(1cb 83 2e a9) = 325 -Sum(158 2aa 35b 3c2) = b1f -Sum(3a3 ff 332 161) = 935 -Sum(292 5a 1bc 30b) = 7b3 -Sum(3a8 2aa 350 83) = a25 -Sum(107 1d5 220 235) = 731 -Sum(2b0 279 62 62) = 5ed -Sum(2f1 1f1 1f8 17a) = 854 -Sum(2e1 2fc 268 77) = 8bc -Sum(115 24f 3a5 19d) = 8a6 -Sum(be 21c 1df 38c) = 845 -Sum(1f8 32f 173 318) = 9b2 -Sum(113 2b5 52 2a4) = 6be -Sum(171 3cd bc 3de) = 9d8 -Sum(24a 1e0 1e0 2c7) = 8d1 -Sum(248 2f9 d1 1b1) = 7c3 -Sum(2ee 1b7 267 37e) = a8a -Sum(1da 144 1f6 158) = 66c -Sum(2ec 1b2 3ac 2d2) = b1c -Sum(3dd 27c 3d 369) = 9ff -Sum(105 131 337 3b) = 5a8 -Sum(19f 33c 9 2fc) = 7e0 -Sum(23f 272 215 31d) = 9e3 -Sum(6e dd 33d 1ad) = 635 -Sum(14b 2e 9d 40) = 256 -Sum(c8 1dd 13e 3b6) = 799 -Sum(bf 2e1 39f 3a6) = ae5 -Sum(20 1d2 0 3b6) = 5a8 -Sum(1a3 25f 1c1 116) = 6d9 -Sum(3aa 96 12 39a) = 7ec -Sum(1ae 352 13e 34f) = 98d -Sum(317 2c1 ea 20e) = 8d0 -Sum(25d 1fe 187 3af) = 991 -Sum(1de 3e4 2a1 310) = b73 -Sum(3da 368 363 2a6) = d4b -Sum(384 4a 2db 6d) = 716 -Sum(272 246 179 1f7) = 828 -Sum(303 226 1eb b4) = 7c8 -Sum(1b ea 27a 15d) = 4dc -Sum(9e 1d6 9f 1e6) = 4f9 -Sum(1f7 191 148 20a) = 6da -Sum(4a 38 1d7 1a7) = 400 -Sum(3a6 299 124 41) = 7a4 -Sum(11a 154 15 163) = 3e6 -Sum(77 d1 dd d0) = 2f5 -Sum(147 2db 2f2 37) = 74b -Sum(13 d3 394 f4) = 56e -Sum(16a 143 271 204) = 722 -Sum(2c9 17d af 1f3) = 6e8 -Sum(2ea d1 22 23a) = 617 -Sum(6f 3ba 238 6a) = 6cb -Sum(102 1aa 381 2af) = 8dc -Sum(d4 116 e6 3b5) = 685 -Sum(f9 3b6 9e 303) = 850 -Sum(286 111 1f6 35f) = 8ec -Sum(8b e3 3d 262) = 40d -Sum(27 308 160 385) = 814 -Sum(a4 20a 261 38d) = 89c -Sum(ae 28b 61 174) = 50e -Sum(a2 288 31d 251) = 898 -Sum(139 e4 1f 53) = 28f -Sum(5d 2a4 10a 2fa) = 705 -Sum(de 193 354 1d9) = 79e -Sum(2c2 ab 1ae 373) = 88e -Sum(37d 3bd 42 1e1) = 95d -Sum(49 3e4 20a 2b2) = 8e9 -Sum(29d 140 306 255) = 938 -Sum(354 311 261 3e6) = cac -Sum(1db 38d 225 38f) = b1c -Sum(c4 3d1 205 16b) = 805 -Sum(1fd 24d 2a6 2f9) = 9e9 -Sum(29d 88 cb 2) = 3f2 -Sum(dd d5 1df cd) = 45e -Sum(245 13b 207 13d) = 6c4 -Sum(2c2 154 19d 305) = 8b8 -Sum(385 29d 38d 1b4) = b63 -Sum(31a 356 3e5 31c) = d71 -Sum(26e 207 2af 393) = ab7 -Sum(1b8 2bf 3cc 1e3) = a26 -Sum(13d 2f9 14 215) = 65f -Sum(75 304 178 27f) = 770 -Sum(132 193 1a0 a0) = 505 -Sum(186 e8 cc 357) = 691 -Sum(234 2e7 1cd 348) = a30 -Sum(2a7 f1 3c7 32f) = a8e -Sum(8a 196 17c 2bd) = 659 -Sum(20d 233 2fd 36e) = aab -Sum(156 23 3e7 3b1) = 911 -Sum(140 370 290 81) = 7c1 -Sum(124 10e 82 186) = 43a -Sum(1b6 345 196 186) = 817 -Sum(13a 33f 15f 2b8) = 890 -Sum(f1 84 323 4c) = 4e4 -Sum(319 cd 18a 2c6) = 836 -Sum(188 2a2 71 2e6) = 781 -Sum(27e 219 331 162) = 92a -Sum(e8 103 217 2ab) = 6ad -Sum(57 14c 223 199) = 55f -Sum(6f 241 179 12d) = 556 -Sum(0 3dd 183 2b6) = 816 -Sum(d4 44 27d d2) = 467 -Sum(e8 39b 97 72) = 58c -Sum(1e5 3b1 2e1 269) = ae0 -Sum(9d 183 2ef 384) = 893 -Sum(14 33 9d 36) = 11a -Sum(128 29b 260 38d) = 9b0 -Sum(249 18a 3c4 d8) = 86f -Sum(33c 5d 2c6 192) = 7f1 -Sum(11f 1a3 211 239) = 70c -Sum(383 25f 7a 135) = 791 -Sum(6c 1a4 c6 8d) = 363 -Sum(253 3a1 fc 2d3) = 9c3 -Sum(339 1dc 118 3b4) = 9e1 -Sum(33f 14c 18d 221) = 839 -Sum(1cc 2d8 270 345) = a59 -Sum(3be 3a7 11e 187) = a0a -Sum(eb 14e 14a 2e1) = 664 -Sum(359 b5 0 19d) = 5ab -Sum(3a1 350 3b2 36b) = e0e -Sum(1d4 1d3 139 290) = 770 -Sum(8e f1 186 160) = 465 -Sum(32d 2f9 23a 3ba) = c1a -Sum(15 3d8 6d ab) = 505 -Sum(3d6 11d 15f ac) = 6fe -Sum(86 327 38a 104) = 83b -Sum(256 202 361 284) = a3d -Sum(1db 363 13 3d7) = 928 -Sum(181 20a 4c 3e0) = 7b7 -Sum(19e 3c8 2b7 2cd) = aea -Sum(221 3a6 242 1d9) = 9e2 -Sum(2fb 177 2cc 369) = aa7 -Sum(47 33d 1af 284) = 7b7 -Sum(1f0 306 2e4 295) = a6f -Sum(230 35 39 274) = 512 -Sum(177 2e9 36c 3b1) = b7d -Sum(2e6 24b 239 1f2) = 95c -Sum(38c 74 2f4 d) = 701 -Sum(24e 27a 273 c7) = 802 -Sum(e4 1b0 1f0 de) = 562 -Sum(31d 3e0 23c 285) = bbe -Sum(1b4 177 1e6 263) = 774 -Sum(30d 19e 1b0 169) = 7c4 -Sum(38f 42 155 31f) = 845 -Sum(39 278 2da b0) = 63b -Sum(326 d9 3c5 3ba) = b7e -Sum(48 1a1 36 35e) = 57d -Sum(92 21d 1b1 7f) = 4df -Sum(22c 342 33e 17f) = a2b -Sum(ad e7 1fd 16f) = 500 -Sum(64 3bd 2cd 1ed) = 8db -Sum(2ac 18b 66 20b) = 6a8 -Sum(124 33c 8e 253) = 741 -Sum(2bd 140 1a0 243) = 7e0 -Sum(1b 269 23c 12) = 4d2 -Sum(141 270 1d0 f0) = 671 -Sum(3d5 130 324 4c) = 875 -Sum(eb 3be 272 33a) = a55 -Sum(7b 16b 4c 351) = 583 -Sum(1f4 1d7 10e 2a5) = 77e -Sum(13b 212 239 13e) = 6c4 -Sum(18e 345 1a9 ec) = 768 -Sum(1a2 fa 164 22b) = 62b -Sum(292 1ea 2d 1dc) = 685 -Sum(1ec 1dc 200 17c) = 744 -Sum(24f 19 11f 14e) = 4d5 -Sum(ca 13b 1d8 152) = 52f -Sum(f4 236 4f 22c) = 5a5 -Sum(15b 37f 2bf 316) = aaf -Sum(36 92 349 8f) = 4a0 -Sum(36e 238 292 3d8) = c10 -Sum(25a 46 a8 279) = 5c1 -Sum(13d 258 164 17) = 510 -Sum(3be 379 55 122) = 8ae -Sum(f2 eb 74 53) = 2a4 -Sum(16d 57 3df 353) = 8f6 -Sum(ab 31f cf 2fa) = 793 -Sum(313 ff 1a5 15a) = 711 -Sum(27b 171 1f5 16) = 5f7 -Sum(16f 12f 2dc 83) = 5fd -Sum(93 346 14 284) = 671 -Sum(d2 38 255 1ca) = 529 -Sum(329 19d 223 332) = a1b -Sum(1d 2f6 309 316) = 932 -Sum(240 326 32d 322) = bb5 -Sum(35f 195 34e 3e) = 880 -Sum(b2 9 183 24c) = 48a -Sum(183 3f 39a 3ab) = 907 -Sum(1f7 3d9 3a8 20e) = b86 -Sum(43 15b 281 72) = 491 -Sum(ca 2ed 3b8 175) = 8e4 -Sum(2c3 25b 13d 36d) = 9c8 -Sum(24f 2c7 1bd 230) = 903 -Sum(1e9 3a1 10b 2e5) = 97a -Sum(3e0 3bd 1d1 c8) = a36 -Sum(29c 8 46 2ea) = 5d4 -Sum(1ea 25f 13b 2b) = 5af -Sum(335 fb 2d3 e4) = 7e7 -Sum(a4 2ba 257 72) = 627 -Sum(1c5 1d8 163 23e) = 73e -Sum(2b4 1fe 3e5 2a4) = b3b -Sum(269 16a 1c2 23d) = 7d2 -Sum(2ce c3 215 6d) = 613 -Sum(371 1b9 1bd c1) = 7a8 -Sum(2a7 ce 373 63) = 74b -Sum(120 111 37 154) = 3bc -Sum(3db d9 18b 316) = 955 -Sum(149 98 11f 208) = 508 -Sum(25a 37b 1fb 9e) = 86e -Sum(b 189 80 80) = 294 -Sum(9d 2e5 145 272) = 739 -Sum(2cd 265 15a 2d3) = 95f -Sum(fd 196 1b3 f7) = 53d -Sum(24c ff 11e 1a3) = 60c -Sum(1c7 291 2b1 190) = 899 -Sum(3c6 293 f3 235) = 981 -Sum(38b c 1fb 3de) = 970 -Sum(3a5 11f b6 11d) = 697 -Sum(4d 2f2 3ad 222) = 90e -Sum(3b3 1ac 26e 176) = 943 -Sum(366 2a0 219 39b) = bba -Sum(2cd 28 3a3 15a) = 7f2 -Sum(25c 57 2be 161) = 6d2 -Sum(2c0 2d8 a2 1f1) = 82b -Sum(1f1 2f8 2cf 2c1) = a79 -Sum(69 164 315 3c5) = 8a7 -Sum(df 95 a9 341) = 55e -Sum(139 2e3 1d8 1a) = 60e -Sum(23 13a 212 125) = 494 -Sum(7a 140 350 362) = 86c -Sum(50 a0 181 34d) = 5be -Sum(249 33c 245 15d) = 927 -Sum(250 1bb 31 bb) = 4f7 -Sum(2c4 395 10c 202) = 967 -Sum(372 23c 2bb 237) = aa0 -Sum(367 61 21 361) = 74a -Sum(267 2a3 13e 185) = 7cd -Sum(c5 364 177 b1) = 651 -Sum(38f 355 270 3b1) = d05 -Sum(3ca 3b0 17c 18b) = a81 -Sum(238 334 f1 365) = 9c2 -Sum(2b2 11c 13d 75) = 580 -Sum(2d 10 19b 225) = 3fd -Sum(26c 119 3e3 3d6) = b3e -Sum(7f 208 e6 38d) = 6fa -Sum(1fd bf 97 2d3) = 626 -Sum(44 367 113 357) = 815 -Sum(125 143 21f 3aa) = 831 -Sum(c0 130 3e6 33) = 609 -Sum(358 a3 af 14a) = 5f4 -Sum(165 c8 158 b5) = 43a -Sum(1c9 33a 22c 276) = 9a5 -Sum(35a 281 331 1c1) = acd -Sum(2c9 1d6 375 178) = 98c -Sum(29c 205 321 332) = af4 -Sum(8 1ce 338 2d1) = 7df -Sum(1d8 1d8 165 13c) = 651 -Sum(11a 3b0 7e 3c4) = 90c -Sum(159 383 227 135) = 838 -Sum(34f 3c9 10a f2) = 914 -Sum(13b 285 3e5 28b) = a30 -Sum(1fa 5e e0 2d1) = 609 -Sum(1a2 26e 17e 153) = 6e1 -Sum(169 165 141 37f) = 78e -Sum(177 26e 2c5 3d9) = a83 -Sum(1d4 ef 15b 392) = 7b0 -Sum(202 355 68 32e) = 8ed -Sum(396 240 210 2bb) = aa1 -Sum(fb 22f 1f6 16b) = 68b -Sum(1d0 ac d7 179) = 4cc -Sum(f7 27b 381 293) = 986 -Sum(231 312 25a 3db) = b78 -Sum(241 290 3a5 e1) = 957 -Sum(103 118 305 4b) = 56b -Sum(12a 53 89 28d) = 493 -Sum(3dd 19a 322 34a) = be3 -Sum(5e 2aa 58 322) = 682 -Sum(356 3be 1ae ca) = 98c -Sum(196 14b 2e1 340) = 902 -Sum(155 2af 160 2d7) = 83b -Sum(fa 74 ec 106) = 360 -Sum(282 12e 1b7 3ca) = 931 -Sum(312 1ab 40 17d) = 67a -Sum(23 231 184 345) = 71d -Sum(340 37 235 36f) = 91b -Sum(2ba 320 85 26b) = 8ca -Sum(e1 2da 332 39f) = a8c -Sum(21f 3ac 2dd 262) = b0a -Sum(2db 199 192 368) = 96e -Sum(ad 21c c8 317) = 6a8 -Sum(f3 76 10 1ab) = 324 -Sum(37d 31 a7 34c) = 7a1 -Sum(3af 384 3e7 11e) = c38 -Sum(7a 1d9 1d 1c2) = 432 -Sum(3d7 269 24 38f) = 9f3 -Sum(1e4 1ff 1aa 34d) = 8da -Sum(2b5 1c6 28a 272) = 977 -Sum(9f 203 297 124) = 65d -Sum(357 306 27b 244) = b1c -Sum(30f 2c1 2d5 b0) = 955 -Sum(233 3cd 3d8 283) = c5b -Sum(3ad 1bf 1e8 102) = 856 -Sum(17b 3b8 378 22b) = ad6 -Sum(1f8 120 132 2db) = 725 -Sum(2b3 ab 2dd 1b7) = 7f2 -Sum(18e 21c 2ad 1ab) = 802 -Sum(ec 2c3 1af 15b) = 6b9 -Sum(345 1c0 29a 115) = 8b4 -Sum(37c 35b 15c 1fe) = a31 -Sum(20f 30f 387 f6) = 99b -Sum(3c6 188 f6 ce) = 712 -Sum(273 6b 314 236) = 828 -Sum(1d3 388 379 264) = b38 -Sum(3cc 11e 30a 17) = 80b -Sum(120 22c 196 ea) = 5cc -Sum(f 8c 1a4 3a3) = 5e2 -Sum(11d 263 297 284) = 89b -Sum(3ac 31c cc 38b) = b1f -Sum(10b 1e7 1d8 6) = 4d0 -Sum(125 1de 320 80) = 6a3 -Sum(396 360 36f 3a4) = e09 -Sum(22f 35 131 25e) = 5f3 -Sum(51 145 de 3ca) = 63e -Sum(179 29d e6 339) = 835 -Sum(3cb 13b 398 10) = 8ae -Sum(1bc 90 272 29f) = 75d -Sum(2bc 3d5 159 1e4) = 9ce -Sum(32d 303 2d9 329) = c32 -Sum(2f4 115 13b 97) = 5db -Sum(104 1e7 174 e8) = 547 -Sum(131 39 232 267) = 603 -Sum(23e e9 212 255) = 78e -Sum(101 286 22 3d2) = 77b -Sum(22d 310 2ac 306) = aef -Sum(ed d0 e8 241) = 4e6 -Sum(2c4 335 318 136) = a47 -Sum(253 19d d5 63) = 528 -Sum(162 4e 234 1e3) = 5c7 -Sum(3af 2b0 2c6 182) = aa7 -Sum(245 b 61 1d2) = 483 -Sum(1c3 2ec e3 a7) = 639 -Sum(2e0 2ef fb 3cc) = a96 -Sum(251 21c 392 34) = 833 -Sum(ff 31a 20c 1e4) = 809 -Sum(108 fc 13 a0) = 2b7 -Sum(13 1af f2 b2) = 366 -Sum(51 227 22 365) = 5ff -Sum(49 129 c1 e1) = 314 -Sum(6e 2fd 7d 3bf) = 7a7 -Sum(252 271 23d 34c) = a4c -Sum(a1 f1 1fa 32e) = 6ba -Sum(a4 200 e4 94) = 41c -Sum(a4 e2 79 148) = 347 -Sum(3e0 3a8 1bf 14) = 95b -Sum(1f9 30 55 b9) = 337 -Sum(2cc 17e 382 127) = 8f3 -Sum(15b 2b0 3ad 1e9) = 9a1 -Sum(29d 234 37a 27f) = aca -Sum(2a7 af 3e7 30a) = a47 -Sum(39c 1ac e 239) = 78f -Sum(143 376 2c0 34e) = ac7 -Sum(b0 360 e9 ae) = 5a7 -Sum(13e 69 1c5 221) = 58d -Sum(21c 184 7f 30b) = 72a -Sum(156 d3 1d2 1db) = 5d6 -Sum(23a 2e2 1df 258) = 953 -Sum(150 1e2 289 330) = 8eb -Sum(3a9 29c 5f 4d) = 6f1 -Sum(21 203 35e 33e) = 8c0 -Sum(2a5 34 76 1e3) = 532 -Sum(1c7 2ff aa 233) = 7a3 -Sum(6e 158 63 122) = 34b -Sum(2f db 1b3 2c9) = 586 -Sum(3c2 21b 3e7 248) = c0c -Sum(196 68 1fe 2b5) = 6b1 -Sum(d6 1a1 ed 289) = 5ed -Sum(a 178 1ff 18) = 399 -Sum(20e 128 65 122) = 4bd -Sum(5c 3d7 13 323) = 769 -Sum(238 394 1e9 3e0) = b95 -Sum(17e 167 300 2ce) = 8b3 -Sum(e9 17d 73 1ee) = 4c7 -Sum(204 115 1a7 23d) = 6fd -Sum(21e 1a8 10c 26) = 4f8 -Sum(212 208 228 2dd) = 91f -Sum(341 4b 2fc 2ef) = 977 -Sum(75 91 26d 3d2) = 745 -Sum(207 c4 10 1ea) = 4c5 -Sum(266 205 137 357) = 8f9 -Sum(288 207 1a0 274) = 8a3 -Sum(105 11 1fb f8) = 409 -Sum(369 210 2ec 29b) = b00 -Sum(2ca 3d5 111 2f) = 7df -Sum(16b 2f3 9f 133) = 630 -Sum(252 11 258 57) = 512 -Sum(2f8 88 3ce 140) = 88e -Sum(105 3a5 3bf 18) = 881 -Sum(71 19d 185 a) = 39d -Sum(67 39b 1d8 32f) = 909 -Sum(237 204 36e 24d) = 9f6 -Sum(18c 3b9 107 219) = 865 -Sum(39a 3c9 7e 35a) = b3b -Sum(166 4 105 276) = 4e5 -Sum(396 149 364 7d) = 8c0 -Sum(2a bb 3da 3ab) = 86a -Sum(238 2d4 229 12d) = 862 -Sum(24f 106 3af 1b7) = 8bb -Sum(11b 157 2e3 4d) = 5a2 -Sum(8b b 2b2 326) = 66e -Sum(368 1b5 18d 124) = 7ce -Sum(326 65 299 6d) = 691 -Sum(98 2f6 5c 306) = 6f0 -Sum(37 15a 305 17c) = 612 -Sum(212 3aa 14 2a0) = 870 -Sum(190 3c2 3b1 294) = b97 -Sum(19 2af 23e f) = 515 -Sum(108 b7 1eb 38b) = 735 -Sum(27e 3e fc 25d) = 615 -Sum(315 2b5 84 1d3) = 821 -Sum(3b 1bc 95 340) = 5cc -Sum(aa a8 180 3a8) = 67a -Sum(239 167 223 c8) = 68b -Sum(3de 12 196 38e) = 914 -Sum(1eb 2e0 10a 77) = 64c -Sum(20e 289 34d 3c8) = bac -Sum(10b b9 88 3e0) = 62c -Sum(5e a8 24e 344) = 698 -Sum(325 229 265 3bb) = b6e -Sum(32d 70 1ea 359) = 8e0 -Sum(12e 27d 1be 388) = 8f1 -Sum(27c 122 394 26b) = 99d -Sum(290 156 71 34) = 48b -Sum(1aa d 3ad 199) = 6fd -Sum(256 b4 235 3d8) = 917 -Sum(1f 187 34a 190) = 680 -Sum(301 2c6 18b 1b5) = 907 -Sum(195 1c2 60 17a) = 531 -Sum(f6 199 330 378) = 937 -Sum(cb 3ad 14c 3d0) = 994 -Sum(10c 205 150 2f0) = 751 -Sum(36 219 199 317) = 6ff -Sum(264 cb 164 278) = 70b -Sum(223 1f9 339 38e) = ae3 -Sum(31b 2d 86 39d) = 76b -Sum(37f 1de d6 cd) = 700 -Sum(f5 2df 326 11) = 70b -Sum(c5 34 ce 291) = 458 -Sum(34f 24c 2cc 202) = a69 -Sum(37 5a 159 e8) = 2d2 -Sum(12c 25b 339 ca) = 78a -Sum(5d 1cf 33e 2e4) = 84e -Sum(247 389 5c 9d) = 6c9 -Sum(326 196 51 316) = 823 -Sum(12f 83 1fa 13f) = 4eb -Sum(2db 188 252 22f) = 8e4 -Sum(3e4 2bb 311 3be) = d6e -Sum(315 137 357 2c1) = a64 -Sum(2bb 386 271 123) = 9d5 -Sum(28d c 6f 30d) = 615 -Sum(388 41 1b1 123) = 69d -Sum(3bf 30 352 1b9) = 8fa -Sum(1fb 39e d3 39b) = a07 -Sum(247 1a 1f4 38b) = 7e0 -Sum(8 10a cd 18a) = 369 -Sum(2a6 368 3b7 110) = ad5 -Sum(31 183 1ca 388) = 706 -Sum(30d 315 88 122) = 7cc -Sum(13 2d4 19e 299) = 71e -Sum(3be 12e 257 3b4) = af7 -Sum(350 158 1c3 35f) = 9ca -Sum(64 3a3 79 2a1) = 721 -Sum(191 2cd 11b 183) = 6fc -Sum(326 2cd 67 20) = 67a -Sum(71 a1 f1 23c) = 43f -Sum(24e 3b2 293 2cd) = b60 -Sum(315 398 388 35b) = d90 -Sum(263 342 3b5 2c1) = c1b -Sum(29a 101 221 14e) = 70a -Sum(1a5 58 ab 76) = 31e -Sum(109 310 1f6 40) = 64f -Sum(1fa 2ed 1e4 12e) = 7f9 -Sum(26d 17e 227 31c) = 92e -Sum(3d 1b 165 385) = 542 -Sum(2ac 2dd 49 b3) = 685 -Sum(1b 105 3d 236) = 393 -Sum(2ff 241 2d0 225) = a35 -Sum(ab 300 263 19) = 627 -Sum(3dd 7b 132 325) = 8af -Sum(1bc 252 d3 28) = 509 -Sum(1b4 25a ee e5) = 5e1 -Sum(11b 98 284 27f) = 6b6 -Sum(383 284 289 2af) = b3f -Sum(e 3e0 5a 3) = 44b -Sum(2d3 3a7 128 3a1) = b43 -Sum(1c 3a6 82 354) = 798 -Sum(303 20e 327 3a9) = be1 -Sum(2d0 227 1c9 59) = 719 -Sum(20a 21 2e7 1a9) = 6bb -Sum(17 340 11b 267) = 6d9 -Sum(3a4 51 28a 20f) = 88e -Sum(77 39b b 3e5) = 802 -Sum(20 12e 52 1fc) = 39c -Sum(12a 119 b 1f5) = 443 -Sum(1d7 181 1ca 2f4) = 816 -Sum(159 2c2 397 15c) = 90e -Sum(bb 22f a4 3d4) = 762 -Sum(f8 299 3d4 37b) = ae0 -Sum(28b 2a8 3e5 3b3) = ccb -Sum(11c 25b 2dd 34f) = 9a3 -Sum(202 329 389 1a1) = a55 -Sum(1d5 3b4 317 f0) = 990 -Sum(16 f1 3b9 27e) = 73e -Sum(175 20e 10e e1) = 572 -Sum(a2 12d 154 22f) = 552 -Sum(2f7 1f 2ec 16e) = 770 -Sum(142 bf 358 269) = 7c2 -Sum(11c 40 d9 363) = 598 -Sum(28c 263 f2 217) = 7f8 -Sum(321 1f5 36f ee) = 973 -Sum(300 39b 3be 7) = a60 -Sum(2c8 156 205 7d) = 6a0 -Sum(19a 303 310 3d6) = b83 -Sum(107 ae 58 1e3) = 3f0 -Sum(3c 21a 5f 115) = 3ca -Sum(5b ae 3bc 3bd) = 882 -Sum(18 55 3d9 2fa) = 740 -Sum(11c 2df 1a7 294) = 836 -Sum(1a6 65 316 343) = 864 -Sum(48 2e5 28e 3d4) = 98f -Sum(2a1 2db ce 200) = 84a -Sum(117 eb 25c 1de) = 63c -Sum(20a 2f6 1da 106) = 7e0 -Sum(196 259 93 252) = 6d4 -Sum(3c7 0 3d6 272) = a0f -Sum(164 2f2 348 2a3) = a41 -Sum(15b 7b 22f a2) = 4a7 -Sum(17f fc 19a 13e) = 553 -Sum(374 72 25c 167) = 7a9 -Sum(3c7 1bb 2a6 314) = b3c -Sum(1e1 1fb ef 20d) = 6d8 -Sum(104 a0 fe 335) = 5d7 -Sum(3ba c4 363 48) = 829 -Sum(2f6 1c 26f 397) = 918 -Sum(37a 213 a4 9b) = 6cc -Sum(2a3 1b9 5e 10d) = 5c7 -Sum(15b 9b 17a 123) = 493 -Sum(3e5 313 272 20b) = b75 -Sum(142 c2 90 350) = 5e4 -Sum(202 13b 19e bb) = 596 -Sum(365 2e2 d0 ba) = 7d1 -Sum(181 152 230 358) = 85b -Sum(1ea 2ac 1d5 1b3) = 81e -Sum(116 20a 71 27) = 3b8 -Sum(19d 3ca b1 69) = 681 -Sum(30e 3c3 338 a6) = aaf -Sum(239 157 2ed 234) = 8b1 -Sum(373 307 194 23f) = a4d -Sum(75 3d1 11e 3a9) = 90d -Sum(e9 38b 374 9) = 7f1 -Sum(253 20a 320 138) = 8b5 -Sum(13d bf f5 320) = 611 -Sum(50 69 1b3 220) = 48c diff --git a/Samples/Summator/process-large-file.txt b/Samples/Summator/process-large-file.txt deleted file mode 100644 index 360c53010..000000000 --- a/Samples/Summator/process-large-file.txt +++ /dev/null @@ -1,1000 +0,0 @@ -34f 21f 2d9 355 -318 2a9 2a4 161 -2e3 82 1a6 231 -162 2c7 36f 3c2 -9d d4 20c 3af -9a 3ab 2df 163 -170 3e7 1 2f9 -3cc 185 30f 16a -390 8b 127 1a2 -8 f9 36b 194 -68 2cb 1c5 1a1 -7b 35 69 321 -e5 178 24b 26b -8f 235 145 38c -327 176 1e4 319 -2a8 393 17c 15e -15a 3b 288 d7 -1a0 1cc 3b9 38b -151 78 346 1b7 -1e6 13d e7 13a -37d c8 214 253 -322 a4 da 15a -32d 182 262 27a -2fc 52 2d4 bd -14e 67 47 3f -194 37a 180 300 -1a7 2a7 11a 17 -2d7 376 1a6 1ea -8e a9 383 1df -17f 311 223 b6 -348 12e 191 1d -352 13e 8f ff -18c 289 68 17f -f6 a8 38d 3 -32f 2fb ac 7a -371 c3 26b 39f -3c4 77 1ec 252 -b7 f4 325 225 -22b 378 df 66 -1e8 163 395 68 -218 1a 272 4d -30d 27 f3 3cd -103 37f 298 26b -264 1a8 12c 284 -2ab 1da 41 2da -33a 224 48 98 -34b 22b 33b 166 -3b1 327 203 1cc -34e 43 24a 356 -239 21d 92 39d -36c 206 14f 11f -243 ba 3a8 82 -24c 242 1a3 e -121 ba 116 3dc -3ba 1da ea e1 -d3 375 1c 1c4 -17c cc 304 220 -346 1bd 4b 8d -148 8b 356 133 -d5 310 23d 290 -26a 2b8 301 16f -184 1f4 aa 294 -315 256 384 5f -132 398 2c9 3df -1ea 372 38b 176 -19d 187 124 8e -1f6 115 2ae 96 -14b 3cb 307 aa -384 241 25e 202 -73 295 40 36f -22 70 2f3 ef -221 3d4 343 197 -1a9 35f 331 14a -36c c6 f2 e3 -193 272 360 6e -32e 14 148 331 -337 2d9 c2 2d8 -139 c7 36b 33c -332 1c 154 12d -27c 35 255 209 -1ba 116 2d4 1d4 -9a 1bc 125 2be -14f 17b 6e 1e8 -31c 3b9 40 d -30f 3a9 1a0 32f -312 6a 5d e2 -3db 3dc 1ff 350 -359 3ad 24f 15e -1d 157 29c 217 -128 35c 297 17a -1b3 1eb 189 3a6 -58 182 20 361 -3c3 146 206 2ba -27c 30d 2df 14f -b9 1f1 1e0 a6 -3a5 1bd 34d 18a -314 2f7 1ca 1c5 -11c 211 2cd 39b -112 25 17c 126 -2c9 9f 8a 258 -3bc 285 375 212 -1a6 37c 2ca f8 -25f 14d 15e 31e -17b 1f1 1bc 3de -286 3e7 91 148 -39f 242 244 1b1 -261 253 1a8 256 -191 1b8 26e 19 -167 36f d1 120 -22f 13a 3cb 37f -2d d2 34d 3b7 -b3 397 21f 308 -251 185 288 2ca -d 329 2a1 179 -40 1c3 399 33b -1d4 73 34f a6 -2c7 144 1b6 1f3 -11f 9c 1ae 14b -1b f9 11a 2cd -2dc 275 fe 2b2 -33 287 5 b -318 38b c 225 -ce 33f df 6c -189 8f 1ab 3a2 -16b 2a7 f0 357 -1e0 2c 1e8 1b4 -3bf 13c 2ae 2fb -20f 3bb 285 1b3 -c4 e7 308 334 -6d 343 176 323 -137 379 124 1c7 -3a5 141 3aa 373 -336 160 161 35 -184 18f d1 6 -9f 239 34 1db -dd f8 16 fb -ba 314 38f a2 -16f 25f 334 20f -130 1b5 21f 24f -2ef 20 1f7 27d -17a 322 3ad af -259 24f 2e 31d -15 301 295 21c -30f 382 245 3e -362 29 10d 91 -19a 308 204 d6 -213 33a 2ec 3b7 -358 26a ff 7e -41 19e 61 1a2 -133 34f 289 252 -2e5 72 8c 255 -3b2 118 2e8 196 -357 1e0 2d5 2c5 -2fb 166 e2 25a -11e 2d6 283 307 -34a 5f 3ad 193 -202 216 1db 170 -320 a2 2f3 12 -188 2a2 193 128 -27b 396 10c fa -52 156 5f 177 -37d 221 16b 2c3 -bc 1ef 84 244 -cb 369 2f0 285 -ac 2e1 53 19b -325 177 25d 2b4 -2d4 2c2 13d 58 -3ba 3a2 3c6 16f -1ca 189 2b5 1f4 -3e7 235 1fd 3c8 -ab 2ff b4 131 -1e ae 32e 207 -39a 2f5 7b 381 -342 358 1bd 2c2 -2e1 192 28a ba -e3 112 2f0 101 -210 2fd 7a 32a -b3 fc 286 25f -e4 72 f0 25 -c2 12c 296 226 -37b 31e a6 c4 -327 45 214 25a -3b3 3db 2fa 254 -392 142 2e5 380 -f 39b 308 108 -34 4 153 29e -6f 203 29d 2cf -267 198 1ff 1b5 -2e4 64 127 1ad -6b 22b 1c1 1cb -e8 208 35e c0 -1e9 31 2b5 389 -175 157 16 10f -20b 18d 1f2 377 -1cb 1f0 55 123 -18f 32c 3da 21b -1db 145 358 32d -1b 137 315 1cb -1d9 13d 20 213 -8d 92 b4 3d -336 2fc 15a 10f -181 ef 391 156 -25a 197 3ca 263 -1d6 9e 3b 1a3 -215 143 2cc a3 -c1 36f 36d 3e -22a 232 31e ce -16d 3c6 2e9 138 -13b 17 1d4 27b -1d2 2ca 99 eb -19c 202 d3 26c -155 b2 3dc 13a -101 265 3e5 332 -1ae 11 364 3af -376 11c 2cd 337 -1b9 2c2 3d1 27b -295 291 127 1be -2b1 1a7 3b8 332 -a9 1e1 2ef 2a6 -29b 101 2d0 178 -23d 1e3 3b5 2f0 -3c8 39f 372 12a -9a 20d 2ed b6 -3e5 14a 319 2c1 -219 23f 3be 2ab -372 21d 271 341 -294 b3 289 3b4 -109 bd 113 274 -233 154 f9 3be -2f5 e0 29b 2de -307 3e5 123 a4 -2a2 302 335 346 -2af 2e 8d 5c -12b 25 1ee 20 -359 73 5b 315 -116 3a5 20 3c5 -f9 78 26b 291 -51 343 3b3 138 -38c e0 38b 374 -f9 1cd 9e 37a -165 32a 105 db -2f b7 149 20e -34e d9 64 282 -85 36e 1c7 34a -3c4 146 392 22a -96 a3 2f4 353 -62 33d b8 4b -81 261 30e 18c -3bc c9 1af 1d3 -29f 32c 395 42 -316 5d 32b 360 -2df 131 b2 1d -300 41 397 39b -222 25b 2e3 c2 -35 19a 79 da -3e7 fe 18c ec -1b3 2bf 2ad 3a3 -1be 3db ab 98 -199 350 2fc 39 -160 37c 74 319 -28a 9a 132 40 -2b1 35a 352 160 -1cd ed 19e 105 -2b3 2ba 42 217 -2b8 23e 1ad 1f -26b f6 247 30c -37e 2ff 201 2ba -347 2aa 349 a1 -126 147 237 34d -3b3 14b 223 240 -355 14c 1d6 d1 -23d 38d 1cc 2c6 -30d e4 168 30a -1ce 54 ae 193 -23a 20d aa 399 -159 98 201 30b -18b fc 3c7 16c -83 dd 24c 16c -1b 355 b2 11e -308 175 cf 3d8 -133 323 394 262 -141 3f 2ba d1 -99 a3 112 1a6 -a1 271 e7 2bd -333 2b2 32d 140 -3ae 5f 243 230 -280 3e3 2ee 261 -389 377 273 c5 -13e ba 266 21d -ec 386 1c9 14c -22a 2f5 d2 3c7 -3b2 a5 cb 192 -26e 1dd 1d3 55 -22f 376 29 d3 -e0 163 3ca 146 -138 59 12f 109 -218 4d 67 3cf -a 2b1 1d1 394 -e9 1e1 2fe b9 -374 263 265 1da -318 15c 3db 1ac -1a3 21d 27d 1af -90 193 13d 370 -3a4 83 13 1d0 -1c5 3b1 1af 365 -3e e4 187 3a4 -b 226 354 4e -78 358 146 264 -2e5 1ef 216 3c3 -66 36b 34d 3e2 -1b7 2a0 33f 25f -173 248 3d8 65 -1ce 12d 2db 3ab -b3 2e7 19c 234 -3bd 36 1a0 365 -2b0 22f 136 11f -4c 2c1 8a 1b4 -255 37 16a 242 -63 1b4 1ae 186 -18c 30f 231 1ab -236 2ee 267 333 -7d 182 1b1 3e6 -53 3ca 6 2f -1ca 3d0 131 370 -169 21f 212 29a -1bd 223 1f8 196 -3aa 99 348 2e6 -139 16 a7 ad -1ec 3dd 12e 3a9 -3af 1eb a2 2b2 -110 8c 160 273 -31 13b 78 7a -83 225 1c 355 -fd 37f 3d5 12 -f6 d1 371 3cb -221 2b2 2ac 3d -322 340 22b 1d4 -2a2 16f 3a8 366 -17d 348 189 130 -c3 36c 165 26f -21 2ce 263 2fb -3e1 139 398 255 -7 11b 2b3 2de -262 be 90 245 -1c8 123 338 d8 -2f8 28b 1bf 2fb -265 9e 1d7 19c -1f5 2a4 337 171 -354 102 3aa 33b -4e 398 43 1e8 -390 3bd 1cd 186 -2a4 ca 104 be -2b7 32c 350 388 -34 8c 35e 17d -10e 350 96 343 -34a 1fb 5f 14a -1cc 141 371 15f -fb 13d 1f7 217 -df 14f 12f 322 -be 39 56 19a -381 118 2cd 2b1 -33c 397 13d 2c7 -18b 22d 22c e6 -301 e8 286 195 -35e 82 3a1 163 -141 38c 3ab 179 -134 139 2a8 2cc -345 3c bf d5 -2f9 2e4 3a9 231 -315 196 1f6 21d -3 a9 3bc 2b6 -2f9 110 2fd 3df -2b0 381 3ab 3b -7f 2db 59 3dd -155 153 17c 203 -ee 258 36a 14e -16d 157 1d4 1d -126 55 267 1c8 -273 183 15f 97 -3b7 f4 268 23 -223 2b1 1b 3ab -78 83 55 1e -28b 3b4 db 1c2 -19 3db 12f 192 -126 26d 149 1a2 -324 2e0 2b8 25b -11a 1e3 1af 2e3 -17c 3a9 25 61 -2d2 158 3c7 224 -23c 3a7 d6 e9 -101 1b6 179 15a -361 a4 1af 280 -54 3b4 358 149 -393 c6 e7 32b -3e1 1a8 290 27d -2bb 159 28a 2ca -38e 7a 2d8 2a8 -13 2f2 33d 167 -197 5f a0 16e -166 15d 2e1 75 -34 317 28f 1af -50 2c4 b6 20c -71 9c 22b 94 -116 240 216 38f -314 297 1b4 2e7 -1b4 2ee 26c 149 -76 1f1 399 333 -25 361 1c5 283 -2af 21b 249 38c -2b2 da 279 c9 -3ab cb 237 35f -c7 2a9 50 33f -231 f8 242 2b6 -391 3e1 2a8 12d -aa 145 138 247 -312 16a 1e6 f8 -2be 390 1cd 2e3 -3df 272 dd 239 -1e ea 323 e9 -b6 2f4 82 1b5 -1a0 5a 102 314 -11f 2f7 2d9 2b5 -35a 11b 2e1 16c -389 31 19f 241 -2cc 13f a0 11f -dc 154 347 3a6 -1b 8 297 2a6 -bd 1bf 77 19e -c3 36b a6 2b7 -67 2ba 3c0 3af -b6 2ff 370 2c4 -378 359 297 3d5 -332 e2 204 35f -253 34a d8 4c -fb 143 2f9 116 -5f 32 1f7 19b -a3 143 a8 1e -373 2d0 1d9 12 -166 d9 68 1c4 -1cb 83 2e a9 -158 2aa 35b 3c2 -3a3 ff 332 161 -292 5a 1bc 30b -3a8 2aa 350 83 -107 1d5 220 235 -2b0 279 62 62 -2f1 1f1 1f8 17a -2e1 2fc 268 77 -115 24f 3a5 19d -be 21c 1df 38c -1f8 32f 173 318 -113 2b5 52 2a4 -171 3cd bc 3de -24a 1e0 1e0 2c7 -248 2f9 d1 1b1 -2ee 1b7 267 37e -1da 144 1f6 158 -2ec 1b2 3ac 2d2 -3dd 27c 3d 369 -105 131 337 3b -19f 33c 9 2fc -23f 272 215 31d -6e dd 33d 1ad -14b 2e 9d 40 -c8 1dd 13e 3b6 -bf 2e1 39f 3a6 -20 1d2 0 3b6 -1a3 25f 1c1 116 -3aa 96 12 39a -1ae 352 13e 34f -317 2c1 ea 20e -25d 1fe 187 3af -1de 3e4 2a1 310 -3da 368 363 2a6 -384 4a 2db 6d -272 246 179 1f7 -303 226 1eb b4 -1b ea 27a 15d -9e 1d6 9f 1e6 -1f7 191 148 20a -4a 38 1d7 1a7 -3a6 299 124 41 -11a 154 15 163 -77 d1 dd d0 -147 2db 2f2 37 -13 d3 394 f4 -16a 143 271 204 -2c9 17d af 1f3 -2ea d1 22 23a -6f 3ba 238 6a -102 1aa 381 2af -d4 116 e6 3b5 -f9 3b6 9e 303 -286 111 1f6 35f -8b e3 3d 262 -27 308 160 385 -a4 20a 261 38d -ae 28b 61 174 -a2 288 31d 251 -139 e4 1f 53 -5d 2a4 10a 2fa -de 193 354 1d9 -2c2 ab 1ae 373 -37d 3bd 42 1e1 -49 3e4 20a 2b2 -29d 140 306 255 -354 311 261 3e6 -1db 38d 225 38f -c4 3d1 205 16b -1fd 24d 2a6 2f9 -29d 88 cb 2 -dd d5 1df cd -245 13b 207 13d -2c2 154 19d 305 -385 29d 38d 1b4 -31a 356 3e5 31c -26e 207 2af 393 -1b8 2bf 3cc 1e3 -13d 2f9 14 215 -75 304 178 27f -132 193 1a0 a0 -186 e8 cc 357 -234 2e7 1cd 348 -2a7 f1 3c7 32f -8a 196 17c 2bd -20d 233 2fd 36e -156 23 3e7 3b1 -140 370 290 81 -124 10e 82 186 -1b6 345 196 186 -13a 33f 15f 2b8 -f1 84 323 4c -319 cd 18a 2c6 -188 2a2 71 2e6 -27e 219 331 162 -e8 103 217 2ab -57 14c 223 199 -6f 241 179 12d -0 3dd 183 2b6 -d4 44 27d d2 -e8 39b 97 72 -1e5 3b1 2e1 269 -9d 183 2ef 384 -14 33 9d 36 -128 29b 260 38d -249 18a 3c4 d8 -33c 5d 2c6 192 -11f 1a3 211 239 -383 25f 7a 135 -6c 1a4 c6 8d -253 3a1 fc 2d3 -339 1dc 118 3b4 -33f 14c 18d 221 -1cc 2d8 270 345 -3be 3a7 11e 187 -eb 14e 14a 2e1 -359 b5 0 19d -3a1 350 3b2 36b -1d4 1d3 139 290 -8e f1 186 160 -32d 2f9 23a 3ba -15 3d8 6d ab -3d6 11d 15f ac -86 327 38a 104 -256 202 361 284 -1db 363 13 3d7 -181 20a 4c 3e0 -19e 3c8 2b7 2cd -221 3a6 242 1d9 -2fb 177 2cc 369 -47 33d 1af 284 -1f0 306 2e4 295 -230 35 39 274 -177 2e9 36c 3b1 -2e6 24b 239 1f2 -38c 74 2f4 d -24e 27a 273 c7 -e4 1b0 1f0 de -31d 3e0 23c 285 -1b4 177 1e6 263 -30d 19e 1b0 169 -38f 42 155 31f -39 278 2da b0 -326 d9 3c5 3ba -48 1a1 36 35e -92 21d 1b1 7f -22c 342 33e 17f -ad e7 1fd 16f -64 3bd 2cd 1ed -2ac 18b 66 20b -124 33c 8e 253 -2bd 140 1a0 243 -1b 269 23c 12 -141 270 1d0 f0 -3d5 130 324 4c -eb 3be 272 33a -7b 16b 4c 351 -1f4 1d7 10e 2a5 -13b 212 239 13e -18e 345 1a9 ec -1a2 fa 164 22b -292 1ea 2d 1dc -1ec 1dc 200 17c -24f 19 11f 14e -ca 13b 1d8 152 -f4 236 4f 22c -15b 37f 2bf 316 -36 92 349 8f -36e 238 292 3d8 -25a 46 a8 279 -13d 258 164 17 -3be 379 55 122 -f2 eb 74 53 -16d 57 3df 353 -ab 31f cf 2fa -313 ff 1a5 15a -27b 171 1f5 16 -16f 12f 2dc 83 -93 346 14 284 -d2 38 255 1ca -329 19d 223 332 -1d 2f6 309 316 -240 326 32d 322 -35f 195 34e 3e -b2 9 183 24c -183 3f 39a 3ab -1f7 3d9 3a8 20e -43 15b 281 72 -ca 2ed 3b8 175 -2c3 25b 13d 36d -24f 2c7 1bd 230 -1e9 3a1 10b 2e5 -3e0 3bd 1d1 c8 -29c 8 46 2ea -1ea 25f 13b 2b -335 fb 2d3 e4 -a4 2ba 257 72 -1c5 1d8 163 23e -2b4 1fe 3e5 2a4 -269 16a 1c2 23d -2ce c3 215 6d -371 1b9 1bd c1 -2a7 ce 373 63 -120 111 37 154 -3db d9 18b 316 -149 98 11f 208 -25a 37b 1fb 9e -b 189 80 80 -9d 2e5 145 272 -2cd 265 15a 2d3 -fd 196 1b3 f7 -24c ff 11e 1a3 -1c7 291 2b1 190 -3c6 293 f3 235 -38b c 1fb 3de -3a5 11f b6 11d -4d 2f2 3ad 222 -3b3 1ac 26e 176 -366 2a0 219 39b -2cd 28 3a3 15a -25c 57 2be 161 -2c0 2d8 a2 1f1 -1f1 2f8 2cf 2c1 -69 164 315 3c5 -df 95 a9 341 -139 2e3 1d8 1a -23 13a 212 125 -7a 140 350 362 -50 a0 181 34d -249 33c 245 15d -250 1bb 31 bb -2c4 395 10c 202 -372 23c 2bb 237 -367 61 21 361 -267 2a3 13e 185 -c5 364 177 b1 -38f 355 270 3b1 -3ca 3b0 17c 18b -238 334 f1 365 -2b2 11c 13d 75 -2d 10 19b 225 -26c 119 3e3 3d6 -7f 208 e6 38d -1fd bf 97 2d3 -44 367 113 357 -125 143 21f 3aa -c0 130 3e6 33 -358 a3 af 14a -165 c8 158 b5 -1c9 33a 22c 276 -35a 281 331 1c1 -2c9 1d6 375 178 -29c 205 321 332 -8 1ce 338 2d1 -1d8 1d8 165 13c -11a 3b0 7e 3c4 -159 383 227 135 -34f 3c9 10a f2 -13b 285 3e5 28b -1fa 5e e0 2d1 -1a2 26e 17e 153 -169 165 141 37f -177 26e 2c5 3d9 -1d4 ef 15b 392 -202 355 68 32e -396 240 210 2bb -fb 22f 1f6 16b -1d0 ac d7 179 -f7 27b 381 293 -231 312 25a 3db -241 290 3a5 e1 -103 118 305 4b -12a 53 89 28d -3dd 19a 322 34a -5e 2aa 58 322 -356 3be 1ae ca -196 14b 2e1 340 -155 2af 160 2d7 -fa 74 ec 106 -282 12e 1b7 3ca -312 1ab 40 17d -23 231 184 345 -340 37 235 36f -2ba 320 85 26b -e1 2da 332 39f -21f 3ac 2dd 262 -2db 199 192 368 -ad 21c c8 317 -f3 76 10 1ab -37d 31 a7 34c -3af 384 3e7 11e -7a 1d9 1d 1c2 -3d7 269 24 38f -1e4 1ff 1aa 34d -2b5 1c6 28a 272 -9f 203 297 124 -357 306 27b 244 -30f 2c1 2d5 b0 -233 3cd 3d8 283 -3ad 1bf 1e8 102 -17b 3b8 378 22b -1f8 120 132 2db -2b3 ab 2dd 1b7 -18e 21c 2ad 1ab -ec 2c3 1af 15b -345 1c0 29a 115 -37c 35b 15c 1fe -20f 30f 387 f6 -3c6 188 f6 ce -273 6b 314 236 -1d3 388 379 264 -3cc 11e 30a 17 -120 22c 196 ea -f 8c 1a4 3a3 -11d 263 297 284 -3ac 31c cc 38b -10b 1e7 1d8 6 -125 1de 320 80 -396 360 36f 3a4 -22f 35 131 25e -51 145 de 3ca -179 29d e6 339 -3cb 13b 398 10 -1bc 90 272 29f -2bc 3d5 159 1e4 -32d 303 2d9 329 -2f4 115 13b 97 -104 1e7 174 e8 -131 39 232 267 -23e e9 212 255 -101 286 22 3d2 -22d 310 2ac 306 -ed d0 e8 241 -2c4 335 318 136 -253 19d d5 63 -162 4e 234 1e3 -3af 2b0 2c6 182 -245 b 61 1d2 -1c3 2ec e3 a7 -2e0 2ef fb 3cc -251 21c 392 34 -ff 31a 20c 1e4 -108 fc 13 a0 -13 1af f2 b2 -51 227 22 365 -49 129 c1 e1 -6e 2fd 7d 3bf -252 271 23d 34c -a1 f1 1fa 32e -a4 200 e4 94 -a4 e2 79 148 -3e0 3a8 1bf 14 -1f9 30 55 b9 -2cc 17e 382 127 -15b 2b0 3ad 1e9 -29d 234 37a 27f -2a7 af 3e7 30a -39c 1ac e 239 -143 376 2c0 34e -b0 360 e9 ae -13e 69 1c5 221 -21c 184 7f 30b -156 d3 1d2 1db -23a 2e2 1df 258 -150 1e2 289 330 -3a9 29c 5f 4d -21 203 35e 33e -2a5 34 76 1e3 -1c7 2ff aa 233 -6e 158 63 122 -2f db 1b3 2c9 -3c2 21b 3e7 248 -196 68 1fe 2b5 -d6 1a1 ed 289 -a 178 1ff 18 -20e 128 65 122 -5c 3d7 13 323 -238 394 1e9 3e0 -17e 167 300 2ce -e9 17d 73 1ee -204 115 1a7 23d -21e 1a8 10c 26 -212 208 228 2dd -341 4b 2fc 2ef -75 91 26d 3d2 -207 c4 10 1ea -266 205 137 357 -288 207 1a0 274 -105 11 1fb f8 -369 210 2ec 29b -2ca 3d5 111 2f -16b 2f3 9f 133 -252 11 258 57 -2f8 88 3ce 140 -105 3a5 3bf 18 -71 19d 185 a -67 39b 1d8 32f -237 204 36e 24d -18c 3b9 107 219 -39a 3c9 7e 35a -166 4 105 276 -396 149 364 7d -2a bb 3da 3ab -238 2d4 229 12d -24f 106 3af 1b7 -11b 157 2e3 4d -8b b 2b2 326 -368 1b5 18d 124 -326 65 299 6d -98 2f6 5c 306 -37 15a 305 17c -212 3aa 14 2a0 -190 3c2 3b1 294 -19 2af 23e f -108 b7 1eb 38b -27e 3e fc 25d -315 2b5 84 1d3 -3b 1bc 95 340 -aa a8 180 3a8 -239 167 223 c8 -3de 12 196 38e -1eb 2e0 10a 77 -20e 289 34d 3c8 -10b b9 88 3e0 -5e a8 24e 344 -325 229 265 3bb -32d 70 1ea 359 -12e 27d 1be 388 -27c 122 394 26b -290 156 71 34 -1aa d 3ad 199 -256 b4 235 3d8 -1f 187 34a 190 -301 2c6 18b 1b5 -195 1c2 60 17a -f6 199 330 378 -cb 3ad 14c 3d0 -10c 205 150 2f0 -36 219 199 317 -264 cb 164 278 -223 1f9 339 38e -31b 2d 86 39d -37f 1de d6 cd -f5 2df 326 11 -c5 34 ce 291 -34f 24c 2cc 202 -37 5a 159 e8 -12c 25b 339 ca -5d 1cf 33e 2e4 -247 389 5c 9d -326 196 51 316 -12f 83 1fa 13f -2db 188 252 22f -3e4 2bb 311 3be -315 137 357 2c1 -2bb 386 271 123 -28d c 6f 30d -388 41 1b1 123 -3bf 30 352 1b9 -1fb 39e d3 39b -247 1a 1f4 38b -8 10a cd 18a -2a6 368 3b7 110 -31 183 1ca 388 -30d 315 88 122 -13 2d4 19e 299 -3be 12e 257 3b4 -350 158 1c3 35f -64 3a3 79 2a1 -191 2cd 11b 183 -326 2cd 67 20 -71 a1 f1 23c -24e 3b2 293 2cd -315 398 388 35b -263 342 3b5 2c1 -29a 101 221 14e -1a5 58 ab 76 -109 310 1f6 40 -1fa 2ed 1e4 12e -26d 17e 227 31c -3d 1b 165 385 -2ac 2dd 49 b3 -1b 105 3d 236 -2ff 241 2d0 225 -ab 300 263 19 -3dd 7b 132 325 -1bc 252 d3 28 -1b4 25a ee e5 -11b 98 284 27f -383 284 289 2af -e 3e0 5a 3 -2d3 3a7 128 3a1 -1c 3a6 82 354 -303 20e 327 3a9 -2d0 227 1c9 59 -20a 21 2e7 1a9 -17 340 11b 267 -3a4 51 28a 20f -77 39b b 3e5 -20 12e 52 1fc -12a 119 b 1f5 -1d7 181 1ca 2f4 -159 2c2 397 15c -bb 22f a4 3d4 -f8 299 3d4 37b -28b 2a8 3e5 3b3 -11c 25b 2dd 34f -202 329 389 1a1 -1d5 3b4 317 f0 -16 f1 3b9 27e -175 20e 10e e1 -a2 12d 154 22f -2f7 1f 2ec 16e -142 bf 358 269 -11c 40 d9 363 -28c 263 f2 217 -321 1f5 36f ee -300 39b 3be 7 -2c8 156 205 7d -19a 303 310 3d6 -107 ae 58 1e3 -3c 21a 5f 115 -5b ae 3bc 3bd -18 55 3d9 2fa -11c 2df 1a7 294 -1a6 65 316 343 -48 2e5 28e 3d4 -2a1 2db ce 200 -117 eb 25c 1de -20a 2f6 1da 106 -196 259 93 252 -3c7 0 3d6 272 -164 2f2 348 2a3 -15b 7b 22f a2 -17f fc 19a 13e -374 72 25c 167 -3c7 1bb 2a6 314 -1e1 1fb ef 20d -104 a0 fe 335 -3ba c4 363 48 -2f6 1c 26f 397 -37a 213 a4 9b -2a3 1b9 5e 10d -15b 9b 17a 123 -3e5 313 272 20b -142 c2 90 350 -202 13b 19e bb -365 2e2 d0 ba -181 152 230 358 -1ea 2ac 1d5 1b3 -116 20a 71 27 -19d 3ca b1 69 -30e 3c3 338 a6 -239 157 2ed 234 -373 307 194 23f -75 3d1 11e 3a9 -e9 38b 374 9 -253 20a 320 138 -13d bf f5 320 -50 69 1b3 220 diff --git a/Samples/random_cloud_10.png b/Samples/random_cloud_10.png new file mode 100644 index 000000000..d64e255f0 Binary files /dev/null and b/Samples/random_cloud_10.png differ diff --git a/Samples/random_cloud_100.png b/Samples/random_cloud_100.png new file mode 100644 index 000000000..ed9f9cc3e Binary files /dev/null and b/Samples/random_cloud_100.png differ diff --git a/Samples/random_cloud_50.png b/Samples/random_cloud_50.png new file mode 100644 index 000000000..bc9dcc2a1 Binary files /dev/null and b/Samples/random_cloud_50.png differ diff --git a/TagsCloudContainerCLI/CLI/CliHandler.cs b/TagsCloudContainerCLI/CLI/CliHandler.cs new file mode 100644 index 000000000..a6d32c475 --- /dev/null +++ b/TagsCloudContainerCLI/CLI/CliHandler.cs @@ -0,0 +1,47 @@ +using System.Text.RegularExpressions; +using TagsCloudContainerCore.Models; +using TagsCloudContainerCore.TextProcessor; + +namespace TagsCloudContainerCLI.CLI; + +public partial class CliHandler +{ + public static Result ValidateOptions(CliOptions opts) + { + if (!Enum.TryParse(opts.SortOrder, true, out _)) + { + return Result.Fail($"Invalid sort order: {opts.SortOrder}. Must be one of: {string.Join(", ", Enum.GetNames())}"); + } + + foreach (var part in opts.ExcludedPartsOfSpeech.Split(",")) + { + if (!Enum.TryParse(part, true, out _)) + { + return Result.Fail($"Invalid part of speech: {part}. Must be one of: {string.Join(", ", Enum.GetNames())}"); + } + } + + if (opts.RenderScale <= 0) + { + return Result.Fail($"Render scale must be positive, got: {opts.RenderScale}"); + } + + if (opts.MaxWords <= 0) + { + return Result.Fail($"Max words must be positive, got: {opts.MaxWords}"); + } + + if (opts.MinFontSize <= 0 || opts.MaxFontSize <= 0 || opts.MinFontSize > opts.MaxFontSize) + { + return Result.Fail($"Font sizes must be positive and min size must be less or equal to max size, got: {opts.MinFontSize}, {opts.MaxFontSize}"); + } + + if (opts.LayoutSpacing < 0) + { + return Result.Fail($"Layout spacing must be non-negative, got: {opts.LayoutSpacing}"); + } + + return Result.Ok(); + } + +} \ No newline at end of file diff --git a/TagsCloudContainerCLI/CLI/CliOptions.cs b/TagsCloudContainerCLI/CLI/CliOptions.cs new file mode 100644 index 000000000..a2bd25d5b --- /dev/null +++ b/TagsCloudContainerCLI/CLI/CliOptions.cs @@ -0,0 +1,51 @@ +using CommandLine; + +namespace TagsCloudContainerCLI.CLI; + +public class CliOptions +{ + [Option('d', "demo", Required = false, HelpText = "Run the application in demo mode.")] + public bool Demo { get; set; } + + [Option('f', "file", Required = false, HelpText = "Specify the file path.")] + public string File { get; set; } + + [Option('o', "output", Required = false, HelpText = "Specify the output path.")] + public string? Output { get; set; } + + [Option("font", Required = false, Default = "Arial", HelpText = "Specify the font family.")] + public string FontFamily { get; set; } + + [Option("scale", Required = false, Default = 1, HelpText = "Specify the render scale.")] + public float RenderScale { get; set; } + + [Option("max-words", Required = false, Default = 100, HelpText = "Maximum number of words to include.")] + public int MaxWords { get; set; } + + [Option("min-font", Required = false, Default = 12f, HelpText = "Minimum font size.")] + public float MinFontSize { get; set; } + + [Option("max-font", Required = false, Default = 48f, HelpText = "Maximum font size.")] + public float MaxFontSize { get; set; } + + [Option("spacing", Required = false, Default = 0.1f, HelpText = "Space between words.")] + public float LayoutSpacing { get; set; } + + [Option("radius", Required = false, Default = 0, HelpText = "Initial radius from center.")] + public double InitialRadius { get; set; } + + [Option("excluded-words", Required = false, Default = "", HelpText = "Words to exclude.")] + public string ExcludedWords { get; set; } + + [Option("excluded-parts", Required = false, Default = "PART,ADV,PR,CONJ,ANUM,APRO,SPRO,NUM", HelpText = "Parts of speech to exclude.")] + public string ExcludedPartsOfSpeech { get; set; } + + [Option("sort", Required = false, Default = "Descending", HelpText = "Sort order (Ascending/Descending/Random).")] + public string SortOrder { get; set; } + + [Option("background", Required = false, Default = "#000000", HelpText = "Background color in HEX.")] + public string BackgroundColor { get; set; } + + [Option("foreground", Required = false, Default = "#FFFFFF", HelpText = "Foreground color in HEX.")] + public string ForegroundColor { get; set; } +} \ No newline at end of file diff --git a/TagsCloudContainerCLI/CLI/TagCloudConfig.cs b/TagsCloudContainerCLI/CLI/TagCloudConfig.cs new file mode 100644 index 000000000..cd044afc8 --- /dev/null +++ b/TagsCloudContainerCLI/CLI/TagCloudConfig.cs @@ -0,0 +1,17 @@ +namespace TagsCloudContainerCLI.CLI; + +public record TagCloudConfig +{ + public string FontFamily { get; set; } + public float RenderScale { get; set; } + public int MaxWords { get; set; } + public float MinFontSize { get; set; } + public float MaxFontSize { get; set; } + public float LayoutSpacing { get; set; } + public double InitialRadius { get; set; } + public string[] ExcludedWords { get; set; } + public string[] ExcludedPartsOfSpeech { get; set; } + public string SortOrder { get; set; } + public string BackgroundColor { get; set; } + public string ForegroundColor { get; set; } +} \ No newline at end of file diff --git a/TagsCloudContainerCLI/Demo.cs b/TagsCloudContainerCLI/Demo.cs new file mode 100644 index 000000000..07a74d0f2 --- /dev/null +++ b/TagsCloudContainerCLI/Demo.cs @@ -0,0 +1,89 @@ +using System.Text; +using Microsoft.Extensions.Logging; +using TagsCloudContainerCore.DataProvider; +using TagsCloudContainerCore.Facade; +using TagsCloudContainerCore.FontManager; +using TagsCloudContainerCore.ImageEncoders; +using TagsCloudContainerCore.Layouter; +using TagsCloudContainerCore.Models.Graphics; +using TagsCloudContainerCore.Renderer; +using TagsCloudContainerCore.TextProcessor; + +namespace TagsCloudContainerCLI; + +public class Demo +{ + private ITagCloudFactory _cloudFactory; + private ILogger _logger; + + public Demo(ITagCloudFactory cloudFactory, ILogger logger) + { + _cloudFactory = cloudFactory; + _logger = logger; + } + + public Result Generate() + { + _logger.LogInformation("Generating random clouds"); + Directory.CreateDirectory("results"); + + return GenerateRandomCloud(10) + .Then(_ => GenerateRandomCloud(50)) + .Then(_ => GenerateRandomCloud(100)); + } + + private Result GenerateRandomCloud(int count) + { + _logger.LogInformation("Generating cloud with {Count} words", count); + return _cloudFactory.Create(builder => builder + .UseDataProvider() + .UseWordProcessor( + new MyStemTextProcessorConfig + { + ExcludedWords = ["тест"], + ExcludedPartsOfSpeech = [] + }) + .UseFontManager( + new FontManagerConfig + { + Font = new Font("Arial") + } + ) + .UseLayouter( + new CircularCloudLayouterConfig + { + SpiralStep = 0.5, + InitialRadius = 100 + }) + .UseRenderer(new RendererConfig + { + BackgroundColor = new Color(200, 200, 255), + RenderingScale = 5, + TextColor = new Color(0, 0, 100) + } + ) + .UseImageEncoder()) + .Then(r => r.FromString(GenerateRandomString(count))) + .Then(imageBytes => File.WriteAllBytes($"results/random_cloud_{count}.png", imageBytes)); + } + + private static string GenerateRandomString(int count) + { + var randomString = + ("Lorem ipsum — классический текст-«рыба» (условный, зачастую бессмысленный текст-заполнитель, вставляемый в макет страницы, используемый для образца шрифта и текста, поля размещения на странице). " + + "Используется для демонстрации элементов графики в документах или презентациях, без отвлечения внимания на содержимое. " + + "Слова «Lorem ipsum» берут начало от латинского слова и начинаются с «dolorem ipsum», что означает «боль сама по себе». " + + "Текст не имеет смысла, но по своей структуре напоминает настоящий текст. " + + "Слова и предложения в «Lorem ipsum» обычно не повторяются, что делает его более правдоподобным. ") + .Split(); + var random = new Random(); + var sb = new StringBuilder(); + for (var i = 0; i < count - 1; i++) + { + sb.Append(randomString[random.Next(randomString.Length)]); + sb.Append(' '); + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/TagsCloudContainerCLI/FileMode.cs b/TagsCloudContainerCLI/FileMode.cs new file mode 100644 index 000000000..e377a820a --- /dev/null +++ b/TagsCloudContainerCLI/FileMode.cs @@ -0,0 +1,134 @@ +using Microsoft.Extensions.Logging; +using TagsCloudContainerCLI.CLI; +using TagsCloudContainerCore.DataProvider; +using TagsCloudContainerCore.Facade; +using TagsCloudContainerCore.FontManager; +using TagsCloudContainerCore.ImageEncoders; +using TagsCloudContainerCore.Layouter; +using TagsCloudContainerCore.Models; +using TagsCloudContainerCore.Models.Graphics; +using TagsCloudContainerCore.Renderer; +using TagsCloudContainerCore.TextProcessor; + + +namespace TagsCloudContainerCLI; + +public class FileMode +{ + private readonly ILogger _logger; + private readonly ITagCloudFactory _cloudFactory; + private readonly TagCloudConfig _config; + + public FileMode(ILogger logger, ITagCloudFactory cloudFactory, TagCloudConfig config) + { + _logger = logger; + _cloudFactory = cloudFactory; + _config = config; + } + + public Result Generate(string filePath, string outputPath) + { + _logger.LogInformation("Generating tag cloud to {Path}", outputPath); + + return _cloudFactory.Create(builder => builder + .GuessDataProvider(Path.GetExtension(filePath)) + .UseWordProcessor(new MyStemTextProcessorConfig + { + MaxWordsCount = _config.MaxWords, + ExcludedWords = _config.ExcludedWords, + ExcludedPartsOfSpeech = _config.ExcludedPartsOfSpeech + .Select(pos => + { + if (Enum.TryParse(pos, true, out var parsedPos)) + { + return parsedPos; + } + + _logger.LogWarning("Invalid PartOfSpeech value: {Value}", pos); + return (PartOfSpeech?)null; + }) + .Where(pos => pos.HasValue) + .Select(pos => pos!.Value) + .ToArray(), + SortOrder = Enum.Parse(_config.SortOrder, true) + }) + .UseFontManager(new FontManagerConfig + { + Font = new Font(_config.FontFamily) + }) + .UseLayouter(new CircularCloudLayouterConfig + { + MaxFontSize = _config.MaxFontSize, + MinFontSize = _config.MinFontSize, + SpiralStep = _config.LayoutSpacing, + InitialRadius = _config.InitialRadius + }) + .UseRenderer(new RendererConfig + { + TagFont = new Font(_config.FontFamily), + RenderingScale = _config.RenderScale, + BackgroundColor = new Color(_config.BackgroundColor), + TextColor = new Color(_config.ForegroundColor) + }) + .GuessEncoder(Path.GetExtension(outputPath))) + .Then(r => r.FromFile(filePath) + .RefineError("Failed to generate tag cloud")) + .Then(imageBytes => SaveTo(outputPath, imageBytes)); + } + + private Result SaveTo(string outputPath, byte[] imageBytes) + { + var directory = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(directory)) + { + try + { + Directory.CreateDirectory(directory); + } + catch (Exception ex) + { + return Result.Fail($"Failed to create directory: {ex.Message}"); + } + } + + var fullpath = Path.GetFullPath(outputPath); + _logger.LogInformation("Saving tag cloud to {Path}", fullpath); + try + { + File.WriteAllBytes(outputPath, imageBytes); + } + catch (Exception e) + { + return Result.Fail($"Failed to save image: {e.Message}"); + } + + return Result.Ok(); + } +} + +public static class BuilderExtensions +{ + public static TagCloudBuilder GuessDataProvider(this TagCloudBuilder b, string ext) + { + return ext switch + { + ".docx" => b.UseDataProvider(), + ".doc" => b.UseDataProvider(), + ".ppt" => b.UseDataProvider(), + ".pptx" => b.UseDataProvider(), + ".txt" => b.UseDataProvider(), + _ => b.UseDataProvider(), + }; + } + + public static TagCloudBuilder GuessEncoder(this TagCloudBuilder b, string ext) + { + return ext switch + { + ".png" => b.UseImageEncoder(), + ".jpeg" => b.UseImageEncoder(), + ".jpg" => b.UseImageEncoder(), + _ => b.UseImageEncoder(), + }; + } +} \ No newline at end of file diff --git a/TagsCloudContainerCLI/Program.cs b/TagsCloudContainerCLI/Program.cs new file mode 100644 index 000000000..39e6fef95 --- /dev/null +++ b/TagsCloudContainerCLI/Program.cs @@ -0,0 +1,122 @@ +using Autofac; +using Autofac.Extensions.DependencyInjection; +using CommandLine; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Extensions.Autofac.DependencyInjection; +using TagsCloudContainerCLI.CLI; +using TagsCloudContainerCore.Facade; + +namespace TagsCloudContainerCLI; + +internal class Program +{ + private static ParserResult? _parseResult; + + private static void Main(string[] args) + { + _parseResult = Parser.Default.ParseArguments(args); + + if (_parseResult.Tag == ParserResultType.NotParsed) + { + Environment.Exit(1); + } + + Result.Of(() => BuildHost(args)) + .Then(host => + { + using var scope = host.Services.CreateScope(); + return ProcessArguments(scope.ServiceProvider); + }) + .OnFail(error => + { + Log.Fatal($"Application error: {error}"); + Environment.Exit(1); + }); + } + + private static IHost BuildHost(string[] args) => + Host.CreateDefaultBuilder(args) + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureServices(ConfigureServices) + .ConfigureContainer(ConfigureContainer) + .Build(); + + private static void ConfigureServices(HostBuilderContext _, IServiceCollection services) + { + services.AddOptions(); + services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog()); + } + + private static void ConfigureContainer(ContainerBuilder builder) + { + ConfigureSerilog(builder); + ConfigureTagCloud(builder); + builder.RegisterInstance(CreateTagCloudConfig(_parseResult!.Value)).As(); + } + + private static void ConfigureSerilog(ContainerBuilder builder) + { + builder.RegisterSerilog(new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console() + .WriteTo.File("logs/log-.log", rollingInterval: RollingInterval.Day)); + } + + private static void ConfigureTagCloud(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + + builder.RegisterType().AsSelf(); + builder.RegisterType().AsSelf().SingleInstance(); + } + + private static Result ProcessArguments(IServiceProvider services) + { + return _parseResult! + .MapResult( + opts => HandleValidOptions(opts, services), + errors => Result.Fail($"Invalid command line arguments: {errors}") + ); + } + + private static TagCloudConfig CreateTagCloudConfig(CliOptions options) => + new() + { + FontFamily = options.FontFamily, + RenderScale = options.RenderScale, + MaxWords = options.MaxWords, + MinFontSize = options.MinFontSize, + MaxFontSize = options.MaxFontSize, + LayoutSpacing = options.LayoutSpacing, + InitialRadius = options.InitialRadius, + ExcludedWords = options.ExcludedWords.Split(","), + ExcludedPartsOfSpeech = options.ExcludedPartsOfSpeech.Split(","), + SortOrder = options.SortOrder, + ForegroundColor = options.ForegroundColor, + BackgroundColor = options.BackgroundColor, + }; + + + private static Result HandleValidOptions(CliOptions opts, IServiceProvider services) + { + return CliHandler.ValidateOptions(opts) + .Then(_ => + { + if (opts.Demo) + { + var demo = services.GetRequiredService(); + return demo.Generate(); + } + + if (string.IsNullOrEmpty(opts.File)) + return Result.Fail("No file specified"); + var fileMode = services.GetRequiredService(); + var outputPath = opts.Output ?? $"{opts.File}.png"; + return fileMode.Generate(opts.File, outputPath); + }); + } +} \ No newline at end of file diff --git a/TagsCloudContainerCLI/TagsCloudContainerCLI.csproj b/TagsCloudContainerCLI/TagsCloudContainerCLI.csproj new file mode 100644 index 000000000..84feca64c --- /dev/null +++ b/TagsCloudContainerCLI/TagsCloudContainerCLI.csproj @@ -0,0 +1,23 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/TagsCloudContainerCore/DataProvider/FileDataProvider.cs b/TagsCloudContainerCore/DataProvider/FileDataProvider.cs new file mode 100644 index 000000000..419cd4578 --- /dev/null +++ b/TagsCloudContainerCore/DataProvider/FileDataProvider.cs @@ -0,0 +1,22 @@ +using System.Text; +using Microsoft.Extensions.Logging; + +namespace TagsCloudContainerCore.DataProvider; + +public class FileDataProvider : IDataProvider +{ + private readonly ILogger _logger; + + public FileDataProvider(ILogger logger) + { + _logger = logger; + } + + public Result GetData(byte[] data) + { + _logger.LogInformation("Reading data with FileDataProvider"); + var text = Encoding.UTF8.GetString(data); + _logger.LogInformation("Read {n} characters from file", data.Length); + return text; + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/DataProvider/IDataProvider.cs b/TagsCloudContainerCore/DataProvider/IDataProvider.cs new file mode 100644 index 000000000..79fe08085 --- /dev/null +++ b/TagsCloudContainerCore/DataProvider/IDataProvider.cs @@ -0,0 +1,11 @@ +namespace TagsCloudContainerCore.DataProvider; + +public interface IDataProvider : IDataProvider +{ + TConfig Config { get; init; } +} + +public interface IDataProvider +{ + Result GetData(byte[] data); +} \ No newline at end of file diff --git a/TagsCloudContainerCore/DataProvider/OpenXmlDocumentsProvider.cs b/TagsCloudContainerCore/DataProvider/OpenXmlDocumentsProvider.cs new file mode 100644 index 000000000..0457f78cc --- /dev/null +++ b/TagsCloudContainerCore/DataProvider/OpenXmlDocumentsProvider.cs @@ -0,0 +1,36 @@ +using DocumentFormat.OpenXml.Packaging; +using Microsoft.Extensions.Logging; + +namespace TagsCloudContainerCore.DataProvider; + +public class OpenXmlDocumentsProvider : IDataProvider +{ + private readonly ILogger _logger; + + public OpenXmlDocumentsProvider(ILogger logger) + { + _logger = logger; + } + + public Result GetData(byte[] data) + { + _logger.LogInformation("Reading data with OpenXmlDocumentsProvider"); + using var stream = new MemoryStream(data); + + try + { + using var doc = WordprocessingDocument.Open(stream, false); + if (doc.MainDocumentPart?.Document.Body != null) + { + var result = doc.MainDocumentPart.Document.Body.InnerText; + _logger.LogInformation("Read {w} characters from document", result.Length); + return result; + } + return Result.Fail("No body found in document"); + } + catch (Exception ex) when (ex is OpenXmlPackageException or InvalidDataException) + { + return Result.Fail(ex.ToString()).RefineError("Failed to read document"); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/DataProvider/OpenXmlSlidesProvider.cs b/TagsCloudContainerCore/DataProvider/OpenXmlSlidesProvider.cs new file mode 100644 index 000000000..56ebd4e03 --- /dev/null +++ b/TagsCloudContainerCore/DataProvider/OpenXmlSlidesProvider.cs @@ -0,0 +1,44 @@ +using DocumentFormat.OpenXml.Packaging; +using Microsoft.Extensions.Logging; +using System.Text; + +namespace TagsCloudContainerCore.DataProvider; + +public class OpenXmlSlidesProvider : IDataProvider +{ + private readonly ILogger _logger; + + public OpenXmlSlidesProvider(ILogger logger) + { + _logger = logger; + } + + public Result GetData(byte[] data) + { + _logger.LogInformation("Reading data with OpenXmlProvider"); + using var stream = new MemoryStream(data); + try + { + using var doc = PresentationDocument.Open(stream, false); + var text = new StringBuilder(); + var slides = doc.PresentationPart?.SlideParts; + + if (slides != null) + { + foreach (var slide in slides) + { + text.AppendLine(slide.Slide.InnerText); + } + + var result = text.ToString(); + _logger.LogInformation("Read {w} characters from presentation", result.Length); + return result; + } + return Result.Fail("No slides found in presentation"); + } + catch (Exception ex) when (ex is OpenXmlPackageException or InvalidDataException) + { + return Result.Fail(ex.ToString()).RefineError("Failed to read presentation"); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Facade/ITagCloud.cs b/TagsCloudContainerCore/Facade/ITagCloud.cs new file mode 100644 index 000000000..94aeff139 --- /dev/null +++ b/TagsCloudContainerCore/Facade/ITagCloud.cs @@ -0,0 +1,8 @@ +namespace TagsCloudContainerCore.Facade; + +public interface ITagCloud +{ + Result FromFile(string filePath); + Result FromString(string data); + Result FromBytes(byte[] data); +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Facade/ITagCloudFactory.cs b/TagsCloudContainerCore/Facade/ITagCloudFactory.cs new file mode 100644 index 000000000..680b5eac7 --- /dev/null +++ b/TagsCloudContainerCore/Facade/ITagCloudFactory.cs @@ -0,0 +1,6 @@ +namespace TagsCloudContainerCore.Facade; + +public interface ITagCloudFactory +{ + Result Create(Action configure); +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Facade/TagCloud.cs b/TagsCloudContainerCore/Facade/TagCloud.cs new file mode 100644 index 000000000..fcd459620 --- /dev/null +++ b/TagsCloudContainerCore/Facade/TagCloud.cs @@ -0,0 +1,60 @@ +using System.Text; +using TagsCloudContainerCore.DataProvider; +using TagsCloudContainerCore.ImageEncoders; +using TagsCloudContainerCore.Layouter; +using TagsCloudContainerCore.Renderer; +using TagsCloudContainerCore.TextProcessor; + +namespace TagsCloudContainerCore.Facade; + +public class TagCloud : ITagCloud +{ + private readonly ITextProcessor _textProcessor; + private readonly ILayouterFactory _layouterFactory; + private readonly IRenderer _renderer; + private readonly IImageEncoder _imageEncoder; + private readonly IDataProvider _dataProvider; + + public TagCloud( + ITextProcessor textProcessor, + ILayouterFactory layouterFactory, + IRenderer renderer, + IImageEncoder imageEncoder, + IDataProvider dataProvider) + { + _textProcessor = textProcessor; + _layouterFactory = layouterFactory; + _renderer = renderer; + _imageEncoder = imageEncoder; + _dataProvider = dataProvider; + } + + public Result FromString(string data) + { + return ProcessString(data); + } + + public Result FromFile(string filePath) + { + return Result.Of(() => File.ReadAllBytes(filePath)) + .Then(_dataProvider.GetData) + .Then(ProcessString); + } + + public Result FromBytes(byte[] data) + { + return Result.Of(() => Encoding.UTF8.GetString(data)) + .Then(ProcessString); + } + + private Result ProcessString(string data) + { + var layouter = _layouterFactory.Create(); + if (!layouter.IsSuccess) + return Result.Fail(layouter.Error); + return _textProcessor.ProcessText(data) + .Then(layouter.Value.LayoutTags) + .Then(_renderer.DrawTags) + .Then(_imageEncoder.Encode); + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Facade/TagCloudBuilder.cs b/TagsCloudContainerCore/Facade/TagCloudBuilder.cs new file mode 100644 index 000000000..74f955270 --- /dev/null +++ b/TagsCloudContainerCore/Facade/TagCloudBuilder.cs @@ -0,0 +1,61 @@ +using TagsCloudContainerCore.DataProvider; +using TagsCloudContainerCore.FontManager; +using TagsCloudContainerCore.ImageEncoders; +using TagsCloudContainerCore.Layouter; +using TagsCloudContainerCore.Renderer; +using TagsCloudContainerCore.TextProcessor; + +namespace TagsCloudContainerCore.Facade; + +public class TagCloudBuilder +{ + private readonly TagCloudOptions _options = new(); + + public TagCloudBuilder UseDataProvider() + { + _options.DataProviderType = typeof(T); + return this; + } + + public TagCloudBuilder UseFontManager(TConfig? config) + { + _options.FontManagerType = typeof(T); + _options.RegisterConfiguration(config); + return this; + } + + public TagCloudBuilder UseLayouter(TConfig? config) where T : ILayouterFactory + { + _options.LayouterType = typeof(T); + _options.RegisterConfiguration(config); + return this; + } + + public TagCloudBuilder UseWordProcessor(TConfig? config) + { + _options.WordProcessorType = typeof(T); + _options.RegisterConfiguration(config); + return this; + } + + public TagCloudBuilder UseRenderer(TConfig? config) where T : IRenderer + { + _options.RendererType = typeof(T); + _options.RegisterConfiguration(config); + return this; + } + + public TagCloudBuilder UseImageEncoder() + { + _options.ImageEncoderType = typeof(T); + return this; + } + + public TagCloudBuilder RegisterService() where TImplementation : TService + { + _options.RegisterService(); + return this; + } + + public TagCloudOptions Build() => _options; +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Facade/TagCloudFactory.cs b/TagsCloudContainerCore/Facade/TagCloudFactory.cs new file mode 100644 index 000000000..bc608c8ac --- /dev/null +++ b/TagsCloudContainerCore/Facade/TagCloudFactory.cs @@ -0,0 +1,27 @@ +using Autofac; + +namespace TagsCloudContainerCore.Facade; + +public class TagCloudFactory : ITagCloudFactory +{ + private readonly ILifetimeScope _scope; + + public TagCloudFactory(ILifetimeScope scope) + { + _scope = scope; + } + + public Result Create(Action configure) + { + return Result.Of(() => + { + var builder = new TagCloudBuilder(); + configure(builder); + + var options = builder.Build(); + var childScope = _scope.BeginLifetimeScope(cb => { cb.RegisterModule(new TagCloudModule(options)); }); + + return childScope.Resolve(); + }); + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Facade/TagCloudModule.cs b/TagsCloudContainerCore/Facade/TagCloudModule.cs new file mode 100644 index 000000000..dcc15b027 --- /dev/null +++ b/TagsCloudContainerCore/Facade/TagCloudModule.cs @@ -0,0 +1,72 @@ +using Autofac; +using Autofac.Builder; + +namespace TagsCloudContainerCore.Facade; + +public class TagCloudModule : Module +{ + private readonly TagCloudOptions _options; + + public TagCloudModule(TagCloudOptions options) + { + _options = options; + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .AsSelf() + .As() + .InstancePerLifetimeScope(); + + RegisterService(builder, _options.DataProviderType); + RegisterServiceWithConfig(builder, _options.FontManagerType); + RegisterServiceWithConfig(builder, _options.WordProcessorType); + RegisterServiceWithConfig(builder, _options.LayouterType); + RegisterServiceWithConfig(builder, _options.RendererType); + RegisterService(builder, _options.ImageEncoderType); + + foreach (var (serviceType, implementationType) in _options.ServiceMap) + { + RegisterServiceWithConfig(builder, implementationType, serviceType); + } + } + + private void RegisterServiceWithConfig(ContainerBuilder builder, Type implementationType, Type? serviceType = null) + { + var configInterface = implementationType.GetInterfaces() + .FirstOrDefault(type => type.IsGenericType && + type.GetProperties().Any(p => p.Name == "Config")); + + var registration = RegisterService(builder, implementationType, serviceType); + + if (configInterface == null) return; + var configType = configInterface.GetGenericArguments()[0]; + if (_options.Configurations.TryGetValue(configType, out var config)) + { + registration.OnActivated(@event => + { + var configProperty = @event.Instance.GetType().GetProperty("Config"); + configProperty?.SetValue(@event.Instance, config); + }); + } + } + + private static IRegistrationBuilder + RegisterService(ContainerBuilder builder, Type implementationType, Type? serviceType = null) + { + var registration = builder.RegisterType(implementationType); + + if (serviceType != null) + { + registration.As(serviceType); + } + else + { + registration.AsImplementedInterfaces(); + } + + registration.SingleInstance(); + return registration; + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Facade/TagCloudOptions.cs b/TagsCloudContainerCore/Facade/TagCloudOptions.cs new file mode 100644 index 000000000..76cf22a5c --- /dev/null +++ b/TagsCloudContainerCore/Facade/TagCloudOptions.cs @@ -0,0 +1,31 @@ +using TagsCloudContainerCore.DataProvider; +using TagsCloudContainerCore.FontManager; +using TagsCloudContainerCore.ImageEncoders; +using TagsCloudContainerCore.Layouter; +using TagsCloudContainerCore.Renderer; +using TagsCloudContainerCore.TextProcessor; + +namespace TagsCloudContainerCore.Facade; + +public class TagCloudOptions +{ + internal Dictionary ServiceMap { get; } = new(); + internal Dictionary Configurations { get; } = new(); + + public Type DataProviderType { get; internal set; } = typeof(IDataProvider); + public Type FontManagerType { get; internal set; } = typeof(IFontManager); + public Type LayouterType { get; internal set; } = typeof(ILayouterFactory); + public Type WordProcessorType { get; internal set; } = typeof(ITextProcessor); + public Type RendererType { get; internal set; } = typeof(IRenderer); + public Type ImageEncoderType { get; internal set; } = typeof(IImageEncoder); + + public void RegisterService() where TImplementation : TService + { + ServiceMap[typeof(TService)] = typeof(TImplementation); + } + + public void RegisterConfiguration(TConfig config) + { + Configurations[typeof(TConfig)] = config; + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/FontManager/FontManager.cs b/TagsCloudContainerCore/FontManager/FontManager.cs new file mode 100644 index 000000000..fd7a68931 --- /dev/null +++ b/TagsCloudContainerCore/FontManager/FontManager.cs @@ -0,0 +1,22 @@ +using TagsCloudContainerCore.Models.Graphics; + +namespace TagsCloudContainerCore.FontManager; + +public class FontManager : IFontManager +{ + public FontManagerConfig Config { get; init; } + + public Result MeasureString(string text, float fontSize) + { + try + { + var font = new Font(Config.Font.Typeface, fontSize); + var measuredSize = new Size(font.MeasureText(text), fontSize); + return Result.Ok(measuredSize); + } + catch (Exception ex) + { + return Result.Fail(ex.Message); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/FontManager/FontManagerConfig.cs b/TagsCloudContainerCore/FontManager/FontManagerConfig.cs new file mode 100644 index 000000000..1f5ce816e --- /dev/null +++ b/TagsCloudContainerCore/FontManager/FontManagerConfig.cs @@ -0,0 +1,8 @@ +using TagsCloudContainerCore.Models.Graphics; + +namespace TagsCloudContainerCore.FontManager; + +public record FontManagerConfig +{ + public Font Font { get; init; } = new("Arial"); +} \ No newline at end of file diff --git a/TagsCloudContainerCore/FontManager/IFontManager.cs b/TagsCloudContainerCore/FontManager/IFontManager.cs new file mode 100644 index 000000000..9c15964db --- /dev/null +++ b/TagsCloudContainerCore/FontManager/IFontManager.cs @@ -0,0 +1,13 @@ +using TagsCloudContainerCore.Models.Graphics; + +namespace TagsCloudContainerCore.FontManager; + +public interface IFontManager : IFontManager +{ + TConfig Config { get; init; } +} + +public interface IFontManager +{ + Result MeasureString(string text, float fontSize); +} \ No newline at end of file diff --git a/TagsCloudContainerCore/ImageEncoders/IImageEncoder.cs b/TagsCloudContainerCore/ImageEncoders/IImageEncoder.cs new file mode 100644 index 000000000..1c0cd320d --- /dev/null +++ b/TagsCloudContainerCore/ImageEncoders/IImageEncoder.cs @@ -0,0 +1,8 @@ +using SkiaSharp; + +namespace TagsCloudContainerCore.ImageEncoders; + +public interface IImageEncoder +{ + Result Encode(SKImage image); +} \ No newline at end of file diff --git a/TagsCloudContainerCore/ImageEncoders/JpegEncoder.cs b/TagsCloudContainerCore/ImageEncoders/JpegEncoder.cs new file mode 100644 index 000000000..fffc18664 --- /dev/null +++ b/TagsCloudContainerCore/ImageEncoders/JpegEncoder.cs @@ -0,0 +1,11 @@ +using SkiaSharp; + +namespace TagsCloudContainerCore.ImageEncoders; + +public class JpegEncoder : IImageEncoder +{ + public Result Encode(SKImage image) + { + return Result.Of(() => image.Encode(SKEncodedImageFormat.Jpeg, 100).ToArray()).RefineError("Failed to encode image"); + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/ImageEncoders/PngEncoder.cs b/TagsCloudContainerCore/ImageEncoders/PngEncoder.cs new file mode 100644 index 000000000..6a6ee149f --- /dev/null +++ b/TagsCloudContainerCore/ImageEncoders/PngEncoder.cs @@ -0,0 +1,11 @@ +using SkiaSharp; + +namespace TagsCloudContainerCore.ImageEncoders; + +public class PngEncoder : IImageEncoder +{ + public Result Encode(SKImage image) + { + return Result.Of(() => image.Encode(SKEncodedImageFormat.Png, 100).ToArray()).RefineError("Failed to encode image"); + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Layouter/CircularCloudLayouter.cs b/TagsCloudContainerCore/Layouter/CircularCloudLayouter.cs new file mode 100644 index 000000000..921e62fcd --- /dev/null +++ b/TagsCloudContainerCore/Layouter/CircularCloudLayouter.cs @@ -0,0 +1,121 @@ +using TagsCloudContainerCore.FontManager; +using TagsCloudContainerCore.Models; +using TagsCloudContainerCore.Models.Graphics; + +namespace TagsCloudContainerCore.Layouter; + +public class CircularCloudLayouter : ILayouter +{ + private double _angle; + private readonly List _rectangles = new(); + private readonly CircularCloudLayouterConfig _config; + private readonly IFontManager _fontManager; + + public IReadOnlyList Rectangles => _rectangles.AsReadOnly(); + + public CircularCloudLayouter(CircularCloudLayouterConfig config, IFontManager fontManager) + { + _fontManager = fontManager; + _config = config; + } + + public Result LayoutTags(Dictionary words) + { + if (words.Count == 0) + { + return Result.Fail("Words dictionary is empty"); + } + + var minWeight = words.Values.Min(); + var maxWeight = words.Values.Max(); + + var processed = words + .Select(word => LayoutSingleTag(word, minWeight, maxWeight)) + .ToArray(); + + var failedResult = processed.FirstOrDefault(r => !r.IsSuccess); + if (failedResult.IsSuccess == false) + { + return Result.Fail(failedResult.Error); + } + + return processed.Select(r => r.Value).ToArray(); + } + + private Result LayoutSingleTag(KeyValuePair word, double minWeight, double maxWeight) + { + var adjustedFontSize = GetAdjustedFontSize(word.Value, minWeight, maxWeight); + return PutNextTag(word, adjustedFontSize); + } + + private float GetAdjustedFontSize(double wordValue, double minWeight, double maxWeight) + { + if (maxWeight == minWeight) + return _config.MinFontSize; + var adjustedFontSize = (float)(_config.MinFontSize + (_config.MaxFontSize - _config.MinFontSize) * + (wordValue - minWeight) / (maxWeight - minWeight)); + return adjustedFontSize; + } + + + private Result PutNextTag(KeyValuePair word, float adjustedFontSize) + { + return _fontManager.MeasureString(word.Key, adjustedFontSize) + .Then(rectangleSize => + { + if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) + return Result.Fail("Invalid rectangle size"); + return Result.Ok(rectangleSize); + }) + .Then(rectangleSize => + { + Result rectangle; + do + { + var result = GetNextPosition() + .Then(r => GetRectangleCenter(r, rectangleSize)) + .Then(r => Result.Of(() => new Rectangle( + r.X, + r.Y, + r.X + rectangleSize.Width, + r.Y + rectangleSize.Height))); + if (!result.IsSuccess) + { + return result; + } + + rectangle = result; + } while (_rectangles.Any(r => r.IntersectsWith(rectangle.Value))); + + return rectangle; + }) + .Then(r => + { + _rectangles.Add(r); + return Result.Ok(new Tag + { + Text = word.Key, + FontSize = adjustedFontSize, + BBox = r + }); + }); + } + + + private static Result GetRectangleCenter(Point point, Size rectangleSize) + { + return Result.Of(() => new Point(point.X - rectangleSize.Width / 2, + point.Y - rectangleSize.Height / 2)); + } + + private Result GetNextPosition() + { + var radius = _config.SpiralStep * _angle; + var x = (float)(radius * Math.Cos(_angle)); + var y = (float)(radius * Math.Sin(_angle)); + + _angle += _config.SpiralStep; + + return Result.Of(() => new Point(x, y)); + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Layouter/CircularCloudLayouterConfig.cs b/TagsCloudContainerCore/Layouter/CircularCloudLayouterConfig.cs new file mode 100644 index 000000000..44701fc6d --- /dev/null +++ b/TagsCloudContainerCore/Layouter/CircularCloudLayouterConfig.cs @@ -0,0 +1,9 @@ +namespace TagsCloudContainerCore.Layouter; + +public record CircularCloudLayouterConfig +{ + public double SpiralStep { get; set; } = 0.1; + public float MinFontSize { get; set; } = 12.0f; + public float MaxFontSize { get; set; } = 48.0f; + public double InitialRadius { get; set; } = 0; +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Layouter/CircularCloudLayouterFactory.cs b/TagsCloudContainerCore/Layouter/CircularCloudLayouterFactory.cs new file mode 100644 index 000000000..9e81a76b5 --- /dev/null +++ b/TagsCloudContainerCore/Layouter/CircularCloudLayouterFactory.cs @@ -0,0 +1,19 @@ +using TagsCloudContainerCore.FontManager; + +namespace TagsCloudContainerCore.Layouter; + +public class CircularCloudLayouterFactory : ILayouterFactory +{ + public CircularCloudLayouterConfig Config { get; init; } + private readonly IFontManager _fontManager; + + public CircularCloudLayouterFactory(IFontManager fontManager) + { + _fontManager = fontManager; + } + + public Result Create() + { + return Result.Of(() => new CircularCloudLayouter(Config, _fontManager)); + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Layouter/ILayouter.cs b/TagsCloudContainerCore/Layouter/ILayouter.cs new file mode 100644 index 000000000..bf3acfe32 --- /dev/null +++ b/TagsCloudContainerCore/Layouter/ILayouter.cs @@ -0,0 +1,8 @@ +using TagsCloudContainerCore.Models; + +namespace TagsCloudContainerCore.Layouter; + +public interface ILayouter +{ + Result LayoutTags(Dictionary words); +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Layouter/ILayouterFactory.cs b/TagsCloudContainerCore/Layouter/ILayouterFactory.cs new file mode 100644 index 000000000..582814cd1 --- /dev/null +++ b/TagsCloudContainerCore/Layouter/ILayouterFactory.cs @@ -0,0 +1,11 @@ +namespace TagsCloudContainerCore.Layouter; + +public interface ILayouterFactory +{ + Result Create(); +} + +public interface ILayouterFactory : ILayouterFactory +{ + TConfig Config { get; init; } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Models/Graphics/Color.cs b/TagsCloudContainerCore/Models/Graphics/Color.cs new file mode 100644 index 000000000..9a4a98691 --- /dev/null +++ b/TagsCloudContainerCore/Models/Graphics/Color.cs @@ -0,0 +1,28 @@ +namespace TagsCloudContainerCore.Models.Graphics; + +public struct Color +{ + private readonly uint _color; + + public Color(byte r, byte g, byte b) + { + _color = 0xff000000u | (uint)(r << 16) | (uint)(g << 8) | b; + } + + public Color(string hex) + { + if (hex.StartsWith("#")) + { + hex = hex[1..]; + } + + _color = hex.Length switch + { + 6 => 0xff000000u | (uint)Convert.ToInt32(hex, 16), + 3 => (0xff000000u | (uint)Convert.ToInt32(new string(hex.SelectMany(c => new[] {c, c}).ToArray()), 16)), + _ => throw new ArgumentException("Hex color code should be 3 or 6 characters long.") + }; + } + + public uint ToUint() => _color; +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Models/Graphics/Font.cs b/TagsCloudContainerCore/Models/Graphics/Font.cs new file mode 100644 index 000000000..b1172c7a3 --- /dev/null +++ b/TagsCloudContainerCore/Models/Graphics/Font.cs @@ -0,0 +1,51 @@ +using SkiaSharp; + +namespace TagsCloudContainerCore.Models.Graphics; + +public class Font : SKFont +{ + public Font(string familyName, float size) : base(GetTypeface(familyName), size) + { + } + + public Font(string familyName) : base(GetTypeface(familyName)) + { + } + + public Font() : base(SKTypeface.Default) + { + } + + public Font(SKTypeface typeface, float size) : base(typeface, size) + { + } + + public Font(SKTypeface typeface) : base(typeface) + { + } + + public Font(SKFont font) : base(font.Typeface, font.Size) + { + Hinting = font.Hinting; + Edging = font.Edging; + Embolden = font.Embolden; + Subpixel = font.Subpixel; + LinearMetrics = font.LinearMetrics; + ScaleX = font.ScaleX; + SkewX = font.SkewX; + } + + public static Font Default => new Font(SKTypeface.Default.ToFont()); + + public Font ToFont() => this; + + private static SKTypeface GetTypeface(string familyName) + { + var typeface = SKTypeface.FromFamilyName(familyName); + if (typeface == null || typeface.FamilyName != familyName) + { + throw new ArgumentException($"Font family '{familyName}' is not available on the system."); + } + return typeface; + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Models/Graphics/Point.cs b/TagsCloudContainerCore/Models/Graphics/Point.cs new file mode 100644 index 000000000..41ad7ba17 --- /dev/null +++ b/TagsCloudContainerCore/Models/Graphics/Point.cs @@ -0,0 +1,13 @@ +namespace TagsCloudContainerCore.Models.Graphics; + +public struct Point +{ + public Point(float x, float y) + { + X = x; + Y = y; + } + + public float X { get; } + public float Y { get; } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Models/Graphics/Rectangle.cs b/TagsCloudContainerCore/Models/Graphics/Rectangle.cs new file mode 100644 index 000000000..fb508c29c --- /dev/null +++ b/TagsCloudContainerCore/Models/Graphics/Rectangle.cs @@ -0,0 +1,32 @@ +namespace TagsCloudContainerCore.Models.Graphics; + +public readonly struct Rectangle +{ + public Rectangle(float left, float top, float right, float bottom) + { + if (left > right || top > bottom) + throw new ArgumentException("Invalid rectangle parameters"); + if (float.IsNaN(left) || float.IsNaN(top) || float.IsNaN(right) || float.IsNaN(bottom)) + throw new ArgumentException("Rectangle parameters must not be NaN"); + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + + public float Left { get; } + public float Top { get; } + public float Right { get; } + public float Bottom { get; } + + public float Width => Right - Left; + public float Height => Bottom - Top; + + public bool IntersectsWith(Rectangle other) + { + return Left < other.Right && + Right > other.Left && + Top < other.Bottom && + Bottom > other.Top; + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Models/Graphics/Size.cs b/TagsCloudContainerCore/Models/Graphics/Size.cs new file mode 100644 index 000000000..d98ca5fe7 --- /dev/null +++ b/TagsCloudContainerCore/Models/Graphics/Size.cs @@ -0,0 +1,7 @@ +namespace TagsCloudContainerCore.Models.Graphics; + +public struct Size(float width, float height) +{ + public float Width { get; } = width; + public float Height { get; } = height; +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Models/PartOfSpeech.cs b/TagsCloudContainerCore/Models/PartOfSpeech.cs new file mode 100644 index 000000000..b3821e71c --- /dev/null +++ b/TagsCloudContainerCore/Models/PartOfSpeech.cs @@ -0,0 +1,49 @@ +namespace TagsCloudContainerCore.Models; + +/// +/// Части речи +/// +public enum PartOfSpeech +{ + ///прилагательное + A, + + ///наречие + ADV, + + ///местоименное наречие + ADVPRO, + + ///числительное-прилагательное + ANUM, + + ///местоимение-прилагательное + APRO, + + ///часть композита - сложного слова + COM, + + ///союз + CONJ, + + ///междометие + INTJ, + + ///числительное + NUM, + + ///частица + PART, + + ///предлог + PR, + + ///существительное + S, + + ///местоимение-существительное + SPRO, + + ///глагол + V +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Models/Tag.cs b/TagsCloudContainerCore/Models/Tag.cs new file mode 100644 index 000000000..c21e877c0 --- /dev/null +++ b/TagsCloudContainerCore/Models/Tag.cs @@ -0,0 +1,10 @@ +using TagsCloudContainerCore.Models.Graphics; + +namespace TagsCloudContainerCore.Models; + +public class Tag +{ + public float FontSize; + public required Rectangle BBox; + public required string Text; +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Mystem/mystem b/TagsCloudContainerCore/Mystem/mystem new file mode 100755 index 000000000..9b59628ed Binary files /dev/null and b/TagsCloudContainerCore/Mystem/mystem differ diff --git a/TagsCloudContainerCore/Mystem/mystem.exe b/TagsCloudContainerCore/Mystem/mystem.exe new file mode 100755 index 000000000..e7158ff1b Binary files /dev/null and b/TagsCloudContainerCore/Mystem/mystem.exe differ diff --git a/TagsCloudContainerCore/Renderer/IRenderer.cs b/TagsCloudContainerCore/Renderer/IRenderer.cs new file mode 100644 index 000000000..f656c7298 --- /dev/null +++ b/TagsCloudContainerCore/Renderer/IRenderer.cs @@ -0,0 +1,14 @@ +using SkiaSharp; +using TagsCloudContainerCore.Models; + +namespace TagsCloudContainerCore.Renderer; + +public interface IRenderer : IRenderer +{ + TConfig Config { get; init; } +} + +public interface IRenderer +{ + Result DrawTags(IReadOnlyCollection tags); +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Renderer/Renderer.cs b/TagsCloudContainerCore/Renderer/Renderer.cs new file mode 100644 index 000000000..6fe94cd07 --- /dev/null +++ b/TagsCloudContainerCore/Renderer/Renderer.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Logging; +using SkiaSharp; +using TagsCloudContainerCore.Models; +using TagsCloudContainerCore.Models.Graphics; + +namespace TagsCloudContainerCore.Renderer; + +public class Renderer : IRenderer +{ + private readonly ILogger _logger; + private readonly SKPaint _paint; + public RendererConfig Config { get; init; } = new(); + + + public Renderer(ILogger logger) + { + _logger = logger; + _paint = new SKPaint(); + } + + public Result DrawTags(IReadOnlyCollection tags) + { + if (tags.Count == 0) + { + return Result.Fail("No tags to draw"); + } + _logger.LogInformation("Rendering {tags} tags with scale {scale}", tags.Count, Config.RenderingScale); + var size = CalculateImageSize(tags).Value; + var bitmap = new SKBitmap((int)(size.Width * Config.RenderingScale), + (int)(size.Height * Config.RenderingScale)); + using var canvas = new SKCanvas(bitmap); + canvas.Clear(new SKColor(Config.BackgroundColor.ToUint())); + canvas.Translate(-size.Left * Config.RenderingScale, -size.Top * Config.RenderingScale); + _paint.Color = new SKColor(Config.TextColor.ToUint()); + foreach (var tag in tags) + { + Config.TagFont.Size = tag.FontSize * Config.RenderingScale; + var x = tag.BBox.Left * Config.RenderingScale; + var y = tag.BBox.Bottom * Config.RenderingScale - Config.TagFont.Metrics.Descent; + canvas.DrawText(tag.Text, x, y, Config.TagFont, _paint); + } + + _logger.LogInformation("Finished drawing tags"); + return SKImage.FromBitmap(bitmap); + } + + private Result CalculateImageSize(IReadOnlyCollection tags) + { + var endX = (int)tags.MaxBy(x => x.BBox.Right)!.BBox.Right; + var endY = (int)tags.MaxBy(x => x.BBox.Bottom)!.BBox.Bottom; + var startX = (int)tags.MinBy(x => x.BBox.Left)!.BBox.Left; + var startY = (int)tags.MinBy(x => x.BBox.Top)!.BBox.Top; + + _logger.LogInformation("Calculated image size: {x}x{y}", endX - startX, endY - startY); + return Result.Of(() => new Rectangle(startX, startY, endX, endY)); + } +} \ No newline at end of file diff --git a/TagsCloudContainerCore/Renderer/RendererConfig.cs b/TagsCloudContainerCore/Renderer/RendererConfig.cs new file mode 100644 index 000000000..cc800b6a5 --- /dev/null +++ b/TagsCloudContainerCore/Renderer/RendererConfig.cs @@ -0,0 +1,23 @@ +using TagsCloudContainerCore.Models.Graphics; + +namespace TagsCloudContainerCore.Renderer; + +public record RendererConfig() +{ + private readonly float _renderingScale = 1; + + public Color BackgroundColor { get; init; } = new(255, 255, 255); + public Color TextColor { get; init; } = new(0, 0, 0); + public Font TagFont { get; init; } = new("Arial", 12); + public float RenderingScale + { + get => _renderingScale; + init + { + if (value <= 0) + throw new ArgumentOutOfRangeException(nameof(RenderingScale), + "Rendering scale must be greater than 0."); + _renderingScale = value; + } + } +} \ No newline at end of file diff --git a/FileSenderRailway/Result.cs b/TagsCloudContainerCore/Result/Result.cs similarity index 98% rename from FileSenderRailway/Result.cs rename to TagsCloudContainerCore/Result/Result.cs index da50ce0ad..3a5b30377 100644 --- a/FileSenderRailway/Result.cs +++ b/TagsCloudContainerCore/Result/Result.cs @@ -1,7 +1,3 @@ -using System; - -namespace FileSenderRailway; - public class None { private None() diff --git a/TagsCloudContainerCore/TagsCloudContainerCore.csproj b/TagsCloudContainerCore/TagsCloudContainerCore.csproj new file mode 100644 index 000000000..10fd1c319 --- /dev/null +++ b/TagsCloudContainerCore/TagsCloudContainerCore.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + TagsCloudContainerCore + + + + + + + + + + + + + + + + Always + + + + diff --git a/TagsCloudContainerCore/TextProcessor/ITextProcessor.cs b/TagsCloudContainerCore/TextProcessor/ITextProcessor.cs new file mode 100644 index 000000000..de5255032 --- /dev/null +++ b/TagsCloudContainerCore/TextProcessor/ITextProcessor.cs @@ -0,0 +1,12 @@ +namespace TagsCloudContainerCore.TextProcessor; + +public interface ITextProcessor : ITextProcessor +{ + TConfig Config { get; init; } +} + +public interface ITextProcessor +{ + Result> ProcessText(string word); +} + diff --git a/TagsCloudContainerCore/TextProcessor/MyStem/MyStemProcessedWord.cs b/TagsCloudContainerCore/TextProcessor/MyStem/MyStemProcessedWord.cs new file mode 100644 index 000000000..ca5530ea1 --- /dev/null +++ b/TagsCloudContainerCore/TextProcessor/MyStem/MyStemProcessedWord.cs @@ -0,0 +1,5 @@ +using TagsCloudContainerCore.Models; + +namespace TagsCloudContainerCore.TextProcessor.MyStem; + +public record MyStemProcessedWord(string NormalForm, PartOfSpeech PartOfSpeech); \ No newline at end of file diff --git a/TagsCloudContainerCore/TextProcessor/MyStem/MyStemWrapper.cs b/TagsCloudContainerCore/TextProcessor/MyStem/MyStemWrapper.cs new file mode 100644 index 000000000..7d83708e6 --- /dev/null +++ b/TagsCloudContainerCore/TextProcessor/MyStem/MyStemWrapper.cs @@ -0,0 +1,107 @@ +using System.Diagnostics; +using TagsCloudContainerCore.Models; + +namespace TagsCloudContainerCore.TextProcessor.MyStem; + +public class MyStemWrapper : IDisposable +{ + private static string PathToBinary => + Environment.OSVersion.Platform == PlatformID.Win32NT ? "Mystem/mystem.exe" : "Mystem/mystem"; + private Process? _process; + private StreamWriter _inputWriter; + private StreamReader _outputReader; + private StreamReader _errorReader; + + public Result StartProcess(string arguments = "-in") + { + if (!File.Exists(PathToBinary)) + { + return Result.Fail("MyStem binary not found"); + } + var psi = new ProcessStartInfo + { + FileName = PathToBinary, + Arguments = arguments, + UseShellExecute = false, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + var process = Process.Start(psi); + if (process == null || process.HasExited) + { + return Result.Fail("Failed to start MyStem process"); + } + + _process = process; + _inputWriter = _process.StandardInput; + _outputReader = _process.StandardOutput; + _errorReader = _process.StandardError; + return Result.Ok(); + } + + + //TODO: fix (TSystemError) (Broken pipe) util/stream/output.cpp:322: fflush failed Aborted. + public Result ProcessWord(string word) + { + if (_process == null || _process.HasExited) + { + return Result.Fail("Process is not running."); + } + + _inputWriter.WriteLine(word); + //Possible fix? + //_inputWriter.Flush(); + var line = _outputReader.ReadLine(); + if (line != null) return ParseResult(line); + var error = _errorReader.ReadLine(); + return Result.Fail($"MyStem process failed: {error}"); + } + + private void StopProcess() + { + _inputWriter.Close(); + _outputReader.Close(); + _process?.WaitForExit(); + _process?.Dispose(); + } + + public void Dispose() + { + StopProcess(); + GC.SuppressFinalize(this); + } + + + //TODO: оптипизировать + private static Result ParseResult(string raw) + { + // Пример парса: "сделал{сделать=V,сов,пе=прош,ед,изъяв,муж}" + // Пример ошибки: ъ{ъ??} + var startIndex = raw.IndexOf('{') + 1; + var endIndex = raw.IndexOf('}'); + var metadata = raw.Substring(startIndex, endIndex - startIndex).Split(','); + + if (metadata.Length == 0 || !metadata[0].Contains('=') || metadata[0].Contains('{')) + { + return null; + } + + var rootForm = metadata[0].Split('=')[0]; + if (rootForm.Contains('?')) + { + rootForm = rootForm.Remove(rootForm.IndexOf('?')); + } + var partOfSpeech = metadata[0].Split('=')[1]; + + if (Enum.TryParse(partOfSpeech, out PartOfSpeech pos)) + { + return new MyStemProcessedWord(rootForm, pos); + } + + return Result.Fail("Invalid part of speech in raw string"); + } +} + diff --git a/TagsCloudContainerCore/TextProcessor/MyStemTextProcessor.cs b/TagsCloudContainerCore/TextProcessor/MyStemTextProcessor.cs new file mode 100644 index 000000000..814b79e36 --- /dev/null +++ b/TagsCloudContainerCore/TextProcessor/MyStemTextProcessor.cs @@ -0,0 +1,124 @@ +using Microsoft.Extensions.Logging; +using TagsCloudContainerCore.TextProcessor.MyStem; + +namespace TagsCloudContainerCore.TextProcessor; + +public class MyStemTextProcessor : ITextProcessor +{ + private readonly ILogger _logger; + + public MyStemTextProcessorConfig Config { get; init; } = new(); + + public MyStemTextProcessor(ILogger logger) + { + _logger = logger; + _myStemWrapper = new MyStemWrapper(); + } + + private readonly MyStemWrapper _myStemWrapper; + + + public Result> ProcessText(string text) + { + var words = text.Split(); + _logger.LogInformation("Got {n} words", words.Length); + _logger.LogInformation("Start processing text with MyStem"); + return _myStemWrapper.StartProcess() + .Then(_ => GetWeightedWords(words)) + .Then(r => + { + _myStemWrapper.Dispose(); + _logger.LogInformation("Finished processing text. Got {n} weighted words, excluded {e}", r.Count, + words.Distinct().Count() - r.Count); + return ProcessWeightedWords(r); + }); + } + + private static string RemoveSpecialChars(string word) + { + var wordChars = word.ToCharArray(); + for (var i = 0; i < wordChars.Length; i++) + { + if (!char.IsLetter(wordChars[i])) + { + wordChars[i] = ' '; + } + + wordChars[i] = char.ToLower(wordChars[i]); + } + + return new string(wordChars); + } + + private Result> GetWeightedWords(string[] words) + { + var weightedWords = new Dictionary(); + foreach (var word in words) + { + if (word.Any(char.IsDigit)) continue; + + var wordWithoutSpecialChars = RemoveSpecialChars(word); + + if (string.IsNullOrWhiteSpace(wordWithoutSpecialChars)) + { + continue; + } + + var result = _myStemWrapper.ProcessWord(wordWithoutSpecialChars); + if (!result.IsSuccess) + { + return Result.Fail>(result.Error); + } + + var processedWord = result.Value; + + if (processedWord == null) + { + continue; + } + + var excludedByConfig = Config.ExcludedPartsOfSpeech.Contains(processedWord.PartOfSpeech) || + Config.ExcludedWords.Contains(processedWord.NormalForm); + if (excludedByConfig) + { + continue; + } + + if (weightedWords.TryGetValue(processedWord.NormalForm, out var value)) + { + weightedWords[processedWord.NormalForm] = ++value; + } + else + { + weightedWords.Add(processedWord.NormalForm, 1); + } + } + + return weightedWords; + } + + private Dictionary ProcessWeightedWords(Dictionary weightedWords) + { + weightedWords = weightedWords + .OrderByDescending(pair => pair.Value) + .Take(Config.MaxWordsCount) + .ToDictionary(pair => pair.Key, pair => pair.Value); + weightedWords = Config.SortOrder switch + { + SortOrder.Ascending => weightedWords.OrderBy(pair => pair.Value) + .ToDictionary(pair => pair.Key, pair => pair.Value), + SortOrder.Descending => weightedWords, + SortOrder.Random => weightedWords.OrderBy(_ => Guid.NewGuid()) + .ToDictionary(pair => pair.Key, pair => pair.Value), + _ => weightedWords + }; + return weightedWords; + } +} + +public enum SortOrder +{ + Ascending, + Descending, + Random +} \ No newline at end of file diff --git a/TagsCloudContainerCore/TextProcessor/MyStemTextProcessorConfig.cs b/TagsCloudContainerCore/TextProcessor/MyStemTextProcessorConfig.cs new file mode 100644 index 000000000..b37d954e2 --- /dev/null +++ b/TagsCloudContainerCore/TextProcessor/MyStemTextProcessorConfig.cs @@ -0,0 +1,15 @@ +using TagsCloudContainerCore.Models; + +namespace TagsCloudContainerCore.TextProcessor; + +public record MyStemTextProcessorConfig +{ + public PartOfSpeech[] ExcludedPartsOfSpeech { get; set; } = + { PartOfSpeech.PART, PartOfSpeech.ADV, PartOfSpeech.PR, PartOfSpeech.CONJ }; + + public string[] ExcludedWords { get; set; } = []; + + public SortOrder SortOrder { get; set; } = SortOrder.Descending; + + public int MaxWordsCount { get; set; } = 50; +} \ No newline at end of file diff --git a/TagsCloudTests/CircularCloudLayouterTests.cs b/TagsCloudTests/CircularCloudLayouterTests.cs new file mode 100644 index 000000000..3ac04400f --- /dev/null +++ b/TagsCloudTests/CircularCloudLayouterTests.cs @@ -0,0 +1,142 @@ +using TagsCloudContainerCore.Layouter; +using TagsCloudContainerCore.Models.Graphics; +using TagsCloudContainerCore.FontManager; +using Moq; + +namespace TagsCloudTests; + +[TestFixture] +public class CircularCloudLayouterTests +{ + private Mock _fontManagerMock; + private const float MinFontSize = 12f; + private const float MaxFontSize = 48f; + private const double SpiralStep = 0.1; + private CircularCloudLayouterConfig _config; + + [SetUp] + public void SetUp() + { + _fontManagerMock = new Mock(); + _config = new CircularCloudLayouterConfig + { + MinFontSize = MinFontSize, + MaxFontSize = MaxFontSize, + SpiralStep = SpiralStep + }; + + _fontManagerMock + .Setup(f => f.MeasureString(It.IsAny(), It.IsAny())) + .Returns((string _, float _) => Result.Ok(new Size(10, 10))); + } + + [Test] + public void LayoutTags_ReturnsFailure_WhenEmptyDictionary() + { + var layouter = new CircularCloudLayouter(_config, _fontManagerMock.Object); + var result = layouter.LayoutTags(new Dictionary()); + result.IsSuccess.Should().BeFalse(); + } + + [Test] + public void LayoutTags_ReturnsSuccessWithOneTag_OnSingleWord() + { + var layouter = new CircularCloudLayouter(_config, _fontManagerMock.Object); + var words = new Dictionary { { "1", 1.0 } }; + + var result = layouter.LayoutTags(words); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().HaveCount(1); + result.GetValueOrThrow()[0].Text.Should().Be("1"); + result.GetValueOrThrow()[0].FontSize.Should().Be(MinFontSize); + } + + [Test] + public void LayoutTags_ReturnsTagsThatDontOverlap() + { + var layouter = new CircularCloudLayouter(_config, _fontManagerMock.Object); + var words = new Dictionary + { + { "1", 1.0 }, + { "2", 2.0 }, + { "3", 3.0 } + }; + + var result = layouter.LayoutTags(words); + + result.IsSuccess.Should().BeTrue(); + var tags = result.GetValueOrThrow(); + for (var i = 0; i < tags.Length; i++) + { + for (var j = i + 1; j < tags.Length; j++) + { + tags[i].BBox.IntersectsWith(tags[j].BBox).Should().BeFalse(); + } + } + } + + [Test] + public void LayoutTags_DifferentWeights_AffectFontSizeCorrectly() + { + var layouter = new CircularCloudLayouter(_config, _fontManagerMock.Object); + var words = new Dictionary + { + { "1", 1.0 }, + { "2", 2.0 } + }; + + var result = layouter.LayoutTags(words); + + result.IsSuccess.Should().BeTrue(); + var smallTag = result.GetValueOrThrow().First(t => t.Text == "1"); + var largeTag = result.GetValueOrThrow().First(t => t.Text == "2"); + smallTag.FontSize.Should().Be(MinFontSize); + largeTag.FontSize.Should().Be(MaxFontSize); + } + + [Test] + public void Rectangles_ReturnsAllRectangles_AfterLayouting() + { + var layouter = new CircularCloudLayouter(_config, _fontManagerMock.Object); + var words = new Dictionary { { "1", 1.0 }, { "2", 2.0 } }; + + var result = layouter.LayoutTags(words); + + result.IsSuccess.Should().BeTrue(); + layouter.Rectangles.Should().HaveCount(2); + layouter.Rectangles.Should().Contain(result.GetValueOrThrow()[0].BBox); + layouter.Rectangles.Should().Contain(result.GetValueOrThrow()[1].BBox); + } + + [Test] + public void LayoutTags_UsesMinFontSize_WhenSameWeight() + { + var layouter = new CircularCloudLayouter(_config, _fontManagerMock.Object); + var words = new Dictionary + { + { "1", 1.0 }, + { "2", 1.0 } + }; + + var result = layouter.LayoutTags(words); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().AllSatisfy(tag => tag.FontSize.Should().Be(MinFontSize)); + } + + [Test] + public void LayoutTags_ReturnsFailure_WhenFontManagerFails() + { + _fontManagerMock + .Setup(f => f.MeasureString(It.IsAny(), It.IsAny())) + .Returns(Result.Fail("Font measurement failed")); + + var layouter = new CircularCloudLayouter(_config, _fontManagerMock.Object); + var words = new Dictionary { { "1", 1.0 } }; + + var result = layouter.LayoutTags(words); + + result.IsSuccess.Should().BeFalse(); + } +} \ No newline at end of file diff --git a/TagsCloudTests/GlobalUsings.cs b/TagsCloudTests/GlobalUsings.cs new file mode 100644 index 000000000..91a038c4c --- /dev/null +++ b/TagsCloudTests/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using FluentAssertions; +global using NUnit.Framework; diff --git a/TagsCloudTests/MyStemTextProcessorTests.cs b/TagsCloudTests/MyStemTextProcessorTests.cs new file mode 100644 index 000000000..dcefaa7c2 --- /dev/null +++ b/TagsCloudTests/MyStemTextProcessorTests.cs @@ -0,0 +1,87 @@ +using Microsoft.Extensions.Logging; +using Moq; +using TagsCloudContainerCore.Models; +using TagsCloudContainerCore.TextProcessor; + +namespace TagsCloudTests; + +public class MyStemTextProcessorTests +{ + private readonly MyStemTextProcessor _myStemTextProcessor; + + public MyStemTextProcessorTests() + { + var mockLogger = new Mock>(); + _myStemTextProcessor = new MyStemTextProcessor(mockLogger.Object); + } + + [Test] + public void MyStemWordProcessor_ShouldProcessWord() + { + var result = _myStemTextProcessor.ProcessText("слово"); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().ContainSingle().Which.Should().Be(new KeyValuePair("слово", 1)); + } + + [Test] + public void MyStemWordProcessor_ShouldProcessMultipleWords() + { + var result = _myStemTextProcessor.ProcessText("слово слова"); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().HaveCount(1); + result.GetValueOrThrow().Should().ContainSingle(pair => pair.Key == "слово" && pair.Value == 2); + } + + [Test] + public void MyStemWordProcessor_ShouldProcessWordWithExcludedPartOfSpeech() + { + _myStemTextProcessor.Config.ExcludedPartsOfSpeech = [PartOfSpeech.S]; + var result = _myStemTextProcessor.ProcessText("слово"); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().BeEmpty(); + } + + [Test] + public void MyStemWordProcessor_ShouldProcessWordWithExcludedWord() + { + _myStemTextProcessor.Config.ExcludedWords = ["слово"]; + var result = _myStemTextProcessor.ProcessText("слово"); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().BeEmpty(); + } + + [Test] + public void MyStemWordProcessor_ShouldProcessLongText() + { + var result = _myStemTextProcessor.ProcessText("Это текст, который нужно обработать. Текст, текст, текст."); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().HaveCount(4); + result.GetValueOrThrow().Should().Contain(pair => pair.Key == "это" && pair.Value == 1); + result.GetValueOrThrow().Should().Contain(pair => pair.Key == "текст" && pair.Value == 4); + result.GetValueOrThrow().Should().Contain(pair => pair.Key == "который" && pair.Value == 1); + result.GetValueOrThrow().Should().Contain(pair => pair.Key == "обрабатывать" && pair.Value == 1); + } + + [Test] + public void MyStemWordProcessor_ShouldProcessEmptyString() + { + var result = _myStemTextProcessor.ProcessText(""); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().BeEmpty(); + } + + [Test] + public void MyStemWordProcessor_ShouldProcessInvalidString() + { + var result = _myStemTextProcessor.ProcessText("123"); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().BeEmpty(); + } +} \ No newline at end of file diff --git a/TagsCloudTests/MyStemWrapperTests.cs b/TagsCloudTests/MyStemWrapperTests.cs new file mode 100644 index 000000000..770e7237c --- /dev/null +++ b/TagsCloudTests/MyStemWrapperTests.cs @@ -0,0 +1,115 @@ +using TagsCloudContainerCore.Models; +using TagsCloudContainerCore.TextProcessor.MyStem; + +namespace TagsCloudTests; + +public class MyStemWrapperTests +{ + private readonly MyStemWrapper _myStemWrapper = new(); + + [SetUp] + public void SetUp() + { + var result = _myStemWrapper.StartProcess(); + result.IsSuccess.Should().BeTrue(); + } + + [TearDown] + public void TearDown() + { + _myStemWrapper.Dispose(); + } + + [Test] + public void MyStemWrapper_ShouldProcessWord() + { + var result = _myStemWrapper.ProcessWord("слово"); + result.IsSuccess.Should().BeTrue(); + var processedWord = result.GetValueOrThrow(); + processedWord.Should().NotBeNull(); + processedWord!.NormalForm.Should().Be("слово"); + processedWord.PartOfSpeech.Should().Be(PartOfSpeech.S); + } + + [Test] + public void MyStemWrapper_ShouldProcessWord_WithPunctuation() + { + var result = _myStemWrapper.ProcessWord("слово,"); + result.IsSuccess.Should().BeTrue(); + var processedWord = result.GetValueOrThrow(); + processedWord.Should().NotBeNull(); + processedWord!.NormalForm.Should().Be("слово"); + processedWord.PartOfSpeech.Should().Be(PartOfSpeech.S); + } + + [Test] + public void MyStemWrapper_ShouldProcessWord_InDifferentForms() + { + var result = _myStemWrapper.ProcessWord("слова"); + result.IsSuccess.Should().BeTrue(); + var processedWord = result.GetValueOrThrow(); + processedWord.Should().NotBeNull(); + processedWord!.NormalForm.Should().Be("слово"); + processedWord.PartOfSpeech.Should().Be(PartOfSpeech.S); + } + + [Test] + public void MyStemWrapper_ShouldProcessWord_WithDifferentPartOfSpeech() + { + var result = _myStemWrapper.ProcessWord("говорю"); + result.IsSuccess.Should().BeTrue(); + var processedWord = result.GetValueOrThrow(); + processedWord.Should().NotBeNull(); + processedWord!.NormalForm.Should().Be("говорить"); + processedWord.PartOfSpeech.Should().Be(PartOfSpeech.V); + } + + [Test] + public void MyStemWrapper_ShouldProcessMultipleWords() + { + var result = _myStemWrapper.ProcessWord("слово"); + result.IsSuccess.Should().BeTrue(); + var processedWord = result.GetValueOrThrow(); + processedWord.Should().NotBeNull(); + processedWord!.NormalForm.Should().Be("слово"); + processedWord.PartOfSpeech.Should().Be(PartOfSpeech.S); + + result = _myStemWrapper.ProcessWord("слова"); + result.IsSuccess.Should().BeTrue(); + processedWord = result.GetValueOrThrow(); + processedWord.Should().NotBeNull(); + processedWord!.NormalForm.Should().Be("слово"); + processedWord.PartOfSpeech.Should().Be(PartOfSpeech.S); + + result = _myStemWrapper.ProcessWord("говорю"); + result.IsSuccess.Should().BeTrue(); + processedWord = result.GetValueOrThrow(); + processedWord.Should().NotBeNull(); + processedWord!.NormalForm.Should().Be("говорить"); + processedWord.PartOfSpeech.Should().Be(PartOfSpeech.V); + + result = _myStemWrapper.ProcessWord("говоря"); + result.IsSuccess.Should().BeTrue(); + processedWord = result.GetValueOrThrow(); + processedWord.Should().NotBeNull(); + processedWord!.NormalForm.Should().Be("говорить"); + processedWord.PartOfSpeech.Should().Be(PartOfSpeech.V); + } + + [Test] + public void MyStemWrapper_ShouldReturnNull_ForUnprocessableWord() + { + var result = _myStemWrapper.ProcessWord("ъ"); + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().BeNull(); + } + + [Test] + public void MyStemWrapper_ShouldFail_WhenProcessNotStarted() + { + var wrapper = new MyStemWrapper(); + var result = wrapper.ProcessWord("слово"); + result.IsSuccess.Should().BeFalse(); + result.Error.Should().Be("Process is not running."); + } +} \ No newline at end of file diff --git a/TagsCloudTests/PngEncoderTests.cs b/TagsCloudTests/PngEncoderTests.cs new file mode 100644 index 000000000..e12400667 --- /dev/null +++ b/TagsCloudTests/PngEncoderTests.cs @@ -0,0 +1,58 @@ +using SkiaSharp; +using TagsCloudContainerCore.ImageEncoders; + +namespace TagsCloudTests; + +[TestFixture] +public class PngEncoderTests +{ + private PngEncoder _encoder; + private SKImage _testImage; + + [SetUp] + public void Setup() + { + _encoder = new PngEncoder(); + using var surface = SKSurface.Create(new SKImageInfo(1, 1)); + _testImage = surface.Snapshot(); + } + + [Test] + public void Encode_ValidImage_ReturnsSuccessResultWithNonEmptyByteArray() + { + var result = _encoder.Encode(_testImage); + + result.IsSuccess.Should().BeTrue(); + result.GetValueOrThrow().Should().NotBeEmpty(); + } + + [Test] + public void Encode_ValidImage_ReturnsPngFormat() + { + var result = _encoder.Encode(_testImage); + result.IsSuccess.Should().BeTrue(); + var bytes = result.GetValueOrThrow(); + + // magic numbers + + var pngHeader = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }; + var pngFooter = new byte[] { 73, 69, 78, 68, 174, 66, 96, 130 }; + bytes.Take(8).Should().BeEquivalentTo(pngHeader); + bytes[^8..].Should().BeEquivalentTo(pngFooter); + } + + [Test] + public void Encode_NullImage_ReturnsFailureResult() + { + var result = _encoder.Encode(null!); + + result.IsSuccess.Should().BeFalse(); + result.Error.Should().NotBeNullOrEmpty(); + } + + [TearDown] + public void Cleanup() + { + _testImage.Dispose(); + } +} \ No newline at end of file diff --git a/TagsCloudTests/RendererTests.cs b/TagsCloudTests/RendererTests.cs new file mode 100644 index 000000000..342eacc1b --- /dev/null +++ b/TagsCloudTests/RendererTests.cs @@ -0,0 +1,113 @@ +using Microsoft.Extensions.Logging; +using Moq; +using TagsCloudContainerCore.Models; +using TagsCloudContainerCore.Models.Graphics; +using TagsCloudContainerCore.Renderer; + +namespace TagsCloudTests; + +[TestFixture] +public class RendererTests +{ + private Mock> _loggerMock; + private Renderer _renderer; + + [SetUp] + public void Setup() + { + _loggerMock = new Mock>(); + _renderer = new Renderer(_loggerMock.Object); + } + + [Test] + public void RenderingScale_ShouldThrowException_WhenSetToZeroOrNegative() + { + var zeroFunc = () => new RendererConfig { RenderingScale = 0 }; + var negativeFunc = () => new RendererConfig { RenderingScale = -1 }; + + zeroFunc.Should().Throw(); + negativeFunc.Should().Throw(); + } + + [Test] + public void DrawTags_WithEmptyCollection_ShouldReturnSuccessResultWithEmptyImage() + { + var tags = Array.Empty(); + + var result = _renderer.DrawTags(tags); + + result.IsSuccess.Should().BeFalse(); + } + + [Test] + public void DrawTags_ShouldReturnSuccessResultWithCorrectImage_WhenSingleTag() + { + var tag = new Tag + { + Text = "", + FontSize = 12, + BBox = new Rectangle(0, 0, 100, 20) + }; + + var result = _renderer.DrawTags([tag]); + + result.IsSuccess.Should().BeTrue(); + var image = result.GetValueOrThrow(); + image.Width.Should().Be(100); + image.Height.Should().Be(20); + } + + [Test] + public void DrawTags_ShouldReturnSuccessResultWithScaledImage() + { + var tag = new Tag + { + Text = "", + FontSize = 12, + BBox = new Rectangle(0, 0, 100, 20) + }; + _renderer = new Renderer(_loggerMock.Object) { Config = new RendererConfig { RenderingScale = 2 } }; + + var result = _renderer.DrawTags([tag]); + + result.IsSuccess.Should().BeTrue(); + var image = result.GetValueOrThrow(); + image.Width.Should().Be(200); + image.Height.Should().Be(40); + } + + [Test] + public void DrawTags_ShouldReturnSuccessResultWithCorrectBounds_WithMultipleTags() + { + var tags = new[] + { + new Tag { Text = "", FontSize = 12, BBox = new Rectangle(0, 0, 50, 20) }, + new Tag { Text = "", FontSize = 12, BBox = new Rectangle(60, 0, 110, 20) } + }; + + var result = _renderer.DrawTags(tags); + + result.IsSuccess.Should().BeTrue(); + var image = result.GetValueOrThrow(); + image.Width.Should().Be(110); + image.Height.Should().Be(20); + } + + [Test] + public void DrawTags_ShouldReturnSuccessResultWithImage_WhenTagsHaveNegativeCoordinates() + { + var tag = new Tag + { + Text = "", + FontSize = 12, + BBox = new Rectangle(-50, -50, 50, 50) + }; + + var result = _renderer.DrawTags([tag]); + + result.IsSuccess.Should().BeTrue(); + var image = result.GetValueOrThrow(); + image.Width.Should().Be(100); + image.Height.Should().Be(100); + } +} \ No newline at end of file diff --git a/TagsCloudTests/TagsCloudTests.csproj b/TagsCloudTests/TagsCloudTests.csproj new file mode 100644 index 000000000..413677694 --- /dev/null +++ b/TagsCloudTests/TagsCloudTests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/fp.pptx b/fp.pptx deleted file mode 100644 index 2e616f576..000000000 Binary files a/fp.pptx and /dev/null differ diff --git a/fp.sln b/fp.sln index d104ab530..49bdd90ad 100644 --- a/fp.sln +++ b/fp.sln @@ -1,14 +1,10 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSenderRailway", "FileSenderRailway\FileSenderRailway.csproj", "{D979A1EA-516A-46BC-BE6C-8845CA10853D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudTests", "TagsCloudTests\TagsCloudTests.csproj", "{31B0729B-3264-4753-A918-347EB8A82EB6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErrorHandling", "ErrorHandling\ErrorHandling.csproj", "{66FAF276-533D-4733-AB2E-A9905D678CF6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudContainerCore", "TagsCloudContainerCore\TagsCloudContainerCore.csproj", "{CFC8C9BF-25D4-4757-B1F8-52A7FED5368D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{754C1CC8-A8B6-46C6-B35C-8A43B80111A0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Summator", "Samples\Summator\Summator.csproj", "{C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConwaysGameOfLife", "Samples\ConwaysGameOfLife\ConwaysGameOfLife.csproj", "{4B77EC28-5FB5-4095-B3D7-127F5C488D6E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudContainerCLI", "TagsCloudContainerCLI\TagsCloudContainerCLI.csproj", "{C62A8C76-3C40-4485-A7FA-1073CB3A31E6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -16,25 +12,19 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D979A1EA-516A-46BC-BE6C-8845CA10853D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D979A1EA-516A-46BC-BE6C-8845CA10853D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D979A1EA-516A-46BC-BE6C-8845CA10853D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D979A1EA-516A-46BC-BE6C-8845CA10853D}.Release|Any CPU.Build.0 = Release|Any CPU - {66FAF276-533D-4733-AB2E-A9905D678CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {66FAF276-533D-4733-AB2E-A9905D678CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {66FAF276-533D-4733-AB2E-A9905D678CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {66FAF276-533D-4733-AB2E-A9905D678CF6}.Release|Any CPU.Build.0 = Release|Any CPU - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1}.Release|Any CPU.Build.0 = Release|Any CPU - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E}.Release|Any CPU.Build.0 = Release|Any CPU + {31B0729B-3264-4753-A918-347EB8A82EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31B0729B-3264-4753-A918-347EB8A82EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31B0729B-3264-4753-A918-347EB8A82EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31B0729B-3264-4753-A918-347EB8A82EB6}.Release|Any CPU.Build.0 = Release|Any CPU + {CFC8C9BF-25D4-4757-B1F8-52A7FED5368D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFC8C9BF-25D4-4757-B1F8-52A7FED5368D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFC8C9BF-25D4-4757-B1F8-52A7FED5368D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFC8C9BF-25D4-4757-B1F8-52A7FED5368D}.Release|Any CPU.Build.0 = Release|Any CPU + {C62A8C76-3C40-4485-A7FA-1073CB3A31E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C62A8C76-3C40-4485-A7FA-1073CB3A31E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C62A8C76-3C40-4485-A7FA-1073CB3A31E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C62A8C76-3C40-4485-A7FA-1073CB3A31E6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {C33F3A5E-A1ED-4657-9B35-968A4CB23AA1} = {754C1CC8-A8B6-46C6-B35C-8A43B80111A0} - {4B77EC28-5FB5-4095-B3D7-127F5C488D6E} = {754C1CC8-A8B6-46C6-B35C-8A43B80111A0} EndGlobalSection EndGlobal