diff --git a/GitVersion.yml b/GitVersion.yml index 3f9ebba..c967cdc 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 1.0.1 +next-version: 1.1.0 tag-prefix: '[vV]' mode: ContinuousDeployment branches: diff --git a/LICENSE b/LICENSE index e3a0025..e6791d9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Code Shayk +Copyright (c) 2025 CØDE SHΔYK Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Parsley.md b/Parsley.md index 7defa96..1007104 100644 --- a/Parsley.md +++ b/Parsley.md @@ -15,8 +15,15 @@ Please see below. ``` public interface IParser { - public T[] Parse(string filepath) where T : IFileLine, new(); - public T[] Parse(string[] lines) where T : IFileLine, new(); + T[] Parse(string filepath) where T : IFileLine, new(); + T[] Parse(string[] lines) where T : IFileLine, new(); + T[] Parse(Stream stream) where T : IFileLine, new(); + T[] Parse(byte[] bytes) where T : IFileLine, new(); + + T[] ParseAsync(string filepath) where T : IFileLine, new(); + T[] ParseAsync(string[] lines) where T : IFileLine, new(); + T[] ParseAsync(Stream stream) where T : IFileLine, new(); + T[] ParseAsync(byte[] bytes) where T : IFileLine, new(); } ``` To initialise `Parser` class you could do it manually or use dependency injection as shown below. The parser class has parameterised constructor that takes the delimiter character to initialise the instance. Default character is ',' (comma) to initialise the parser for a CSV file parsing. diff --git a/README.md b/README.md index b9ee849..2584c58 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ninja Parsley.Net v1.0.1 +# ninja Parsley.Net v1.1.0 [![NuGet version](https://badge.fury.io/nu/Parsley.Net.svg)](https://badge.fury.io/nu/Parsley.Net) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/CodeShayk/Parsley.Net/blob/master/LICENSE.md) [![GitHub Release](https://img.shields.io/github/v/release/CodeShayk/Parsley.Net?logo=github&sort=semver)](https://github.com/CodeShayk/Parsley.Net/releases/latest) [![master-build](https://github.com/CodeShayk/parsley.net/actions/workflows/Master-Build.yml/badge.svg)](https://github.com/CodeShayk/parsley.net/actions/workflows/Master-Build.yml) @@ -36,16 +36,78 @@ NuGet\Install-Package Parsley.Net ``` ### ii. Implementation: Using Parsley.Net #### Step 1. Initialise and use Parser class. -`Parser` is an implementation of `IParser` interface that provides methods for +`Parser` is an implementation of `IParser` interface that provides sync and async methods for - parsing content of a file by specifying the file path - parsing an array of delimiter separated strings +- parsing a stream of delimiter separated strings +- parsing byte array of delimiter separated strings Please see below. ``` public interface IParser { - public T[] Parse(string filepath) where T : IFileLine, new(); - public T[] Parse(string[] lines) where T : IFileLine, new(); + /// + /// Parses a file at the specified filepath into an array of objects of type T. + /// + /// + /// + /// + T[] Parse(string filepath) where T : IFileLine, new(); + + /// + /// Parses an array of delimiter seperated strings into an array of objects of type T. + /// + /// + /// + /// + T[] Parse(string[] lines) where T : IFileLine, new(); + + /// + /// Parses a stream of delimiter separated records into an array of objects of type T. + /// + /// + /// + /// + T[] Parse(Stream stream) where T : IFileLine, new(); + + /// + /// Parses an array of bytes of delimiter separated records into an array of objects of type T. + /// + /// + /// + /// + T[] Parse(byte[] bytes) where T : IFileLine, new(); + + /// + /// Asynchronously parses a file at the specified filepath into an array of objects of type T. + /// + /// + /// + /// + Task ParseAsync(string filepath) where T : IFileLine, new(); + + /// + /// Asynchronously parses an array of delimiter separated strings into an array of objects of type T. + /// + /// + /// + /// + Task ParseAsync(string[] lines) where T : IFileLine, new(); + + /// + /// Asynchronously parses a stream of delimiter separated strings into an array of objects of type T. + /// + /// + /// + /// + Task ParseAsync(Stream stream) where T : IFileLine, new(); + /// + /// Asynchronously parses an array of bytes of delimiter separated records into an array of objects of type T. + /// + /// + /// + /// + Task ParseAsync(byte[] bytes) where T : IFileLine, new(); } ``` To initialise `Parser` class you could do it manually or use dependency injection as shown below. The parser class has parameterised constructor that takes the delimiter character to initialise the instance. Default character is ',' (comma) to initialise the parser for a CSV file parsing. diff --git a/src/Parsley/IParser.cs b/src/Parsley/IParser.cs index 5925628..6bba956 100644 --- a/src/Parsley/IParser.cs +++ b/src/Parsley/IParser.cs @@ -1,9 +1,72 @@ +using System.IO; +using System.Threading.Tasks; + namespace parsley { public interface IParser { + /// + /// Parses a file at the specified filepath into an array of objects of type T. + /// + /// + /// + /// T[] Parse(string filepath) where T : IFileLine, new(); - T[] Parse(string[] lines) where T : IFileLine, new(); + /// + /// Parses an array of delimiter seperated strings into an array of objects of type T. + /// + /// + /// + /// + T[] Parse(string[] lines) where T : IFileLine, new(); + + /// + /// Parses a stream of delimiter separated records into an array of objects of type T. + /// + /// + /// + /// + T[] Parse(Stream stream) where T : IFileLine, new(); + + /// + /// Parses an array of bytes of delimiter separated records into an array of objects of type T. + /// + /// + /// + /// + T[] Parse(byte[] bytes) where T : IFileLine, new(); + + /// + /// Asynchronously parses a file at the specified filepath into an array of objects of type T. + /// + /// + /// + /// + Task ParseAsync(string filepath) where T : IFileLine, new(); + + /// + /// Asynchronously parses an array of delimiter separated strings into an array of objects of type T. + /// + /// + /// + /// + Task ParseAsync(string[] lines) where T : IFileLine, new(); + + /// + /// Asynchronously parses a stream of delimiter separated strings into an array of objects of type T. + /// + /// + /// + /// + Task ParseAsync(Stream stream) where T : IFileLine, new(); + + /// + /// Asynchronously parses an array of bytes of delimiter separated records into an array of objects of type T. + /// + /// + /// + /// + Task ParseAsync(byte[] bytes) where T : IFileLine, new(); } } \ No newline at end of file diff --git a/src/Parsley/Parser.cs b/src/Parsley/Parser.cs index f9bd038..2696b67 100644 --- a/src/Parsley/Parser.cs +++ b/src/Parsley/Parser.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Threading.Tasks; namespace parsley @@ -77,7 +78,7 @@ private string[] ReadToLines(string path) } return lines.ToArray(); - } + } private T ParseLine(string line) where T : IFileLine, new() { @@ -163,5 +164,94 @@ private string[] GetDelimiterSeparatedValues(string line) .ToArray(); return values; } + + public T[] Parse(Stream stream) where T : IFileLine, new() + { + if (stream == null || stream.Length == 0) + return Array.Empty(); + + var lines = new List(); + using (var reader = new StreamReader(stream, Encoding.UTF8)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + var trimmedLine = line.Trim(); + if (!string.IsNullOrWhiteSpace(trimmedLine)) + lines.Add(trimmedLine); + line = null; + } + } + + return lines.Any() ? Parse(lines.ToArray()) : Array.Empty(); + } + + public T[] Parse(byte[] bytes) where T : IFileLine, new() + { + if (bytes == null || bytes.Length == 0) + return Array.Empty(); + + return Parse(new MemoryStream(bytes)); + } + + public async Task ParseAsync(string filepath) where T : IFileLine, new() + { + if (string.IsNullOrEmpty(filepath) || !File.Exists(filepath)) + return Array.Empty(); + + var lines = await Task.Run(() => ReadToLines(filepath)); + + return await ParseAsync(lines); + } + + public async Task ParseAsync(string[] lines) where T : IFileLine, new() + { + if (lines == null || lines.Length == 0) + return Array.Empty(); + + var list = new T[lines.Length]; + var index = 0; + var inputs = lines.Select(line => new { Line = line, Index = index++ }); + + foreach (var input in inputs) + { + if (string.IsNullOrWhiteSpace(input.Line)) + continue; + + var parsedLine = await Task.Run(() => ParseLine(input.Line)); + + if (parsedLine != null) + { + parsedLine.Index = input.Index; + list[parsedLine.Index] = parsedLine; + } + } + return list; + } + + public async Task ParseAsync(Stream stream) where T : IFileLine, new() + { + var lines = new List(); + using (var reader = new StreamReader(stream, Encoding.UTF8)) + { + string line; + while ((line = await reader.ReadLineAsync()) != null) + { + var trimmedLine = line.Trim(); + if (!string.IsNullOrWhiteSpace(trimmedLine)) + lines.Add(trimmedLine); + } + } + + return lines.Any() ? await ParseAsync(lines.ToArray()) : Array.Empty(); + } + + public async Task ParseAsync(byte[] bytes) where T : IFileLine, new() + { + if (bytes == null || bytes.Length == 0) + return Array.Empty(); + + return await ParseAsync(new MemoryStream(bytes)); + } } } \ No newline at end of file diff --git a/src/Parsley/Parsley.csproj b/src/Parsley/Parsley.csproj index 16d3b24..f4048fb 100644 --- a/src/Parsley/Parsley.csproj +++ b/src/Parsley/Parsley.csproj @@ -1,7 +1,7 @@  - net462; netstandard2.0;netstandard2.1; net9.0 + net462;netstandard2.0;netstandard2.1;net9.0 disable Parsley.Net CodeShayk @@ -20,9 +20,10 @@ https://github.com/CodeShayk/Parsley.Net/wiki https://github.com/CodeShayk/Parsley.Net - v1.0.1 - Targets .Net9.0, .NetStandard2.1, .NetStandard2.0, and .NetFramework4.6.4.
-* Includes core functionality for parsing delimiter separated files.
- 1.0.1 + v1.1.0 - Targets .Net9.0, .NetStandard2.1, .NetStandard2.0, and .NetFramework4.6.4.
+* Includes core functionality for parsing delimiter separated files. +* Provided Sync and Async parsing methods + 1.1.0 True Parsley.Net
diff --git a/tests/Parsley.Tests/ParserFixture.cs b/tests/Parsley.Tests/ParserFixture.cs index f9cf873..1254b31 100644 --- a/tests/Parsley.Tests/ParserFixture.cs +++ b/tests/Parsley.Tests/ParserFixture.cs @@ -1,6 +1,7 @@ using parsley; using Microsoft.Extensions.DependencyInjection; using Parsley.Tests.FileLines; +using System.Text; namespace Parsley.Tests { @@ -121,5 +122,193 @@ public void TestParseForInvalidFileLineWithNoColumnAttributesShouldReturnError() Assert.That(result[0].Errors, Is.Not.Empty); } + + [Test] + public void TestParseWithStreamInputShouldReturnCorrectlyParsedArray() + { + var lines = new[] + { + "GB-01|Bob Marley|True|Free", + "UH-02|John Walsh McKinsey|False|Paid" + }; + + parser = new Parser('|'); + + var parsed = parser.Parse(new MemoryStream(Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, lines)))); + + Assert.That(parsed.Length, Is.EqualTo(2)); + + Assert.That(parsed[0].Code.Batch, Is.EqualTo("GB")); + Assert.That(parsed[0].Code.SerialNo, Is.EqualTo(1)); + Assert.That(parsed[0].Name.FirstName, Is.EqualTo("Bob")); + Assert.That(parsed[0].Name.Surname, Is.EqualTo("Marley")); + Assert.That(parsed[0].IsActive, Is.EqualTo(true)); + Assert.That(parsed[0].Subcription, Is.EqualTo(Subcription.Free)); + Assert.That(parsed[0].Errors, Is.Empty); + + Assert.That(parsed[1].Code.Batch, Is.EqualTo("UH")); + Assert.That(parsed[1].Code.SerialNo, Is.EqualTo(2)); + Assert.That(parsed[1].Name.FirstName, Is.EqualTo("John Walsh")); + Assert.That(parsed[1].Name.Surname, Is.EqualTo("McKinsey")); + Assert.That(parsed[1].IsActive, Is.EqualTo(false)); + Assert.That(parsed[1].Subcription, Is.EqualTo(Subcription.Paid)); + Assert.That(parsed[1].Errors, Is.Empty); + } + + [Test] + public void TestParseWithByteArrayInputShouldReturnCorrectlyParsedArray() + { + var lines = new[] + { + "GB-01|Bob Marley|True|Free", + "UH-02|John Walsh McKinsey|False|Paid" + }; + + parser = new Parser('|'); + + var parsed = parser.Parse(Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, lines))); + + Assert.That(parsed.Length, Is.EqualTo(2)); + + Assert.That(parsed[0].Code.Batch, Is.EqualTo("GB")); + Assert.That(parsed[0].Code.SerialNo, Is.EqualTo(1)); + Assert.That(parsed[0].Name.FirstName, Is.EqualTo("Bob")); + Assert.That(parsed[0].Name.Surname, Is.EqualTo("Marley")); + Assert.That(parsed[0].IsActive, Is.EqualTo(true)); + Assert.That(parsed[0].Subcription, Is.EqualTo(Subcription.Free)); + Assert.That(parsed[0].Errors, Is.Empty); + + Assert.That(parsed[1].Code.Batch, Is.EqualTo("UH")); + Assert.That(parsed[1].Code.SerialNo, Is.EqualTo(2)); + Assert.That(parsed[1].Name.FirstName, Is.EqualTo("John Walsh")); + Assert.That(parsed[1].Name.Surname, Is.EqualTo("McKinsey")); + Assert.That(parsed[1].IsActive, Is.EqualTo(false)); + Assert.That(parsed[1].Subcription, Is.EqualTo(Subcription.Paid)); + Assert.That(parsed[1].Errors, Is.Empty); + } + + [Test] + public async Task TestParseAsyncWithFilePathShouldReturnCorrectlyParsedArray() + { + var filePath = Path.Combine(Environment.CurrentDirectory, "TestFile.txt"); + + parser = new Parser(); + + var parsed = await parser.ParseAsync(filePath); + + Assert.That(parsed.Length, Is.EqualTo(2)); + + Assert.That(parsed[0].Code.Batch, Is.EqualTo("GB")); + Assert.That(parsed[0].Code.SerialNo, Is.EqualTo(1)); + Assert.That(parsed[0].Name.FirstName, Is.EqualTo("Bob")); + Assert.That(parsed[0].Name.Surname, Is.EqualTo("Marley")); + Assert.That(parsed[0].IsActive, Is.EqualTo(true)); + Assert.That(parsed[0].Subcription, Is.EqualTo(Subcription.Free)); + Assert.That(parsed[0].Errors, Is.Empty); + + Assert.That(parsed[1].Code.Batch, Is.EqualTo("UG")); + Assert.That(parsed[1].Code.SerialNo, Is.EqualTo(2)); + Assert.That(parsed[1].Name.FirstName, Is.EqualTo("John Walsh")); + Assert.That(parsed[1].Name.Surname, Is.EqualTo("McKinsey")); + Assert.That(parsed[1].IsActive, Is.EqualTo(false)); + Assert.That(parsed[1].Subcription, Is.EqualTo(Subcription.Paid)); + Assert.That(parsed[1].Errors, Is.Empty); + } + + [Test] + public async Task TestParseAsyncWithStringArrayInputShouldReturnCorrectlyParsedArray() + { + var lines = new[] + { + "GB-01|Bob Marley|True|Free", + "UH-02|John Walsh McKinsey|False|Paid" + }; + + parser = new Parser('|'); + + var parsed = await parser.ParseAsync(lines); + + Assert.That(parsed.Length, Is.EqualTo(2)); + + Assert.That(parsed[0].Code.Batch, Is.EqualTo("GB")); + Assert.That(parsed[0].Code.SerialNo, Is.EqualTo(1)); + Assert.That(parsed[0].Name.FirstName, Is.EqualTo("Bob")); + Assert.That(parsed[0].Name.Surname, Is.EqualTo("Marley")); + Assert.That(parsed[0].IsActive, Is.EqualTo(true)); + Assert.That(parsed[0].Subcription, Is.EqualTo(Subcription.Free)); + Assert.That(parsed[0].Errors, Is.Empty); + + Assert.That(parsed[1].Code.Batch, Is.EqualTo("UH")); + Assert.That(parsed[1].Code.SerialNo, Is.EqualTo(2)); + Assert.That(parsed[1].Name.FirstName, Is.EqualTo("John Walsh")); + Assert.That(parsed[1].Name.Surname, Is.EqualTo("McKinsey")); + Assert.That(parsed[1].IsActive, Is.EqualTo(false)); + Assert.That(parsed[1].Subcription, Is.EqualTo(Subcription.Paid)); + Assert.That(parsed[1].Errors, Is.Empty); + } + + [Test] + public async Task TestParseAsyncWithStreamInputShouldReturnCorrectlyParsedArray() + { + var lines = new[] + { + "GB-01|Bob Marley|True|Free", + "UH-02|John Walsh McKinsey|False|Paid" + }; + + parser = new Parser('|'); + + var parsed = await parser.ParseAsync(new MemoryStream(Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, lines)))); + + Assert.That(parsed.Length, Is.EqualTo(2)); + + Assert.That(parsed[0].Code.Batch, Is.EqualTo("GB")); + Assert.That(parsed[0].Code.SerialNo, Is.EqualTo(1)); + Assert.That(parsed[0].Name.FirstName, Is.EqualTo("Bob")); + Assert.That(parsed[0].Name.Surname, Is.EqualTo("Marley")); + Assert.That(parsed[0].IsActive, Is.EqualTo(true)); + Assert.That(parsed[0].Subcription, Is.EqualTo(Subcription.Free)); + Assert.That(parsed[0].Errors, Is.Empty); + + Assert.That(parsed[1].Code.Batch, Is.EqualTo("UH")); + Assert.That(parsed[1].Code.SerialNo, Is.EqualTo(2)); + Assert.That(parsed[1].Name.FirstName, Is.EqualTo("John Walsh")); + Assert.That(parsed[1].Name.Surname, Is.EqualTo("McKinsey")); + Assert.That(parsed[1].IsActive, Is.EqualTo(false)); + Assert.That(parsed[1].Subcription, Is.EqualTo(Subcription.Paid)); + Assert.That(parsed[1].Errors, Is.Empty); + } + + [Test] + public async Task TestParseAsyncWithByteArrayInputShouldReturnCorrectlyParsedArray() + { + var lines = new[] + { + "GB-01|Bob Marley|True|Free", + "UH-02|John Walsh McKinsey|False|Paid" + }; + + parser = new Parser('|'); + + var parsed = await parser.ParseAsync(Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, lines))); + + Assert.That(parsed.Length, Is.EqualTo(2)); + + Assert.That(parsed[0].Code.Batch, Is.EqualTo("GB")); + Assert.That(parsed[0].Code.SerialNo, Is.EqualTo(1)); + Assert.That(parsed[0].Name.FirstName, Is.EqualTo("Bob")); + Assert.That(parsed[0].Name.Surname, Is.EqualTo("Marley")); + Assert.That(parsed[0].IsActive, Is.EqualTo(true)); + Assert.That(parsed[0].Subcription, Is.EqualTo(Subcription.Free)); + Assert.That(parsed[0].Errors, Is.Empty); + + Assert.That(parsed[1].Code.Batch, Is.EqualTo("UH")); + Assert.That(parsed[1].Code.SerialNo, Is.EqualTo(2)); + Assert.That(parsed[1].Name.FirstName, Is.EqualTo("John Walsh")); + Assert.That(parsed[1].Name.Surname, Is.EqualTo("McKinsey")); + Assert.That(parsed[1].IsActive, Is.EqualTo(false)); + Assert.That(parsed[1].Subcription, Is.EqualTo(Subcription.Paid)); + Assert.That(parsed[1].Errors, Is.Empty); + } } } \ No newline at end of file