mirror of
https://github.com/SabreTools/SabreTools.Matching.git
synced 2026-02-04 21:30:18 +00:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77280e8da1 | ||
|
|
3d05135a81 | ||
|
|
b2dfffbc92 | ||
|
|
036ee4246b | ||
|
|
8e3bcb9015 | ||
|
|
8b1ac53ccf | ||
|
|
0c9e255d48 | ||
|
|
1227488572 | ||
|
|
bd878cf1a1 | ||
|
|
b436b64c3a | ||
|
|
6f6d071a79 | ||
|
|
69130a6e9f | ||
|
|
93847420a5 | ||
|
|
76e13a47ec | ||
|
|
4f56d716d4 | ||
|
|
cd7e89f869 | ||
|
|
0810b4b083 | ||
|
|
ba0b126714 | ||
|
|
48cdcf96bf | ||
|
|
39ca26bc28 | ||
|
|
5f2940388e | ||
|
|
bac0913b35 | ||
|
|
c52973418d | ||
|
|
c97fe92a3e | ||
|
|
176a892993 | ||
|
|
3bb6a6f5c1 | ||
|
|
efd26133aa | ||
|
|
b1d95652ea | ||
|
|
ea52117717 | ||
|
|
50a0c62560 | ||
|
|
d98629d22b | ||
|
|
2d767122d0 | ||
|
|
e4d9848756 | ||
|
|
c96185d550 | ||
|
|
c1648ccb71 | ||
|
|
31fff83114 | ||
|
|
607b9d30b0 | ||
|
|
cfb82f055c | ||
|
|
2057baeded | ||
|
|
bed920b97c | ||
|
|
cdf2dd6589 | ||
|
|
f10585aa32 | ||
|
|
2d4f974623 | ||
|
|
9490dabbd6 | ||
|
|
425f846e26 | ||
|
|
a180499090 | ||
|
|
c7633b1a53 | ||
|
|
e88771b11f | ||
|
|
e596c7c2c1 | ||
|
|
910e963d13 | ||
|
|
751519fadb | ||
|
|
f86f565136 | ||
|
|
1ad45c6d59 | ||
|
|
38796776ee | ||
|
|
c60f587b69 | ||
|
|
8844ba0ae3 | ||
|
|
cdd41e8bec | ||
|
|
655214503f | ||
|
|
5801424db7 | ||
|
|
afb9ab3e4e | ||
|
|
85006a71bf | ||
|
|
90bb82306b | ||
|
|
c38e10e55b | ||
|
|
e96790d875 | ||
|
|
bf23967f74 | ||
|
|
8debf24a44 | ||
|
|
333b5be76c | ||
|
|
b0fdff2d6e | ||
|
|
3b0e74b9f4 |
@@ -1,4 +1,4 @@
|
||||
name: Nuget Pack
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -16,25 +16,22 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Run tests
|
||||
run: dotnet test
|
||||
|
||||
- name: Pack
|
||||
run: dotnet pack
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'Nuget Package'
|
||||
path: 'SabreTools.Matching/bin/Release/*.nupkg'
|
||||
- name: Run publish script
|
||||
run: ./publish-nix.sh
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: 'SabreTools.Matching/bin/Release/*.nupkg'
|
||||
artifacts: "*.nupkg,*.snupkg"
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
10
.github/workflows/check_pr.yml
vendored
10
.github/workflows/check_pr.yml
vendored
@@ -11,7 +11,13 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
run: dotnet build
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test
|
||||
|
||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2018-2025 Matt Nadareski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
10
README.MD
10
README.MD
@@ -1,5 +1,15 @@
|
||||
# SabreTools.Matching
|
||||
|
||||
[](https://github.com/SabreTools/SabreTools.Matching/actions/workflows/build_and_test.yml)
|
||||
|
||||
**NOTICE:** This library has been deprecated. All functionality formerly in this library is in [SabreTools.IO](https://github.com/SabreTools/SabreTools.IO) as of version 1.7.5.
|
||||
|
||||
This library comprises of code to perform search operations on both byte arrays and streams. There is also an implementation that allows searching for strings in a set of strings, usually used in the context of directory contents.
|
||||
|
||||
Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTools.Matching).
|
||||
|
||||
## Releases
|
||||
|
||||
For the most recent stable build, download the latest release here: [Releases Page](https://github.com/SabreTools/SabreTools.Matching/releases)
|
||||
|
||||
For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/SabreTools.Matching/releases/rolling)
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ namespace SabreTools.Matching.Test.Compare
|
||||
public class NaturalComparerTests
|
||||
{
|
||||
[Fact]
|
||||
public void NaturalComparerListSortTest()
|
||||
public void ListSort_Numeric()
|
||||
{
|
||||
// Setup arrays
|
||||
string[] sortable = ["0", "100", "5", "2", "1000"];
|
||||
@@ -22,17 +22,17 @@ namespace SabreTools.Matching.Test.Compare
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NaturalReversedComparerListSortTest()
|
||||
public void ListSort_Mixed()
|
||||
{
|
||||
// Setup arrays
|
||||
string[] sortable = ["0", "100", "5", "2", "1000"];
|
||||
string[] expected = ["1000", "100", "5", "2", "0"];
|
||||
string[] sortable = ["b3b", "c", "b", "a", "a1"];
|
||||
string[] expected = ["a", "a1", "b", "b3b", "c"];
|
||||
|
||||
// Run sorting on array
|
||||
Array.Sort(sortable, new NaturalReversedComparer());
|
||||
Array.Sort(sortable, new NaturalComparer());
|
||||
|
||||
// Check the output
|
||||
Assert.True(sortable.SequenceEqual(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,54 +6,61 @@ namespace SabreTools.Matching.Test.Compare
|
||||
public class NaturalComparerUtilTests
|
||||
{
|
||||
[Fact]
|
||||
public void CompareNumericBothNullTest()
|
||||
public void CompareNumeric_BothNull_Equal()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric(null, null);
|
||||
int actual = NaturalComparerUtil.ComparePaths(null, null);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericSingleNullTest()
|
||||
public void CompareNumeric_SingleNull_Ordered()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric(null, "notnull");
|
||||
int actual = NaturalComparerUtil.ComparePaths(null, "notnull");
|
||||
Assert.Equal(-1, actual);
|
||||
|
||||
actual = NaturalComparerUtil.CompareNumeric("notnull", null);
|
||||
actual = NaturalComparerUtil.ComparePaths("notnull", null);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericBothEqualTest()
|
||||
public void CompareNumeric_BothEqual_Equal()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric("notnull", "notnull");
|
||||
int actual = NaturalComparerUtil.ComparePaths("notnull", "notnull");
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericBothEqualWithPathTest()
|
||||
public void CompareNumeric_BothEqualWithPath_Equal()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric("notnull/file.ext", "notnull/file.ext");
|
||||
int actual = NaturalComparerUtil.ComparePaths("notnull/file.ext", "notnull/file.ext");
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericNumericNonDecimalStringTest()
|
||||
public void CompareNumeric_BothEqualWithAltPath_Equal()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric("100", "10");
|
||||
int actual = NaturalComparerUtil.ComparePaths("notnull/file.ext", "notnull\\file.ext");
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumeric_NumericNonDecimalString_Ordered()
|
||||
{
|
||||
int actual = NaturalComparerUtil.ComparePaths("100", "10");
|
||||
Assert.Equal(1, actual);
|
||||
|
||||
actual = NaturalComparerUtil.CompareNumeric("10", "100");
|
||||
actual = NaturalComparerUtil.ComparePaths("10", "100");
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericNumericDecimalStringTest()
|
||||
public void CompareNumeric_NumericDecimalString_Ordered()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric("100.100", "100.10");
|
||||
int actual = NaturalComparerUtil.ComparePaths("100.100", "100.10");
|
||||
Assert.Equal(1, actual);
|
||||
|
||||
actual = NaturalComparerUtil.CompareNumeric("100.10", "100.100");
|
||||
actual = NaturalComparerUtil.ComparePaths("100.10", "100.100");
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using SabreTools.Matching.Compare;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test.Compare
|
||||
{
|
||||
public class NaturalReversedComparerTests
|
||||
{
|
||||
[Fact]
|
||||
public void ListSort_Numeric()
|
||||
{
|
||||
// Setup arrays
|
||||
string[] sortable = ["0", "100", "5", "2", "1000"];
|
||||
string[] expected = ["1000", "100", "5", "2", "0"];
|
||||
|
||||
// Run sorting on array
|
||||
Array.Sort(sortable, new NaturalReversedComparer());
|
||||
|
||||
// Check the output
|
||||
Assert.True(sortable.SequenceEqual(expected));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListSort_Mixed()
|
||||
{
|
||||
// Setup arrays
|
||||
string[] sortable = ["b3b", "c", "b", "a", "a1"];
|
||||
string[] expected = ["c", "b3b", "b", "a1", "a"];
|
||||
|
||||
// Run sorting on array
|
||||
Array.Sort(sortable, new NaturalReversedComparer());
|
||||
|
||||
// Check the output
|
||||
Assert.True(sortable.SequenceEqual(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
202
SabreTools.Matching.Test/Content/ContentMatchSetTests.cs
Normal file
202
SabreTools.Matching.Test/Content/ContentMatchSetTests.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Matching.Content;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test.Content
|
||||
{
|
||||
public class ContentMatchSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidNeedle_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet(Array.Empty<byte>(), "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet(Array.Empty<byte>(), ArrayVersionMock, "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet(Array.Empty<byte>(), StreamVersionMock, "name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidNeedles_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet([], "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet([], ArrayVersionMock, "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatchSet([], StreamVersionMock, "name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenericConstructor_NoDelegates()
|
||||
{
|
||||
var needles = new List<ContentMatch> { new byte[] { 0x01, 0x02, 0x03, 0x04 } };
|
||||
var cms = new ContentMatchSet(needles, "name");
|
||||
Assert.Null(cms.GetArrayVersion);
|
||||
Assert.Null(cms.GetStreamVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayConstructor_SingleDelegate()
|
||||
{
|
||||
var needles = new List<ContentMatch> { new byte[] { 0x01, 0x02, 0x03, 0x04 } };
|
||||
var cms = new ContentMatchSet(needles, ArrayVersionMock, "name");
|
||||
Assert.NotNull(cms.GetArrayVersion);
|
||||
Assert.Null(cms.GetStreamVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamConstructor_SingleDelegate()
|
||||
{
|
||||
var needles = new List<ContentMatch> { new byte[] { 0x01, 0x02, 0x03, 0x04 } };
|
||||
var cms = new ContentMatchSet(needles, StreamVersionMock, "name");
|
||||
Assert.Null(cms.GetArrayVersion);
|
||||
Assert.NotNull(cms.GetStreamVersion);
|
||||
}
|
||||
|
||||
#region Array
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_NullArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll((byte[]?)null);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_EmptyArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll([]);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MatchingArray_Matches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll([0x01, 0x02, 0x03, 0x04]);
|
||||
int position = Assert.Single(actual);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MismatchedArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll([0x01, 0x03]);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_NullArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny((byte[]?)null);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_EmptyArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny([]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MatchingArray_Matches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny([0x01, 0x02, 0x03, 0x04]);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MismatchedArray_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny([0x01, 0x03]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_NullStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll((Stream?)null);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_EmptyStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll(new MemoryStream());
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MatchingStream_Matches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll(new MemoryStream([0x01, 0x02, 0x03, 0x04]));
|
||||
int position = Assert.Single(actual);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MismatchedStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
var actual = cms.MatchesAll([0x01, 0x03]);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_NullStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny((Stream?)null);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_EmptyStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny(new MemoryStream());
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MatchingStream_Matches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny(new MemoryStream([0x01, 0x02, 0x03, 0x04]));
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MismatchedStream_NoMatches()
|
||||
{
|
||||
var cms = new ContentMatchSet(new byte[] { 0x01, 0x02, 0x03, 0x04 }, "name");
|
||||
int actual = cms.MatchesAny([0x01, 0x03]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mock Delegates
|
||||
|
||||
/// <inheritdoc cref="GetArrayVersion"/>
|
||||
private static string? ArrayVersionMock(string path, byte[]? content, List<int> positions) => null;
|
||||
|
||||
/// <inheritdoc cref="GetStreamVersion"/>
|
||||
private static string? StreamVersionMock(string path, Stream? content, List<int> positions) => null;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
271
SabreTools.Matching.Test/Content/ContentMatchTests.cs
Normal file
271
SabreTools.Matching.Test/Content/ContentMatchTests.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.Matching.Content;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test.Content
|
||||
{
|
||||
public class ContentMatchTests
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidNeedle_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatch(Array.Empty<byte>()));
|
||||
Assert.Throws<InvalidDataException>(() => new ContentMatch(Array.Empty<byte?>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStart_ThrowsException()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new ContentMatch(new byte[1], start: -1));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new ContentMatch(new byte?[1], start: -1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidEnd_ThrowsException()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new ContentMatch(new byte[1], end: -2));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new ContentMatch(new byte?[1], end: -2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitOperatorArray_Success()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = (ContentMatch)needle;
|
||||
Assert.NotNull(cm);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitOperatorNullableArray_Success()
|
||||
{
|
||||
byte?[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = (ContentMatch)needle;
|
||||
Assert.NotNull(cm);
|
||||
}
|
||||
|
||||
#region Byte Array
|
||||
|
||||
[Fact]
|
||||
public void NullArray_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[1]);
|
||||
int actual = cm.Match((byte[]?)null);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[1]);
|
||||
int actual = cm.Match([]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LargerNeedleArray_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[2]);
|
||||
int actual = cm.Match(new byte[1]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMatchingArray_Match()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(needle);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMatchingArrayReverse_Match()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(needle, reverse: true);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMismatchedArray_NoMatch()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new byte[4]);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMismatchedArrayReverse_NoMatch()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new byte[4], reverse: true);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMatchingArray_Match()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02, 0x03, 0x04];
|
||||
byte[] needle = [0x02, 0x03];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMatchingArrayReverse_Match()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02, 0x03, 0x04];
|
||||
byte[] needle = [0x02, 0x03];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack, reverse: true);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMismatchedArray_NoMatch()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02, 0x03, 0x04];
|
||||
byte[] needle = [0x02, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMismatchedArrayReverse_NoMatch()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02, 0x03, 0x04];
|
||||
byte[] needle = [0x02, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack, reverse: true);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream
|
||||
|
||||
[Fact]
|
||||
public void NullStream_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[1]);
|
||||
int actual = cm.Match((Stream?)null);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[1]);
|
||||
int actual = cm.Match(new MemoryStream());
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LargerNeedleStream_NoMatch()
|
||||
{
|
||||
var cm = new ContentMatch(new byte?[2]);
|
||||
int actual = cm.Match(new MemoryStream(new byte[1]));
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMatchingStream_Match()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new MemoryStream(needle));
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMatchingStreamReverse_Match()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new MemoryStream(needle), reverse: true);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMismatchedStream_NoMatch()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new MemoryStream(new byte[4]));
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengthMismatchedStreamReverse_NoMatch()
|
||||
{
|
||||
byte[] needle = [0x01, 0x02, 0x03, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(new MemoryStream(new byte[4]), reverse: true);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMatchingStream_Match()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01, 0x02, 0x03, 0x04]);
|
||||
byte[] needle = [0x02, 0x03];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMatchingStreamReverse_Match()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01, 0x02, 0x03, 0x04]);
|
||||
byte[] needle = [0x02, 0x03];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack, reverse: true);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMismatchedStream_NoMatch()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01, 0x02, 0x03, 0x04]);
|
||||
byte[] needle = [0x02, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalLengthMismatchedStreamReverse_NoMatch()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01, 0x02, 0x03, 0x04]);
|
||||
byte[] needle = [0x02, 0x04];
|
||||
var cm = new ContentMatch(needle);
|
||||
|
||||
int actual = cm.Match(stack, reverse: true);
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
396
SabreTools.Matching.Test/ExtensionsTests.cs
Normal file
396
SabreTools.Matching.Test/ExtensionsTests.cs
Normal file
@@ -0,0 +1,396 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test
|
||||
{
|
||||
public class ExtensionsTests
|
||||
{
|
||||
#region Find All Positions
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
var positions = stack.FindAllPositions([0x01]);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
var positions = stack.FindAllPositions(Array.Empty<byte>());
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02]);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_InvalidStart_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02], start: -1);
|
||||
Assert.Empty(positions);
|
||||
|
||||
positions = stack.FindAllPositions([0x01, 0x02], start: 2);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_InvalidEnd_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02], end: -2);
|
||||
Assert.Empty(positions);
|
||||
|
||||
positions = stack.FindAllPositions([0x01, 0x02], end: 0);
|
||||
Assert.Empty(positions);
|
||||
|
||||
positions = stack.FindAllPositions([0x01, 0x02], end: 2);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02]);
|
||||
int position = Assert.Single(positions);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
var positions = stack.FindAllPositions([0x01, 0x02]);
|
||||
Assert.Empty(positions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindAllPositions_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
var positions = stack.FindAllPositions([0x01]);
|
||||
Assert.Equal(2, positions.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region First Position
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
int position = stack.FirstPosition([0x01]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.FirstPosition(Array.Empty<byte>());
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.FirstPosition([0x01, 0x02]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_InvalidStart_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.FirstPosition([0x01, 0x02], start: -1);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.FirstPosition([0x01, 0x02], start: 2);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_InvalidEnd_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.FirstPosition([0x01, 0x02], end: -2);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.FirstPosition([0x01, 0x02], end: 0);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.FirstPosition([0x01, 0x02], end: 2);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
int position = stack.FirstPosition([0x01, 0x02]);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
int position = stack.FirstPosition([0x01, 0x02]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstPosition_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
int position = stack.FirstPosition([0x01]);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Last Position
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
int position = stack.LastPosition([0x01]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.LastPosition(Array.Empty<byte>());
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.LastPosition([0x01, 0x02]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_InvalidStart_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.LastPosition([0x01, 0x02], start: -1);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.LastPosition([0x01, 0x02], start: 2);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_InvalidEnd_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
int position = stack.LastPosition([0x01, 0x02], end: -2);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.LastPosition([0x01, 0x02], end: 0);
|
||||
Assert.Equal(-1, position);
|
||||
|
||||
position = stack.LastPosition([0x01, 0x02], end: 2);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
int position = stack.LastPosition([0x01, 0x02]);
|
||||
Assert.Equal(0, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
int position = stack.LastPosition([0x01, 0x02]);
|
||||
Assert.Equal(-1, position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LastPosition_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
int position = stack.LastPosition([0x01]);
|
||||
Assert.Equal(1, position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equals Exactly
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
bool found = stack.EqualsExactly([0x01]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.EqualsExactly(Array.Empty<byte>());
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_ShorterNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
bool found = stack.EqualsExactly([0x01]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.EqualsExactly([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
bool found = stack.EqualsExactly([0x01, 0x02]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsExactly_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
bool found = stack.EqualsExactly([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Starts With
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
bool found = stack.StartsWith([0x01]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.StartsWith(Array.Empty<byte>());
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.StartsWith([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
bool found = stack.StartsWith([0x01, 0x02]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
bool found = stack.StartsWith([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartsWith_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
bool found = stack.StartsWith([0x01]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ends With
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
bool found = stack.EndsWith([0x01]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_EmptyNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.EndsWith(Array.Empty<byte>());
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_LongerNeedle_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
bool found = stack.StartsWith([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x02];
|
||||
bool found = stack.EndsWith([0x01, 0x02]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_Mismatch_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x03];
|
||||
bool found = stack.EndsWith([0x01, 0x02]);
|
||||
Assert.False(found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWith_Multiple_Matches()
|
||||
{
|
||||
byte[] stack = [0x01, 0x01];
|
||||
bool found = stack.EndsWith([0x01]);
|
||||
Assert.True(found);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
324
SabreTools.Matching.Test/MatchUtilTests.cs
Normal file
324
SabreTools.Matching.Test/MatchUtilTests.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Matching.Content;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test
|
||||
{
|
||||
public class MatchUtilTests
|
||||
{
|
||||
#region Array
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_NullStack_NoMatches()
|
||||
{
|
||||
byte[]? stack = null;
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_EmptyMatchSets_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets = [];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[] { 0x01 }, "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
string setName = Assert.Single(matches);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_PartialMatchingAny_Matches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets, any: true);
|
||||
string setName = Assert.Single(matches);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetAllMatches_PartialMatchingAll_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets, any: false);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_NullStack_NoMatches()
|
||||
{
|
||||
byte[]? stack = null;
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_EmptyStack_NoMatches()
|
||||
{
|
||||
byte[] stack = [];
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_EmptyMatchSets_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets = [];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_Matching_Matches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[] { 0x01 }, "name")];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_PartialMatchingAny_Matches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets, any: true);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayGetFirstMatch_PartialMatchingAll_NoMatches()
|
||||
{
|
||||
byte[] stack = [0x01];
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets, any: false);
|
||||
Assert.Null(setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExactSizeArrayMatch()
|
||||
{
|
||||
byte[] source = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
||||
byte?[] check = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
||||
string expected = "match";
|
||||
|
||||
var matchers = new List<ContentMatchSet>
|
||||
{
|
||||
new(check, expected),
|
||||
};
|
||||
|
||||
string? actual = MatchUtil.GetFirstMatch("testfile", source, matchers, any: false);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_NullStack_NoMatches()
|
||||
{
|
||||
Stream? stack = null;
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_EmptyStack_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream();
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_EmptyMatchSets_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets = [];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_Matching_Matches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[] { 0x01 }, "name")];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets);
|
||||
string setName = Assert.Single(matches);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_PartialMatchingAny_Matches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets, any: true);
|
||||
string setName = Assert.Single(matches);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetAllMatches_PartialMatchingAll_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
var matches = MatchUtil.GetAllMatches("file", stack, matchSets, any: false);
|
||||
Assert.Empty(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_NullStack_NoMatches()
|
||||
{
|
||||
Stream? stack = null;
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_EmptyStack_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream();
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[1], "name")];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_EmptyMatchSets_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets = [];
|
||||
string? match = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_Matching_Matches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets = [new ContentMatchSet(new byte[] { 0x01 }, "name")];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_PartialMatchingAny_Matches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets, any: true);
|
||||
Assert.Equal("name", setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamGetFirstMatch_PartialMatchingAll_NoMatches()
|
||||
{
|
||||
Stream stack = new MemoryStream([0x01]);
|
||||
List<ContentMatchSet> matchSets =
|
||||
[
|
||||
new ContentMatchSet([
|
||||
new byte[] { 0x00 },
|
||||
new ContentMatch([0x01]),
|
||||
], "name")
|
||||
];
|
||||
string? setName = MatchUtil.GetFirstMatch("file", stack, matchSets, any: false);
|
||||
Assert.Null(setName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExactSizeStreamMatch()
|
||||
{
|
||||
byte[] source = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
||||
var stream = new MemoryStream(source);
|
||||
|
||||
byte?[] check = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
||||
string expected = "match";
|
||||
|
||||
var matchers = new List<ContentMatchSet>
|
||||
{
|
||||
new(check, expected),
|
||||
};
|
||||
|
||||
string? actual = MatchUtil.GetFirstMatch("testfile", stream, matchers, any: false);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Path
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
22
SabreTools.Matching.Test/Paths/FilePathMatchTests.cs
Normal file
22
SabreTools.Matching.Test/Paths/FilePathMatchTests.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.IO;
|
||||
using SabreTools.Matching.Paths;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test.Paths
|
||||
{
|
||||
/// <remarks>
|
||||
/// All other test cases are covered by <see cref="PathMatchTests"/>
|
||||
/// </remarks>
|
||||
public class FilePathMatchTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConstructorFormatsNeedle()
|
||||
{
|
||||
string needle = "test";
|
||||
string expected = $"{Path.DirectorySeparatorChar}{needle}";
|
||||
|
||||
var fpm = new FilePathMatch(needle);
|
||||
Assert.Equal(expected, fpm.Needle);
|
||||
}
|
||||
}
|
||||
}
|
||||
186
SabreTools.Matching.Test/Paths/PathMatchSetTests.cs
Normal file
186
SabreTools.Matching.Test/Paths/PathMatchSetTests.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Matching.Paths;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test.Paths
|
||||
{
|
||||
public class PathMatchSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidNeedle_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatchSet(string.Empty, "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatchSet(string.Empty, PathVersionMock, "name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidNeedles_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatchSet([], "name"));
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatchSet([], PathVersionMock, "name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenericConstructor_NoDelegates()
|
||||
{
|
||||
var needles = new List<PathMatch> { "test" };
|
||||
var cms = new PathMatchSet(needles, "name");
|
||||
Assert.Null(cms.GetVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VersionConstructor_SingleDelegate()
|
||||
{
|
||||
var needles = new List<PathMatch> { "test" };
|
||||
var cms = new PathMatchSet(needles, PathVersionMock, "name");
|
||||
Assert.NotNull(cms.GetVersion);
|
||||
}
|
||||
|
||||
#region Array
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_NullArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll((string[]?)null);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_EmptyArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(Array.Empty<string>());
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MatchingArray_Matches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new string[] { "test" });
|
||||
string path = Assert.Single(actual);
|
||||
Assert.Equal("test", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MismatchedArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new string[] { "not" });
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_NullArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny((string[]?)null);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_EmptyArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(Array.Empty<string>());
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MatchingArray_Matches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new string[] { "test" });
|
||||
Assert.Equal("test", actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MismatchedArray_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new string[] { "not" });
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region List
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_NullList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll((List<string>?)null);
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_EmptyList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new List<string>());
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MatchingList_Matches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new List<string> { "test" });
|
||||
string path = Assert.Single(actual);
|
||||
Assert.Equal("test", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAll_MismatchedList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
var actual = cms.MatchesAll(new List<string> { "not" });
|
||||
Assert.Empty(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_NullList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny((List<string>?)null);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_EmptyList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new List<string>());
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MatchingList_Matches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new List<string> { "test" });
|
||||
Assert.Equal("test", actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesAny_MismatchedList_NoMatches()
|
||||
{
|
||||
var cms = new PathMatchSet("test", "name");
|
||||
string? actual = cms.MatchesAny(new List<string> { "not" });
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mock Delegates
|
||||
|
||||
/// <inheritdoc cref="GetPathVersion"/>
|
||||
private static string? PathVersionMock(string path, List<string>? files) => null;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
293
SabreTools.Matching.Test/Paths/PathMatchTests.cs
Normal file
293
SabreTools.Matching.Test/Paths/PathMatchTests.cs
Normal file
@@ -0,0 +1,293 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Matching.Paths;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test.Paths
|
||||
{
|
||||
public class PathMatchTests
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidNeedle_ThrowsException()
|
||||
{
|
||||
Assert.Throws<InvalidDataException>(() => new PathMatch(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitOperatorArray_Success()
|
||||
{
|
||||
string needle = "test";
|
||||
var pm = (PathMatch)needle;
|
||||
Assert.NotNull(pm);
|
||||
}
|
||||
|
||||
#region Array
|
||||
|
||||
[Fact]
|
||||
public void NullArray_NoMatch()
|
||||
{
|
||||
var pm = new PathMatch("test");
|
||||
string? actual = pm.Match((string[]?)null);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_NoMatch()
|
||||
{
|
||||
var pm = new PathMatch("test");
|
||||
string? actual = pm.Match(Array.Empty<string>());
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleItemArrayMatching_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
string[] stack = [needle];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleItemArrayMismatched_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
string[] stack = ["not"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiItemArrayMatching_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
string[] stack = ["not", needle, "far"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiItemArrayMismatched_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
string[] stack = ["not", "too", "far"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region List
|
||||
|
||||
[Fact]
|
||||
public void NullList_NoMatch()
|
||||
{
|
||||
var pm = new PathMatch("test");
|
||||
string? actual = pm.Match((List<string>?)null);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyList_NoMatch()
|
||||
{
|
||||
var pm = new PathMatch("test");
|
||||
string? actual = pm.Match(new List<string>());
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleItemListMatching_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleItemListMismatched_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = ["not"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiItemListMatching_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = ["not", needle, "far"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiItemListMismatched_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = ["not", "too", "far"];
|
||||
var pm = new PathMatch(needle);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Match Case
|
||||
|
||||
[Fact]
|
||||
public void MatchCaseEqual_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle, matchCase: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchCaseEqual_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle, matchCase: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchCaseInequal_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle.ToUpperInvariant()];
|
||||
var pm = new PathMatch(needle, matchCase: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchCaseInequal_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle.ToUpperInvariant()];
|
||||
var pm = new PathMatch(needle, matchCase: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle.ToUpperInvariant(), actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchCaseContains_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [$"prefix_{needle}_postfix"];
|
||||
var pm = new PathMatch(needle, matchCase: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal($"prefix_{needle}_postfix", actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchCaseContains_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [$"prefix_{needle}_postfix"];
|
||||
var pm = new PathMatch(needle, matchCase: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal($"prefix_{needle}_postfix", actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Use Ends With
|
||||
|
||||
[Fact]
|
||||
public void EndsWithEqual_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle, useEndsWith: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoEndsWithEqual_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle];
|
||||
var pm = new PathMatch(needle, useEndsWith: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWithInequal_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle.ToUpperInvariant()];
|
||||
var pm = new PathMatch(needle, useEndsWith: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle.ToUpperInvariant(), actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoEndsWithInequal_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [needle.ToUpperInvariant()];
|
||||
var pm = new PathMatch(needle, useEndsWith: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal(needle.ToUpperInvariant(), actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EndsWithContains_NoMatch()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [$"prefix_{needle}_postfix"];
|
||||
var pm = new PathMatch(needle, useEndsWith: true);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoEndsWithContains_Match()
|
||||
{
|
||||
string needle = "test";
|
||||
List<string> stack = [$"prefix_{needle}_postfix"];
|
||||
var pm = new PathMatch(needle, useEndsWith: false);
|
||||
|
||||
string? actual = pm.Match(stack);
|
||||
Assert.Equal($"prefix_{needle}_postfix", actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>NU1903</NoWarn>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.Matching\SabreTools.Matching.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.Matching\SabreTools.Matching.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -11,25 +11,22 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SabreTools.Matching.Compare
|
||||
{
|
||||
public class NaturalComparer : Comparer<string>, IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, string[]> table;
|
||||
private readonly Dictionary<string, string[]> _table;
|
||||
|
||||
public NaturalComparer()
|
||||
{
|
||||
table = [];
|
||||
_table = [];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
table.Clear();
|
||||
_table.Clear();
|
||||
}
|
||||
|
||||
public override int Compare(string? x, string? y)
|
||||
@@ -47,46 +44,20 @@ namespace SabreTools.Matching.Compare
|
||||
if (x.ToLowerInvariant() == y.ToLowerInvariant())
|
||||
return x.CompareTo(y);
|
||||
|
||||
if (!table.TryGetValue(x, out string[]? x1))
|
||||
if (!_table.TryGetValue(x, out string[]? x1))
|
||||
{
|
||||
//x1 = Regex.Split(x.Replace(" ", string.Empty), "([0-9]+)");
|
||||
#if NET20 || NET35
|
||||
var nonempty = new List<string>();
|
||||
x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)");
|
||||
foreach (var s in x1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(s))
|
||||
nonempty.Add(s);
|
||||
}
|
||||
|
||||
x1 = nonempty.ToArray();
|
||||
#else
|
||||
x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)")
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToArray();
|
||||
#endif
|
||||
table.Add(x, x1);
|
||||
x1 = Array.FindAll(x1, s => !string.IsNullOrEmpty(s));
|
||||
_table.Add(x, x1);
|
||||
}
|
||||
|
||||
if (!table.TryGetValue(y, out string[]? y1))
|
||||
if (!_table.TryGetValue(y, out string[]? y1))
|
||||
{
|
||||
//y1 = Regex.Split(y.Replace(" ", string.Empty), "([0-9]+)");
|
||||
#if NET20 || NET35
|
||||
var nonempty = new List<string>();
|
||||
y1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)");
|
||||
foreach (var s in y1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(s))
|
||||
nonempty.Add(s);
|
||||
}
|
||||
|
||||
y1 = nonempty.ToArray();
|
||||
#else
|
||||
y1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)")
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToArray();
|
||||
#endif
|
||||
table.Add(y, y1);
|
||||
y1 = Regex.Split(y.ToLowerInvariant(), "([0-9]+)");
|
||||
y1 = Array.FindAll(y1, s => !string.IsNullOrEmpty(s));
|
||||
_table.Add(y, y1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < x1.Length && i < y1.Length; i++)
|
||||
@@ -95,9 +66,9 @@ namespace SabreTools.Matching.Compare
|
||||
return PartCompare(x1[i], y1[i]);
|
||||
}
|
||||
|
||||
if (y1.Length > x1.Length)
|
||||
if (x1.Length > y1.Length)
|
||||
return 1;
|
||||
else if (x1.Length > y1.Length)
|
||||
else if (y1.Length > x1.Length)
|
||||
return -1;
|
||||
else
|
||||
return x.CompareTo(y);
|
||||
@@ -106,10 +77,10 @@ namespace SabreTools.Matching.Compare
|
||||
private static int PartCompare(string left, string right)
|
||||
{
|
||||
if (!long.TryParse(left, out long x))
|
||||
return NaturalComparerUtil.CompareNumeric(left, right);
|
||||
return NaturalComparerUtil.ComparePaths(left, right);
|
||||
|
||||
if (!long.TryParse(right, out long y))
|
||||
return NaturalComparerUtil.CompareNumeric(left, right);
|
||||
return NaturalComparerUtil.ComparePaths(left, right);
|
||||
|
||||
// If we have an equal part, then make sure that "longer" ones are taken into account
|
||||
if (x.CompareTo(y) == 0)
|
||||
|
||||
@@ -1,82 +1,93 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Matching.Compare
|
||||
namespace SabreTools.Matching.Compare
|
||||
{
|
||||
public static class NaturalComparerUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two strings by numeric parts
|
||||
/// Compare two strings by path parts
|
||||
/// </summary>
|
||||
public static int CompareNumeric(string? s1, string? s2)
|
||||
public static int ComparePaths(string? left, string? right)
|
||||
{
|
||||
// If both strings are null, return
|
||||
if (s1 == null && s2 == null)
|
||||
if (left == null && right == null)
|
||||
return 0;
|
||||
|
||||
// If one is null, then say that's less than
|
||||
if (s1 == null)
|
||||
if (left == null)
|
||||
return -1;
|
||||
if (s2 == null)
|
||||
if (right == null)
|
||||
return 1;
|
||||
|
||||
// Save the orginal strings, for later comparison
|
||||
string s1orig = s1;
|
||||
string s2orig = s2;
|
||||
// Normalize the path seperators
|
||||
left = left.Replace('\\', '/');
|
||||
right = right.Replace('\\', '/');
|
||||
|
||||
// We want to normalize the strings, so we set both to lower case
|
||||
s1 = s1.ToLowerInvariant();
|
||||
s2 = s2.ToLowerInvariant();
|
||||
// Save the orginal adjusted strings
|
||||
string leftOrig = left;
|
||||
string rightOrig = right;
|
||||
|
||||
// Normalize strings by lower-case
|
||||
left = leftOrig.ToLowerInvariant();
|
||||
right = rightOrig.ToLowerInvariant();
|
||||
|
||||
// If the strings are the same exactly, return
|
||||
if (s1 == s2)
|
||||
return s1orig.CompareTo(s2orig);
|
||||
if (left == right)
|
||||
return leftOrig.CompareTo(rightOrig);
|
||||
|
||||
// Now split into path parts after converting AltDirSeparator to DirSeparator
|
||||
s1 = s1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
s2 = s2.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
string[] s1parts = s1.Split(Path.DirectorySeparatorChar);
|
||||
string[] s2parts = s2.Split(Path.DirectorySeparatorChar);
|
||||
// Now split into path parts
|
||||
string[] leftParts = left.Split('/');
|
||||
string[] rightParts = right.Split('/');
|
||||
|
||||
// Then compare each part in turn
|
||||
for (int j = 0; j < s1parts.Length && j < s2parts.Length; j++)
|
||||
for (int i = 0; i < leftParts.Length && i < rightParts.Length; i++)
|
||||
{
|
||||
int compared = CompareNumericPart(s1parts[j], s2parts[j]);
|
||||
if (compared != 0)
|
||||
return compared;
|
||||
int partCompare = ComparePathSegment(leftParts[i], rightParts[i]);
|
||||
if (partCompare != 0)
|
||||
return partCompare;
|
||||
}
|
||||
|
||||
// If we got out here, then it looped through at least one of the strings
|
||||
if (s1parts.Length > s2parts.Length)
|
||||
if (leftParts.Length > rightParts.Length)
|
||||
return 1;
|
||||
if (s1parts.Length < s2parts.Length)
|
||||
if (leftParts.Length < rightParts.Length)
|
||||
return -1;
|
||||
|
||||
return s1orig.CompareTo(s2orig);
|
||||
return leftOrig.CompareTo(rightOrig);
|
||||
}
|
||||
|
||||
private static int CompareNumericPart(string s1, string s2)
|
||||
/// <summary>
|
||||
/// Compare two path segments deterministically
|
||||
/// </summary>
|
||||
private static int ComparePathSegment(string left, string right)
|
||||
{
|
||||
// If the lengths are both zero, they're equal
|
||||
if (left.Length == 0 && right.Length == 0)
|
||||
return 0;
|
||||
|
||||
// Shorter strings are sorted before
|
||||
if (left.Length == 0)
|
||||
return -1;
|
||||
if (right.Length == 0)
|
||||
return 1;
|
||||
|
||||
// Otherwise, loop through until we have an answer
|
||||
for (int i = 0; i < s1.Length && i < s2.Length; i++)
|
||||
for (int i = 0; i < left.Length && i < right.Length; i++)
|
||||
{
|
||||
int s1c = s1[i];
|
||||
int s2c = s2[i];
|
||||
// Get the next characters from the inputs as integers
|
||||
int leftChar = left[i];
|
||||
int rightChar = right[i];
|
||||
|
||||
// If the characters are the same, continue
|
||||
if (s1c == s2c)
|
||||
if (leftChar == rightChar)
|
||||
continue;
|
||||
|
||||
// If they're different, check which one was larger
|
||||
if (s1c > s2c)
|
||||
return 1;
|
||||
if (s1c < s2c)
|
||||
return -1;
|
||||
return leftChar > rightChar ? 1 : -1;
|
||||
}
|
||||
|
||||
// If we got out here, then it looped through at least one of the strings
|
||||
if (s1.Length > s2.Length)
|
||||
if (left.Length > right.Length)
|
||||
return 1;
|
||||
if (s1.Length < s2.Length)
|
||||
if (left.Length < right.Length)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -11,25 +11,22 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SabreTools.Matching.Compare
|
||||
{
|
||||
public class NaturalReversedComparer : Comparer<string>, IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, string[]> table;
|
||||
private readonly Dictionary<string, string[]> _table;
|
||||
|
||||
public NaturalReversedComparer()
|
||||
{
|
||||
table = [];
|
||||
_table = [];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
table.Clear();
|
||||
_table.Clear();
|
||||
}
|
||||
|
||||
public override int Compare(string? x, string? y)
|
||||
@@ -47,46 +44,20 @@ namespace SabreTools.Matching.Compare
|
||||
if (y.ToLowerInvariant() == x.ToLowerInvariant())
|
||||
return y.CompareTo(x);
|
||||
|
||||
if (!table.TryGetValue(x, out string[]? x1))
|
||||
if (!_table.TryGetValue(x, out string[]? x1))
|
||||
{
|
||||
//x1 = Regex.Split(x.Replace(" ", string.Empty), "([0-9]+)");
|
||||
#if NET20 || NET35
|
||||
var nonempty = new List<string>();
|
||||
x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)");
|
||||
foreach (var s in x1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(s))
|
||||
nonempty.Add(s);
|
||||
}
|
||||
|
||||
x1 = nonempty.ToArray();
|
||||
#else
|
||||
x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)")
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToArray();
|
||||
#endif
|
||||
table.Add(x, x1);
|
||||
x1 = Array.FindAll(x1, s => !string.IsNullOrEmpty(s));
|
||||
_table.Add(x, x1);
|
||||
}
|
||||
|
||||
if (!table.TryGetValue(y, out string[]? y1))
|
||||
if (!_table.TryGetValue(y, out string[]? y1))
|
||||
{
|
||||
//y1 = Regex.Split(y.Replace(" ", string.Empty), "([0-9]+)");
|
||||
#if NET20 || NET35
|
||||
var nonempty = new List<string>();
|
||||
y1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)");
|
||||
foreach (var s in y1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(s))
|
||||
nonempty.Add(s);
|
||||
}
|
||||
|
||||
y1 = nonempty.ToArray();
|
||||
#else
|
||||
y1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)")
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToArray();
|
||||
#endif
|
||||
table.Add(y, y1);
|
||||
y1 = Regex.Split(y.ToLowerInvariant(), "([0-9]+)");
|
||||
y1 = Array.FindAll(y1, s => !string.IsNullOrEmpty(s));
|
||||
_table.Add(y, y1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < x1.Length && i < y1.Length; i++)
|
||||
@@ -106,10 +77,10 @@ namespace SabreTools.Matching.Compare
|
||||
private static int PartCompare(string left, string right)
|
||||
{
|
||||
if (!long.TryParse(left, out long x))
|
||||
return NaturalComparerUtil.CompareNumeric(right, left);
|
||||
return NaturalComparerUtil.ComparePaths(right, left);
|
||||
|
||||
if (!long.TryParse(right, out long y))
|
||||
return NaturalComparerUtil.CompareNumeric(right, left);
|
||||
return NaturalComparerUtil.ComparePaths(right, left);
|
||||
|
||||
// If we have an equal part, then make sure that "longer" ones are taken into account
|
||||
if (y.CompareTo(x) == 0)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Matching.Content
|
||||
@@ -10,35 +11,74 @@ namespace SabreTools.Matching.Content
|
||||
/// <summary>
|
||||
/// Content to match
|
||||
/// </summary>
|
||||
#if NETFRAMEWORK || NETCOREAPP
|
||||
public byte?[]? Needle { get; private set; }
|
||||
#else
|
||||
public byte?[]? Needle { get; init; }
|
||||
#endif
|
||||
public byte?[] Needle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starting index for matching
|
||||
/// </summary>
|
||||
public int Start { get; internal set; }
|
||||
private readonly int _start;
|
||||
|
||||
/// <summary>
|
||||
/// Ending index for matching
|
||||
/// </summary>
|
||||
public int End { get; private set; }
|
||||
private readonly int _end;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">Byte array representing the search</param>
|
||||
/// <param name="start">Optional starting index</param>
|
||||
/// <param name="end">Optional ending index</param>
|
||||
public ContentMatch(byte?[]? needle, int start = -1, int end = -1)
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public ContentMatch(byte[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
this.Needle = needle;
|
||||
this.Start = start;
|
||||
this.End = end;
|
||||
// Validate the inputs
|
||||
if (needle.Length == 0)
|
||||
throw new InvalidDataException(nameof(needle));
|
||||
if (start < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(start));
|
||||
if (end < -1)
|
||||
throw new ArgumentOutOfRangeException(nameof(end));
|
||||
|
||||
Needle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
_start = start;
|
||||
_end = end;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">Nullable byte array representing the search</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public ContentMatch(byte?[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needle.Length == 0)
|
||||
throw new InvalidDataException(nameof(needle));
|
||||
if (start < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(start));
|
||||
if (end < -1)
|
||||
throw new ArgumentOutOfRangeException(nameof(end));
|
||||
|
||||
Needle = needle;
|
||||
_start = start;
|
||||
_end = end;
|
||||
}
|
||||
|
||||
#region Conversion
|
||||
|
||||
/// <summary>
|
||||
/// Allow conversion from byte array to ContentMatch
|
||||
/// </summary>
|
||||
public static implicit operator ContentMatch(byte[] needle) => new ContentMatch(needle);
|
||||
|
||||
/// <summary>
|
||||
/// Allow conversion from nullable byte array to ContentMatch
|
||||
/// </summary>
|
||||
public static implicit operator ContentMatch(byte?[] needle) => new ContentMatch(needle);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Array Matching
|
||||
|
||||
/// <summary>
|
||||
@@ -46,28 +86,72 @@ namespace SabreTools.Matching.Content
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search for the given content</param>
|
||||
/// <param name="reverse">True to search from the end of the array, false from the start</param>
|
||||
/// <returns>Found position on success, -1 on error</returns>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
public int Match(byte[]? stack, bool reverse = false)
|
||||
{
|
||||
// If either array is null or empty, we can't do anything
|
||||
if (stack == null || stack.Length == 0 || this.Needle == null || this.Needle.Length == 0)
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Needle.Length == 0)
|
||||
return -1;
|
||||
|
||||
// If the needle array is larger than the stack array, it can't be contained within
|
||||
if (this.Needle.Length > stack.Length)
|
||||
// Get the adjusted end value for comparison
|
||||
int end = _end < 0 ? stack.Length : _end;
|
||||
end = end > stack.Length ? stack.Length : end;
|
||||
|
||||
// If the stack window is invalid
|
||||
if (end < _start)
|
||||
return -1;
|
||||
|
||||
// If the needle is larger than the stack window, it can't be contained within
|
||||
if (Needle.Length > stack.Length - _start)
|
||||
return -1;
|
||||
|
||||
// If the needle and stack window are identically sized, short-circuit
|
||||
if (Needle.Length == stack.Length - _start)
|
||||
return EqualAt(stack, _start) ? _start : -1;
|
||||
|
||||
// Return based on the direction of search
|
||||
return reverse ? MatchReverse(stack) : MatchForward(stack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match within a stack starting from the smallest index
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search for the given content</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
private int MatchForward(byte[] stack)
|
||||
{
|
||||
// Set the default start and end values
|
||||
int start = this.Start;
|
||||
int end = this.End;
|
||||
int start = _start < 0 ? 0 : _start;
|
||||
int end = _end < 0 ? stack.Length - Needle.Length : _end;
|
||||
|
||||
// If start or end are not set properly, set them to defaults
|
||||
if (start < 0)
|
||||
start = 0;
|
||||
if (end < 0)
|
||||
end = stack.Length - this.Needle.Length;
|
||||
// Loop starting from the smallest index
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
return -1;
|
||||
|
||||
for (int i = reverse ? end : start; reverse ? i > start : i < end; i += reverse ? -1 : 1)
|
||||
// Check to see if the values are equal
|
||||
if (EqualAt(stack, i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match within a stack starting from the largest index
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search for the given content</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
private int MatchReverse(byte[] stack)
|
||||
{
|
||||
// Set the default start and end values
|
||||
int start = _start < 0 ? 0 : _start;
|
||||
int end = _end < 0 ? stack.Length - Needle.Length : _end;
|
||||
|
||||
// Loop starting from the largest index
|
||||
for (int i = end; i > start; i--)
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
@@ -89,25 +173,21 @@ namespace SabreTools.Matching.Content
|
||||
/// <returns>True if the needle matches the stack at a given index</returns>
|
||||
private bool EqualAt(byte[] stack, int index)
|
||||
{
|
||||
// If the needle is invalid, we can't do anything
|
||||
if (this.Needle == null)
|
||||
return false;
|
||||
|
||||
// If the index is invalid, we can't do anything
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
// If we're too close to the end of the stack, return false
|
||||
if (this.Needle.Length > stack.Length - index)
|
||||
if (Needle.Length > stack.Length - index)
|
||||
return false;
|
||||
|
||||
// Loop through and check the value
|
||||
for (int i = 0; i < this.Needle.Length; i++)
|
||||
for (int i = 0; i < Needle.Length; i++)
|
||||
{
|
||||
// A null value is a wildcard
|
||||
if (this.Needle[i] == null)
|
||||
if (Needle[i] == null)
|
||||
continue;
|
||||
else if (stack[i + index] != this.Needle[i])
|
||||
else if (stack[i + index] != Needle[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -123,28 +203,72 @@ namespace SabreTools.Matching.Content
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search for the given content</param>
|
||||
/// <param name="reverse">True to search from the end of the array, false from the start</param>
|
||||
/// <returns>Found position on success, -1 on error</returns>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
public int Match(Stream? stack, bool reverse = false)
|
||||
{
|
||||
// If either array is null or empty, we can't do anything
|
||||
if (stack == null || stack.Length == 0 || this.Needle == null || this.Needle.Length == 0)
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Needle.Length == 0)
|
||||
return -1;
|
||||
|
||||
// If the needle array is larger than the stack array, it can't be contained within
|
||||
if (this.Needle.Length > stack.Length)
|
||||
// Get the adjusted end value for comparison
|
||||
int end = _end < 0 ? (int)stack.Length : _end;
|
||||
end = end > (int)stack.Length ? (int)stack.Length : end;
|
||||
|
||||
// If the stack window is invalid
|
||||
if (end < _start)
|
||||
return -1;
|
||||
|
||||
// If the needle is larger than the stack window, it can't be contained within
|
||||
if (Needle.Length > stack.Length - _start)
|
||||
return -1;
|
||||
|
||||
// If the needle and stack window are identically sized, short-circuit
|
||||
if (Needle.Length == stack.Length - _start)
|
||||
return EqualAt(stack, _start) ? _start : -1;
|
||||
|
||||
// Return based on the direction of search
|
||||
return reverse ? MatchReverse(stack) : MatchForward(stack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match within a stack starting from the smallest index
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search for the given content</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
private int MatchForward(Stream stack)
|
||||
{
|
||||
// Set the default start and end values
|
||||
int start = this.Start;
|
||||
int end = this.End;
|
||||
int start = _start < 0 ? 0 : _start;
|
||||
int end = _end < 0 ? (int)stack.Length - Needle.Length : _end;
|
||||
|
||||
// If start or end are not set properly, set them to defaults
|
||||
if (start < 0)
|
||||
start = 0;
|
||||
if (end < 0)
|
||||
end = (int)(stack.Length - this.Needle.Length);
|
||||
// Loop starting from the smallest index
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
return -1;
|
||||
|
||||
for (int i = reverse ? end : start; reverse ? i > start : i < end; i += reverse ? -1 : 1)
|
||||
// Check to see if the values are equal
|
||||
if (EqualAt(stack, i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match within a stack starting from the largest index
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search for the given content</param>
|
||||
/// <returns>Found position on success, -1 otherwise</returns>
|
||||
private int MatchReverse(Stream stack)
|
||||
{
|
||||
// Set the default start and end values
|
||||
int start = _start < 0 ? 0 : _start;
|
||||
int end = _end < 0 ? (int)stack.Length - Needle.Length : _end;
|
||||
|
||||
// Loop starting from the largest index
|
||||
for (int i = end; i > start; i--)
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
@@ -166,16 +290,12 @@ namespace SabreTools.Matching.Content
|
||||
/// <returns>True if the needle matches the stack at a given index</returns>
|
||||
private bool EqualAt(Stream stack, int index)
|
||||
{
|
||||
// If the needle is invalid, we can't do anything
|
||||
if (this.Needle == null)
|
||||
return false;
|
||||
|
||||
// If the index is invalid, we can't do anything
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
// If we're too close to the end of the stack, return false
|
||||
if (this.Needle.Length > stack.Length - index)
|
||||
if (Needle.Length > stack.Length - index)
|
||||
return false;
|
||||
|
||||
// Save the current position and move to the index
|
||||
@@ -186,16 +306,16 @@ namespace SabreTools.Matching.Content
|
||||
bool matched = true;
|
||||
|
||||
// Loop through and check the value
|
||||
for (int i = 0; i < this.Needle.Length; i++)
|
||||
for (int i = 0; i < Needle.Length; i++)
|
||||
{
|
||||
byte stackValue = (byte)stack.ReadByte();
|
||||
|
||||
// A null value is a wildcard
|
||||
if (this.Needle[i] == null)
|
||||
if (Needle[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (stackValue != this.Needle[i])
|
||||
else if (stackValue != Needle[i])
|
||||
{
|
||||
matched = false;
|
||||
break;
|
||||
@@ -209,4 +329,4 @@ namespace SabreTools.Matching.Content
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
|
||||
namespace SabreTools.Matching.Content
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of content matches that work together
|
||||
/// </summary>
|
||||
public class ContentMatchSet : MatchSet<ContentMatch, byte?[]>
|
||||
public class ContentMatchSet : IMatchSet<ContentMatch, byte?[]>
|
||||
{
|
||||
/// <summary>
|
||||
/// Function to get a content version
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A content version method takes the file path, the file contents,
|
||||
/// and a list of found positions and returns a single string. That
|
||||
/// string is either a version string, in which case it will be appended
|
||||
/// to the protection name, or `null`, in which case it will cause
|
||||
/// the protection to be omitted.
|
||||
/// </remarks>
|
||||
public Func<string, byte[]?, List<int>, string?>? GetArrayVersion { get; private set; }
|
||||
/// <inheritdoc/>
|
||||
public List<ContentMatch> Matchers { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string SetName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Function to get a content version
|
||||
@@ -31,93 +21,110 @@ namespace SabreTools.Matching.Content
|
||||
/// A content version method takes the file path, the file contents,
|
||||
/// and a list of found positions and returns a single string. That
|
||||
/// string is either a version string, in which case it will be appended
|
||||
/// to the protection name, or `null`, in which case it will cause
|
||||
/// the protection to be omitted.
|
||||
/// to the match name, or `null`, in which case it will cause
|
||||
/// the match name to be omitted.
|
||||
/// </remarks>
|
||||
public Func<string, Stream?, List<int>, string?>? GetStreamVersion { get; private set; }
|
||||
public GetArrayVersion? GetArrayVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Function to get a content version
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A content version method takes the file path, the file contents,
|
||||
/// and a list of found positions and returns a single string. That
|
||||
/// string is either a version string, in which case it will be appended
|
||||
/// to the match name, or `null`, in which case it will cause
|
||||
/// the match name to be omitted.
|
||||
/// </remarks>
|
||||
public GetStreamVersion? GetStreamVersion { get; }
|
||||
|
||||
#region Generic Constructors
|
||||
|
||||
public ContentMatchSet(byte?[] needle, string protectionName)
|
||||
: this(new List<byte?[]> { needle }, getArrayVersion: null, protectionName) { }
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">ContentMatch representing the comparisons</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(ContentMatch needle, string setName)
|
||||
: this([needle], setName) { }
|
||||
|
||||
public ContentMatchSet(List<byte?[]> needles, string protectionName)
|
||||
: this(needles, getArrayVersion: null, protectionName) { }
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of ContentMatch objects representing the comparisons</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(List<ContentMatch> needles, string setName)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
public ContentMatchSet(ContentMatch needle, string protectionName)
|
||||
: this(new List<ContentMatch>() { needle }, getArrayVersion: null, protectionName) { }
|
||||
|
||||
public ContentMatchSet(List<ContentMatch> needles, string protectionName)
|
||||
: this(needles, getArrayVersion: null, protectionName) { }
|
||||
Matchers = needles;
|
||||
SetName = setName;
|
||||
GetArrayVersion = null;
|
||||
GetStreamVersion = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Array Constructors
|
||||
|
||||
public ContentMatchSet(byte?[] needle, Func<string, byte[]?, List<int>, string?>? getArrayVersion, string protectionName)
|
||||
: this(new List<byte?[]> { needle }, getArrayVersion, protectionName) { }
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">ContentMatch representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match of an array</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(ContentMatch needle, GetArrayVersion getVersion, string setName)
|
||||
: this([needle], getVersion, setName) { }
|
||||
|
||||
#if NET20 || NET35
|
||||
public ContentMatchSet(List<byte?[]> needles, Func<string, byte[]?, List<int>, string?>? getArrayVersion, string protectionName)
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of ContentMatch objects representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match of an array</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(List<ContentMatch> needles, GetArrayVersion getVersion, string setName)
|
||||
{
|
||||
var matchers = new List<ContentMatch>();
|
||||
foreach (var n in needles)
|
||||
{
|
||||
matchers.Add(new ContentMatch(n));
|
||||
}
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
Matchers = matchers;
|
||||
GetArrayVersion = getArrayVersion;
|
||||
ProtectionName = protectionName;
|
||||
}
|
||||
#else
|
||||
public ContentMatchSet(List<byte?[]> needles, Func<string, byte[]?, List<int>, string?>? getArrayVersion, string protectionName)
|
||||
: this(needles.Select(n => new ContentMatch(n)).ToList(), getArrayVersion, protectionName) { }
|
||||
#endif
|
||||
|
||||
public ContentMatchSet(ContentMatch needle, Func<string, byte[]?, List<int>, string?>? getArrayVersion, string protectionName)
|
||||
: this(new List<ContentMatch>() { needle }, getArrayVersion, protectionName) { }
|
||||
|
||||
public ContentMatchSet(List<ContentMatch> needles, Func<string, byte[]?, List<int>, string?>? getArrayVersion, string protectionName)
|
||||
{
|
||||
Matchers = needles;
|
||||
GetArrayVersion = getArrayVersion;
|
||||
ProtectionName = protectionName;
|
||||
SetName = setName;
|
||||
GetArrayVersion = getVersion;
|
||||
GetStreamVersion = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Constructors
|
||||
|
||||
public ContentMatchSet(byte?[] needle, Func<string, Stream?, List<int>, string?>? getStreamVersion, string protectionName)
|
||||
: this(new List<byte?[]> { needle }, getStreamVersion, protectionName) { }
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">ContentMatch representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match of a Stream</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(ContentMatch needle, GetStreamVersion getVersion, string setName)
|
||||
: this([needle], getVersion, setName) { }
|
||||
|
||||
#if NET20 || NET35
|
||||
public ContentMatchSet(List<byte?[]> needles, Func<string, Stream?, List<int>, string?>? getStreamVersion, string protectionName)
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of ContentMatch objects representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match of a Stream</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public ContentMatchSet(List<ContentMatch> needles, GetStreamVersion getVersion, string setName)
|
||||
{
|
||||
var matchers = new List<ContentMatch>();
|
||||
foreach (var n in needles)
|
||||
{
|
||||
matchers.Add(new ContentMatch(n));
|
||||
}
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
Matchers = matchers;
|
||||
GetStreamVersion = getStreamVersion;
|
||||
ProtectionName = protectionName;
|
||||
}
|
||||
#else
|
||||
public ContentMatchSet(List<byte?[]> needles, Func<string, Stream?, List<int>, string?>? getStreamVersion, string protectionName)
|
||||
: this(needles.Select(n => new ContentMatch(n)).ToList(), getStreamVersion, protectionName) { }
|
||||
#endif
|
||||
|
||||
public ContentMatchSet(ContentMatch needle, Func<string, Stream?, List<int>, string?>? getStreamVersion, string protectionName)
|
||||
: this(new List<ContentMatch>() { needle }, getStreamVersion, protectionName) { }
|
||||
|
||||
public ContentMatchSet(List<ContentMatch> needles, Func<string, Stream?, List<int>, string?>? getStreamVersion, string protectionName)
|
||||
{
|
||||
Matchers = needles;
|
||||
GetStreamVersion = getStreamVersion;
|
||||
ProtectionName = protectionName;
|
||||
SetName = setName;
|
||||
GetArrayVersion = null;
|
||||
GetStreamVersion = getVersion;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -131,12 +138,8 @@ namespace SabreTools.Matching.Content
|
||||
/// <returns>List of matching positions, if any</returns>
|
||||
public List<int> MatchesAll(byte[]? stack)
|
||||
{
|
||||
// If no content matches are defined, we fail out
|
||||
#if NET20 || NET35
|
||||
if (Matchers == null || new List<ContentMatch>(Matchers).Count == 0)
|
||||
#else
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
#endif
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Matchers.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the position list
|
||||
@@ -148,8 +151,8 @@ namespace SabreTools.Matching.Content
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position < 0)
|
||||
return [];
|
||||
else
|
||||
positions.Add(position);
|
||||
|
||||
positions.Add(position);
|
||||
}
|
||||
|
||||
return positions;
|
||||
@@ -162,12 +165,8 @@ namespace SabreTools.Matching.Content
|
||||
/// <returns>First matching position on success, -1 on error</returns>
|
||||
public int MatchesAny(byte[]? stack)
|
||||
{
|
||||
// If no content matches are defined, we fail out
|
||||
#if NET20 || NET35
|
||||
if (Matchers == null || new List<ContentMatch>(Matchers).Count == 0)
|
||||
#else
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
#endif
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Matchers.Count == 0)
|
||||
return -1;
|
||||
|
||||
// Loop through all content matches and make sure all pass
|
||||
@@ -192,12 +191,8 @@ namespace SabreTools.Matching.Content
|
||||
/// <returns>List of matching positions, if any</returns>
|
||||
public List<int> MatchesAll(Stream? stack)
|
||||
{
|
||||
// If no content matches are defined, we fail out
|
||||
#if NET20 || NET35
|
||||
if (Matchers == null || new List<ContentMatch>(Matchers).Count == 0)
|
||||
#else
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
#endif
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Matchers.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the position list
|
||||
@@ -209,8 +204,8 @@ namespace SabreTools.Matching.Content
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position < 0)
|
||||
return [];
|
||||
else
|
||||
positions.Add(position);
|
||||
|
||||
positions.Add(position);
|
||||
}
|
||||
|
||||
return positions;
|
||||
@@ -223,12 +218,8 @@ namespace SabreTools.Matching.Content
|
||||
/// <returns>First matching position on success, -1 on error</returns>
|
||||
public int MatchesAny(Stream? stack)
|
||||
{
|
||||
// If no content matches are defined, we fail out
|
||||
#if NET20 || NET35
|
||||
if (Matchers == null || new List<ContentMatch>(Matchers).Count == 0)
|
||||
#else
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
#endif
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || Matchers.Count == 0)
|
||||
return -1;
|
||||
|
||||
// Loop through all content matches and make sure all pass
|
||||
@@ -244,4 +235,4 @@ namespace SabreTools.Matching.Content
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31
SabreTools.Matching/Delegates.cs
Normal file
31
SabreTools.Matching/Delegates.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Matching
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a version number from a file
|
||||
/// </summary>
|
||||
/// <param name="path">File path to get the version from</param>
|
||||
/// <param name="content">Optional file contents as a byte array</param>
|
||||
/// <param name="positions">List of positions in the array that were matched</param>
|
||||
/// <returns>Version string on success, null on failure</returns>
|
||||
public delegate string? GetArrayVersion(string path, byte[]? content, List<int> positions);
|
||||
|
||||
/// <summary>
|
||||
/// Get a version number from an input path
|
||||
/// </summary>
|
||||
/// <param name="path">File or directory path to get the version from</param>
|
||||
/// <param name="files">Optional set of files in the directory</param>
|
||||
/// <returns>Version string on success, null on failure</returns>
|
||||
public delegate string? GetPathVersion(string path, List<string>? files);
|
||||
|
||||
/// <summary>
|
||||
/// Get a version number from a file
|
||||
/// </summary>
|
||||
/// <param name="path">File path to get the version from</param>
|
||||
/// <param name="content">Optional file contents as a Stream</param>
|
||||
/// <param name="positions">List of positions in the Stream that were matched</param>
|
||||
/// <returns>Version string on success, null on failure</returns>
|
||||
public delegate string? GetStreamVersion(string path, Stream? content, List<int> positions);
|
||||
}
|
||||
9
SabreTools.Matching/ExtensionAttribute.cs
Normal file
9
SabreTools.Matching/ExtensionAttribute.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
#if NET20
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
|
||||
internal sealed class ExtensionAttribute : Attribute {}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using SabreTools.Matching.Content;
|
||||
|
||||
namespace SabreTools.Matching
|
||||
@@ -10,34 +7,62 @@ namespace SabreTools.Matching
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the specified array is null or has a length of zero
|
||||
/// Find all positions of one array in another, if possible
|
||||
/// </summary>
|
||||
public static bool IsNullOrEmpty(this Array? array)
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static List<int> FindAllPositions(this byte[] stack, byte[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
return array == null || array.Length == 0;
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return FindAllPositions(stack, nullableNeedle, start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all positions of one array in another, if possible, if possible
|
||||
/// Find all positions of one array in another, if possible
|
||||
/// </summary>
|
||||
public static List<int> FindAllPositions(this byte[] stack, byte?[]? needle, int start = 0, int end = -1)
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static List<int> FindAllPositions(this byte[] stack, byte?[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
// Get the outgoing list
|
||||
List<int> positions = [];
|
||||
|
||||
// Initialize the loop variables
|
||||
int lastPosition = start;
|
||||
var matcher = new ContentMatch(needle, end: end);
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return positions;
|
||||
|
||||
// Loop over and get all positions
|
||||
while (true)
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return positions;
|
||||
|
||||
// Normalize the end value, if necessary
|
||||
if (end == -1)
|
||||
end = stack.Length;
|
||||
|
||||
// Validate the start and end values
|
||||
if (start < 0 || start >= stack.Length)
|
||||
return positions;
|
||||
if (end < -1 || end < start || end > stack.Length)
|
||||
return positions;
|
||||
|
||||
// Loop while there is data to check
|
||||
while (start < end)
|
||||
{
|
||||
matcher.Start = lastPosition;
|
||||
lastPosition = matcher.Match(stack, false);
|
||||
if (lastPosition < 0)
|
||||
// Create a new matcher for this segment
|
||||
var matcher = new ContentMatch(needle, start, end);
|
||||
|
||||
// Get the next matching position
|
||||
int position = matcher.Match(stack, reverse: false);
|
||||
if (position < 0)
|
||||
break;
|
||||
|
||||
positions.Add(lastPosition);
|
||||
// Append the position and reset the start index
|
||||
positions.Add(position);
|
||||
start = position + 1;
|
||||
}
|
||||
|
||||
return positions;
|
||||
@@ -46,114 +71,156 @@ namespace SabreTools.Matching
|
||||
/// <summary>
|
||||
/// Find the first position of one array in another, if possible
|
||||
/// </summary>
|
||||
public static bool FirstPosition(this byte[] stack, byte[]? needle, out int position, int start = 0, int end = -1)
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static int FirstPosition(this byte[] stack, byte[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
#if NET20 || NET35
|
||||
byte?[]? nullableNeedle;
|
||||
if (needle == null)
|
||||
{
|
||||
nullableNeedle = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
nullableNeedle = new byte?[needle.Length];
|
||||
for (int i = 0; i < needle.Length; i++)
|
||||
{
|
||||
nullableNeedle[i] = needle[i];
|
||||
}
|
||||
}
|
||||
#else
|
||||
byte?[]? nullableNeedle = needle?.Select(b => (byte?)b).ToArray();
|
||||
#endif
|
||||
return stack.FirstPosition(nullableNeedle, out position, start, end);
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return FirstPosition(stack, nullableNeedle, start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the first position of one array in another, if possible
|
||||
/// </summary>
|
||||
public static bool FirstPosition(this byte[] stack, byte?[]? needle, out int position, int start = 0, int end = -1)
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static int FirstPosition(this byte[] stack, byte?[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return -1;
|
||||
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return -1;
|
||||
|
||||
var matcher = new ContentMatch(needle, start, end);
|
||||
position = matcher.Match(stack, false);
|
||||
return position >= 0;
|
||||
return matcher.Match(stack, reverse: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the last position of one array in another, if possible
|
||||
/// </summary>
|
||||
public static bool LastPosition(this byte[] stack, byte?[]? needle, out int position, int start = 0, int end = -1)
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static int LastPosition(this byte[] stack, byte[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return LastPosition(stack, nullableNeedle, start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the last position of one array in another, if possible
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
/// <param name="start">Optional starting position in the stack, defaults to 0</param>
|
||||
/// <param name="end">Optional ending position in the stack, defaults to -1 (length of stack)</param>
|
||||
public static int LastPosition(this byte[] stack, byte?[] needle, int start = 0, int end = -1)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return -1;
|
||||
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return -1;
|
||||
|
||||
var matcher = new ContentMatch(needle, start, end);
|
||||
position = matcher.Match(stack, true);
|
||||
return position >= 0;
|
||||
return matcher.Match(stack, reverse: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See if a byte array starts with another
|
||||
/// Check if a byte array exactly matches another
|
||||
/// </summary>
|
||||
public static bool StartsWith(this byte[] stack, byte[]? needle, bool exact = false)
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool EqualsExactly(this byte[] stack, byte[] needle)
|
||||
{
|
||||
// If we have any invalid inputs, we return false
|
||||
if (needle == null
|
||||
|| stack.Length == 0 || needle.Length == 0
|
||||
|| needle.Length > stack.Length
|
||||
|| (exact && stack.Length != needle.Length))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stack.FirstPosition(needle, out int _, start: 0, end: 1);
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return EqualsExactly(stack, nullableNeedle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See if a byte array starts with another
|
||||
/// Check if a byte array exactly matches another
|
||||
/// </summary>
|
||||
public static bool StartsWith(this byte[] stack, byte?[]? needle, bool exact = false)
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool EqualsExactly(this byte[] stack, byte?[] needle)
|
||||
{
|
||||
// If we have any invalid inputs, we return false
|
||||
if (needle == null
|
||||
|| stack.Length == 0 || needle.Length == 0
|
||||
|| needle.Length > stack.Length
|
||||
|| (exact && stack.Length != needle.Length))
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return stack.FirstPosition(needle, out int _, start: 0, end: 1);
|
||||
// If the needle is not the exact length of the stack
|
||||
if (needle.Length != stack.Length)
|
||||
return false;
|
||||
|
||||
return FirstPosition(stack, needle, start: 0, end: 1) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See if a byte array ends with another
|
||||
/// Check if a byte array starts with another
|
||||
/// </summary>
|
||||
public static bool EndsWith(this byte[] stack, byte[]? needle, bool exact = false)
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool StartsWith(this byte[] stack, byte[] needle)
|
||||
{
|
||||
// If we have any invalid inputs, we return false
|
||||
if (needle == null
|
||||
|| stack.Length == 0 || needle.Length == 0
|
||||
|| needle.Length > stack.Length
|
||||
|| (exact && stack.Length != needle.Length))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stack.FirstPosition(needle, out int _, start: stack.Length - needle.Length);
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return StartsWith(stack, nullableNeedle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See if a byte array ends with another
|
||||
/// Check if a byte array starts with another
|
||||
/// </summary>
|
||||
public static bool EndsWith(this byte[] stack, byte?[]? needle, bool exact = false)
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool StartsWith(this byte[] stack, byte?[] needle)
|
||||
{
|
||||
// If we have any invalid inputs, we return false
|
||||
if (needle == null
|
||||
|| stack.Length == 0 || needle.Length == 0
|
||||
|| needle.Length > stack.Length
|
||||
|| (exact && stack.Length != needle.Length))
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return stack.FirstPosition(needle, out int _, start: stack.Length - needle.Length);
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return false;
|
||||
|
||||
return FirstPosition(stack, needle, start: 0, end: 1) > -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a byte array ends with another
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool EndsWith(this byte[] stack, byte[] needle)
|
||||
{
|
||||
byte?[] nullableNeedle = Array.ConvertAll(needle, b => (byte?)b);
|
||||
return EndsWith(stack, nullableNeedle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a byte array ends with another
|
||||
/// </summary>
|
||||
/// <param name="stack">Byte array to search within</param>
|
||||
/// <param name="needle">Byte array representing the search value</param>
|
||||
public static bool EndsWith(this byte[] stack, byte?[] needle)
|
||||
{
|
||||
// If either set is null or empty
|
||||
if (stack.Length == 0 || needle.Length == 0)
|
||||
return false;
|
||||
|
||||
// If the needle is longer than the stack
|
||||
if (needle.Length > stack.Length)
|
||||
return false;
|
||||
|
||||
return FirstPosition(stack, needle, start: stack.Length - needle.Length) > -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
namespace SabreTools.Matching
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a matcher for a particular type
|
||||
/// </summary>
|
||||
public interface IMatch<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Nullable typed data to be matched
|
||||
/// </summary>
|
||||
T? Needle { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,16 @@ namespace SabreTools.Matching
|
||||
/// <summary>
|
||||
/// Wrapper for a single set of matching criteria
|
||||
/// </summary>
|
||||
public abstract class MatchSet<T, U> where T : IMatch<U>
|
||||
public interface IMatchSet<T, U> where T : IMatch<U>
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of all matchers
|
||||
/// </summary>
|
||||
public IEnumerable<T>? Matchers { get; set; }
|
||||
public List<T> Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the protection to show
|
||||
/// Unique name for the match set
|
||||
/// </summary>
|
||||
public string? ProtectionName { get; set; }
|
||||
public string SetName { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Collections.Concurrent;
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.Text;
|
||||
using SabreTools.Matching.Content;
|
||||
using SabreTools.Matching.Paths;
|
||||
|
||||
@@ -23,37 +18,37 @@ namespace SabreTools.Matching
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
|
||||
#if NET20 || NET35
|
||||
public static Queue<string>? GetAllMatches(string file, byte[]? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
#else
|
||||
public static ConcurrentQueue<string>? GetAllMatches(string file, byte[]? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
#endif
|
||||
{
|
||||
return FindAllMatches(file, stack, matchers, includeDebug, false);
|
||||
}
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
public static List<string> GetAllMatches(string file,
|
||||
byte[]? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any = false,
|
||||
bool includeDebug = false)
|
||||
=> FindAllMatches(file, stack, matchSets, any, includeDebug, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get first content match for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <returns>String representing the matched protection, null otherwise</returns>
|
||||
public static string? GetFirstMatch(string file, byte[]? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
/// <returns>String representing the match, null otherwise</returns>
|
||||
public static string? GetFirstMatch(string file,
|
||||
byte[]? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any = false,
|
||||
bool includeDebug = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true);
|
||||
var contentMatches = FindAllMatches(file, stack, matchSets, any, includeDebug, true);
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
#if NET20 || NET35
|
||||
return contentMatches.Peek();
|
||||
#else
|
||||
return contentMatches.First();
|
||||
#endif
|
||||
return contentMatches[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,74 +56,71 @@ namespace SabreTools.Matching
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <param name="stopAfterFirst">True to stop after the first match, false otherwise</param>
|
||||
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
|
||||
#if NET20 || NET35
|
||||
private static Queue<string>? FindAllMatches(string file, byte[]? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug, bool stopAfterFirst)
|
||||
#else
|
||||
private static ConcurrentQueue<string>? FindAllMatches(string file, byte[]? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug, bool stopAfterFirst)
|
||||
#endif
|
||||
/// <returns>List of strings representing the matches, empty otherwise</returns>
|
||||
private static List<string> FindAllMatches(string file,
|
||||
byte[]? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any,
|
||||
bool includeDebug,
|
||||
bool stopAfterFirst)
|
||||
{
|
||||
// If there's no mappings, we can't match
|
||||
#if NET20 || NET35
|
||||
if (matchers == null || new List<ContentMatchSet>(matchers).Count == 0)
|
||||
#else
|
||||
if (matchers == null || !matchers.Any())
|
||||
#endif
|
||||
return null;
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || matchSets.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the queue of matched protections
|
||||
#if NET20 || NET35
|
||||
var matchedProtections = new Queue<string>();
|
||||
#else
|
||||
var matchedProtections = new ConcurrentQueue<string>();
|
||||
#endif
|
||||
// Initialize the list of matches
|
||||
var matchesList = new List<string>();
|
||||
|
||||
// Loop through and try everything otherwise
|
||||
foreach (var matcher in matchers)
|
||||
foreach (var matcher in matchSets)
|
||||
{
|
||||
// Determine if the matcher passes
|
||||
var positions = matcher.MatchesAll(stack);
|
||||
if (positions.Count == 0)
|
||||
List<int> positions = any
|
||||
? [matcher.MatchesAny(stack)]
|
||||
: matcher.MatchesAll(stack);
|
||||
|
||||
// If we don't have a pass, just continue
|
||||
if (positions.Count == 0 || positions[0] == -1)
|
||||
continue;
|
||||
|
||||
// Format the list of all positions found
|
||||
#if NET20 || NET35
|
||||
var positionStrs = new List<string>();
|
||||
foreach (int pos in positions)
|
||||
{
|
||||
positionStrs.Add(pos.ToString());
|
||||
}
|
||||
string positionsString = string.Join(", ", [.. positionStrs]);
|
||||
#else
|
||||
string positionsString = string.Join(", ", positions);
|
||||
#endif
|
||||
// Build the output string
|
||||
var matchString = new StringBuilder();
|
||||
matchString.Append(matcher.SetName);
|
||||
|
||||
// If we there is no version method, just return the protection name
|
||||
if (matcher.GetArrayVersion == null)
|
||||
{
|
||||
matchedProtections.Enqueue((matcher.ProtectionName ?? "Unknown Protection") + (includeDebug ? $" (Index {positionsString})" : string.Empty));
|
||||
}
|
||||
|
||||
// Otherwise, invoke the version method
|
||||
else
|
||||
// Invoke the version delegate, if it exists
|
||||
if (matcher.GetArrayVersion != null)
|
||||
{
|
||||
// A null version returned means the check didn't pass at the version step
|
||||
var version = matcher.GetArrayVersion(file, stack, positions);
|
||||
if (version == null)
|
||||
continue;
|
||||
|
||||
matchedProtections.Enqueue($"{matcher.ProtectionName ?? "Unknown Protection"} {version}".Trim() + (includeDebug ? $" (Index {positionsString})" : string.Empty));
|
||||
// Trim and add the version
|
||||
version = version.Trim();
|
||||
if (version.Length > 0)
|
||||
matchString.Append($" {version}");
|
||||
}
|
||||
|
||||
// If we're stopping after the first protection, bail out here
|
||||
// Append the positional data if required
|
||||
if (includeDebug)
|
||||
{
|
||||
string positionsString = string.Join(", ", [.. positions.ConvertAll(p => p.ToString())]);
|
||||
matchString.Append($" (Index {positionsString})");
|
||||
}
|
||||
|
||||
// Append the match to the list
|
||||
matchesList.Add(matchString.ToString());
|
||||
|
||||
// If we're stopping after the first match, bail out here
|
||||
if (stopAfterFirst)
|
||||
return matchedProtections;
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
return matchedProtections;
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -140,37 +132,37 @@ namespace SabreTools.Matching
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
|
||||
#if NET20 || NET35
|
||||
public static Queue<string>? GetAllMatches(string file, Stream? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
#else
|
||||
public static ConcurrentQueue<string>? GetAllMatches(string file, Stream? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
#endif
|
||||
{
|
||||
return FindAllMatches(file, stack, matchers, includeDebug, false);
|
||||
}
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
public static List<string> GetAllMatches(string file,
|
||||
Stream? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any = false,
|
||||
bool includeDebug = false)
|
||||
=> FindAllMatches(file, stack, matchSets, any, includeDebug, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get first content match for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <returns>String representing the matched protection, null otherwise</returns>
|
||||
public static string? GetFirstMatch(string file, Stream? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
/// <returns>String representing the match, null otherwise</returns>
|
||||
public static string? GetFirstMatch(string file,
|
||||
Stream? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any = false,
|
||||
bool includeDebug = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true);
|
||||
var contentMatches = FindAllMatches(file, stack, matchSets, any, includeDebug, true);
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
#if NET20 || NET35
|
||||
return contentMatches.Peek();
|
||||
#else
|
||||
return contentMatches.First();
|
||||
#endif
|
||||
return contentMatches[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -178,74 +170,71 @@ namespace SabreTools.Matching
|
||||
/// </summary>
|
||||
/// <param name="file">File to check for matches</param>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="matchSets">List of ContentMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any content match is a success, false if all have to match</param>
|
||||
/// <param name="includeDebug">True to include positional data, false otherwise</param>
|
||||
/// <param name="stopAfterFirst">True to stop after the first match, false otherwise</param>
|
||||
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
|
||||
#if NET20 || NET35
|
||||
private static Queue<string>? FindAllMatches(string file, Stream? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug, bool stopAfterFirst)
|
||||
#else
|
||||
private static ConcurrentQueue<string>? FindAllMatches(string file, Stream? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug, bool stopAfterFirst)
|
||||
#endif
|
||||
/// <returns>List of strings representing the matches, empty otherwise</returns>
|
||||
private static List<string> FindAllMatches(string file,
|
||||
Stream? stack,
|
||||
List<ContentMatchSet> matchSets,
|
||||
bool any,
|
||||
bool includeDebug,
|
||||
bool stopAfterFirst)
|
||||
{
|
||||
// If there's no mappings, we can't match
|
||||
#if NET20 || NET35
|
||||
if (matchers == null || new List<ContentMatchSet>(matchers).Count == 0)
|
||||
#else
|
||||
if (matchers == null || !matchers.Any())
|
||||
#endif
|
||||
return null;
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Length == 0 || matchSets.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the queue of matched protections
|
||||
#if NET20 || NET35
|
||||
var matchedProtections = new Queue<string>();
|
||||
#else
|
||||
var matchedProtections = new ConcurrentQueue<string>();
|
||||
#endif
|
||||
// Initialize the list of matches
|
||||
var matchesList = new List<string>();
|
||||
|
||||
// Loop through and try everything otherwise
|
||||
foreach (var matcher in matchers)
|
||||
foreach (var matcher in matchSets)
|
||||
{
|
||||
// Determine if the matcher passes
|
||||
var positions = matcher.MatchesAll(stack);
|
||||
if (positions.Count == 0)
|
||||
List<int> positions = any
|
||||
? [matcher.MatchesAny(stack)]
|
||||
: matcher.MatchesAll(stack);
|
||||
|
||||
// If we don't have a pass, just continue
|
||||
if (positions.Count == 0 || positions[0] == -1)
|
||||
continue;
|
||||
|
||||
// Format the list of all positions found
|
||||
#if NET20 || NET35
|
||||
var positionStrs = new List<string>();
|
||||
foreach (int pos in positions)
|
||||
{
|
||||
positionStrs.Add(pos.ToString());
|
||||
}
|
||||
string positionsString = string.Join(", ", [.. positionStrs]);
|
||||
#else
|
||||
string positionsString = string.Join(", ", positions);
|
||||
#endif
|
||||
// Build the output string
|
||||
var matchString = new StringBuilder();
|
||||
matchString.Append(matcher.SetName);
|
||||
|
||||
// If we there is no version method, just return the protection name
|
||||
if (matcher.GetStreamVersion == null)
|
||||
{
|
||||
matchedProtections.Enqueue((matcher.ProtectionName ?? "Unknown Protection") + (includeDebug ? $" (Index {positionsString})" : string.Empty));
|
||||
}
|
||||
|
||||
// Otherwise, invoke the version method
|
||||
else
|
||||
// Invoke the version delegate, if it exists
|
||||
if (matcher.GetStreamVersion != null)
|
||||
{
|
||||
// A null version returned means the check didn't pass at the version step
|
||||
var version = matcher.GetStreamVersion(file, stack, positions);
|
||||
if (version == null)
|
||||
continue;
|
||||
|
||||
matchedProtections.Enqueue($"{matcher.ProtectionName ?? "Unknown Protection"} {version}".Trim() + (includeDebug ? $" (Index {positionsString})" : string.Empty));
|
||||
// Trim and add the version
|
||||
version = version.Trim();
|
||||
if (version.Length > 0)
|
||||
matchString.Append($" {version}");
|
||||
}
|
||||
|
||||
// If we're stopping after the first protection, bail out here
|
||||
// Append the positional data if required
|
||||
if (includeDebug)
|
||||
{
|
||||
string positionsString = string.Join(", ", [.. positions.ConvertAll(p => p.ToString())]);
|
||||
matchString.Append($" (Index {positionsString})");
|
||||
}
|
||||
|
||||
// Append the match to the list
|
||||
matchesList.Add(matchString.ToString());
|
||||
|
||||
// If we're stopping after the first match, bail out here
|
||||
if (stopAfterFirst)
|
||||
return matchedProtections;
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
return matchedProtections;
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -255,152 +244,121 @@ namespace SabreTools.Matching
|
||||
/// <summary>
|
||||
/// Get all path matches for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="file">File path to check for matches</param>
|
||||
/// <param name="matchers">Enumerable of PathMatchSets to be run on the file</param>
|
||||
/// <param name="stack">File path to check for matches</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
|
||||
#if NET20 || NET35
|
||||
public static Queue<string> GetAllMatches(string file, IEnumerable<PathMatchSet>? matchers, bool any = false)
|
||||
#else
|
||||
public static ConcurrentQueue<string> GetAllMatches(string file, IEnumerable<PathMatchSet>? matchers, bool any = false)
|
||||
#endif
|
||||
{
|
||||
return FindAllMatches(new List<string> { file }, matchers, any, false);
|
||||
}
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
public static List<string> GetAllMatches(string stack, List<PathMatchSet> matchSets, bool any = false)
|
||||
=> FindAllMatches([stack], matchSets, any, false);
|
||||
|
||||
// <summary>
|
||||
/// <summary>
|
||||
/// Get all path matches for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="files">File paths to check for matches</param>
|
||||
/// <param name="matchers">Enumerable of PathMatchSets to be run on the file</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
|
||||
#if NET20 || NET35
|
||||
public static Queue<string> GetAllMatches(IEnumerable<string>? files, IEnumerable<PathMatchSet>? matchers, bool any = false)
|
||||
#else
|
||||
public static ConcurrentQueue<string> GetAllMatches(IEnumerable<string>? files, IEnumerable<PathMatchSet>? matchers, bool any = false)
|
||||
#endif
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
public static List<string> GetAllMatches(List<string>? stack, List<PathMatchSet> matchSets, bool any = false)
|
||||
=> FindAllMatches(stack, matchSets, any, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get first path match for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="stack">File path to check for matches</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <returns>String representing the match, null otherwise</returns>
|
||||
public static string? GetFirstMatch(string stack, List<PathMatchSet> matchSets, bool any = false)
|
||||
{
|
||||
return FindAllMatches(files, matchers, any, false);
|
||||
var contentMatches = FindAllMatches([stack], matchSets, any, true);
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
return contentMatches[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get first path match for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="file">File path to check for matches</param>
|
||||
/// <param name="matchers">Enumerable of PathMatchSets to be run on the file</param>
|
||||
/// <param name="stack">File paths to check for matches</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <returns>String representing the matched protection, null otherwise</returns>
|
||||
public static string? GetFirstMatch(string file, IEnumerable<PathMatchSet> matchers, bool any = false)
|
||||
/// <returns>String representing the match, null otherwise</returns>
|
||||
public static string? GetFirstMatch(List<string> stack, List<PathMatchSet> matchSets, bool any = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(new List<string> { file }, matchers, any, true);
|
||||
var contentMatches = FindAllMatches(stack, matchSets, any, true);
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
#if NET20 || NET35
|
||||
return contentMatches.Peek();
|
||||
#else
|
||||
return contentMatches.First();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get first path match for a given list of matchers
|
||||
/// </summary>
|
||||
/// <param name="files">File paths to check for matches</param>
|
||||
/// <param name="matchers">Enumerable of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <returns>String representing the matched protection, null otherwise</returns>
|
||||
public static string? GetFirstMatch(IEnumerable<string> files, IEnumerable<PathMatchSet> matchers, bool any = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(files, matchers, any, true);
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
#if NET20 || NET35
|
||||
return contentMatches.Peek();
|
||||
#else
|
||||
return contentMatches.First();
|
||||
#endif
|
||||
return contentMatches[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the required set of path matches on a per Matcher basis
|
||||
/// </summary>
|
||||
/// <param name="files">File paths to check for matches</param>
|
||||
/// <param name="matchers">Enumerable of PathMatchSets to be run on the file</param>
|
||||
/// <param name="stack">File paths to check for matches</param>
|
||||
/// <param name="matchSets">List of PathMatchSets to be run on the file</param>
|
||||
/// <param name="any">True if any path match is a success, false if all have to match</param>
|
||||
/// <param name="stopAfterFirst">True to stop after the first match, false otherwise</param>
|
||||
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
|
||||
#if NET20 || NET35
|
||||
private static Queue<string> FindAllMatches(IEnumerable<string>? files, IEnumerable<PathMatchSet>? matchers, bool any, bool stopAfterFirst)
|
||||
#else
|
||||
private static ConcurrentQueue<string> FindAllMatches(IEnumerable<string>? files, IEnumerable<PathMatchSet>? matchers, bool any, bool stopAfterFirst)
|
||||
#endif
|
||||
/// <returns>List of strings representing the matches, null or empty otherwise</returns>
|
||||
private static List<string> FindAllMatches(List<string>? stack, List<PathMatchSet> matchSets, bool any, bool stopAfterFirst)
|
||||
{
|
||||
// If there's no mappings, we can't match
|
||||
#if NET20 || NET35
|
||||
if (matchers == null || new List<PathMatchSet>(matchers).Count == 0)
|
||||
#else
|
||||
if (matchers == null || !matchers.Any())
|
||||
#endif
|
||||
return new();
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Count == 0 || matchSets.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the list of matched protections
|
||||
#if NET20 || NET35
|
||||
var matchedProtections = new Queue<string>();
|
||||
#else
|
||||
var matchedProtections = new ConcurrentQueue<string>();
|
||||
#endif
|
||||
// Initialize the list of matches
|
||||
var matchesList = new List<string>();
|
||||
|
||||
// Loop through and try everything otherwise
|
||||
foreach (var matcher in matchers)
|
||||
foreach (var matcher in matchSets)
|
||||
{
|
||||
// Determine if the matcher passes
|
||||
bool passes;
|
||||
string? firstMatchedString;
|
||||
List<string> matches = [];
|
||||
if (any)
|
||||
{
|
||||
string? matchedString = matcher.MatchesAny(files);
|
||||
passes = matchedString != null;
|
||||
firstMatchedString = matchedString;
|
||||
string? anyMatch = matcher.MatchesAny(stack);
|
||||
if (anyMatch != null)
|
||||
matches = [anyMatch];
|
||||
}
|
||||
else
|
||||
{
|
||||
List<string> matchedStrings = matcher.MatchesAll(files);
|
||||
passes = matchedStrings.Count > 0;
|
||||
firstMatchedString = passes ? matchedStrings[0] : null;
|
||||
matches = matcher.MatchesAll(stack);
|
||||
}
|
||||
|
||||
// If we don't have a pass, just continue
|
||||
if (!passes || firstMatchedString == null)
|
||||
if (matches.Count == 0)
|
||||
continue;
|
||||
|
||||
// If we there is no version method, just return the protection name
|
||||
if (matcher.GetVersion == null)
|
||||
{
|
||||
matchedProtections.Enqueue(matcher.ProtectionName ?? "Unknown Protection");
|
||||
}
|
||||
// Build the output string
|
||||
var matchString = new StringBuilder();
|
||||
matchString.Append(matcher.SetName);
|
||||
|
||||
// Otherwise, invoke the version method
|
||||
else
|
||||
// Invoke the version delegate, if it exists
|
||||
if (matcher.GetVersion != null)
|
||||
{
|
||||
// A null version returned means the check didn't pass at the version step
|
||||
var version = matcher.GetVersion(firstMatchedString, files);
|
||||
var version = matcher.GetVersion(matches[0], stack);
|
||||
if (version == null)
|
||||
continue;
|
||||
|
||||
matchedProtections.Enqueue($"{matcher.ProtectionName ?? "Unknown Protection"} {version}".Trim());
|
||||
// Trim and add the version
|
||||
version = version.Trim();
|
||||
if (version.Length > 0)
|
||||
matchString.Append($" {version}");
|
||||
}
|
||||
|
||||
// If we're stopping after the first protection, bail out here
|
||||
// Append the match to the list
|
||||
matchesList.Add(matchString.ToString());
|
||||
|
||||
// If we're stopping after the first match, bail out here
|
||||
if (stopAfterFirst)
|
||||
return matchedProtections;
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
return matchedProtections;
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace SabreTools.Matching.Paths
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">String representing the search</param>
|
||||
public FilePathMatch(string needle) : base($"{Path.DirectorySeparatorChar}{needle}", false, true) { }
|
||||
/// <param name="matchCase">True to match exact casing, false otherwise</param>
|
||||
public FilePathMatch(string needle, bool matchCase = false)
|
||||
: base($"{Path.DirectorySeparatorChar}{needle}", matchCase, true) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Matching.Paths
|
||||
{
|
||||
@@ -13,63 +11,76 @@ namespace SabreTools.Matching.Paths
|
||||
/// <summary>
|
||||
/// String to match
|
||||
/// </summary>
|
||||
#if NETFRAMEWORK || NETCOREAPP
|
||||
public string? Needle { get; private set; }
|
||||
#else
|
||||
public string? Needle { get; init; }
|
||||
#endif
|
||||
public string Needle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Match exact casing instead of invariant
|
||||
/// Match casing instead of invariant
|
||||
/// </summary>
|
||||
public bool MatchExact { get; private set; }
|
||||
private readonly bool _matchCase;
|
||||
|
||||
/// <summary>
|
||||
/// Match that values end with the needle and not just contains
|
||||
/// </summary>
|
||||
public bool UseEndsWith { get; private set; }
|
||||
private readonly bool _useEndsWith;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">String representing the search</param>
|
||||
/// <param name="matchExact">True to match exact casing, false otherwise</param>
|
||||
/// <param name="useEndsWith">True to match the end only, false for all contents</param>
|
||||
public PathMatch(string? needle, bool matchExact = false, bool useEndsWith = false)
|
||||
/// <param name="matchCase">True to match exact casing, false otherwise</param>
|
||||
/// <param name="useEndsWith">True to match the end only, false for contains</param>
|
||||
public PathMatch(string needle, bool matchCase = false, bool useEndsWith = false)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needle.Length == 0)
|
||||
throw new InvalidDataException(nameof(needle));
|
||||
|
||||
Needle = needle;
|
||||
MatchExact = matchExact;
|
||||
UseEndsWith = useEndsWith;
|
||||
_matchCase = matchCase;
|
||||
_useEndsWith = useEndsWith;
|
||||
}
|
||||
|
||||
#region Conversion
|
||||
|
||||
/// <summary>
|
||||
/// Allow conversion from string to PathMatch
|
||||
/// </summary>
|
||||
public static implicit operator PathMatch(string needle) => new PathMatch(needle);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">Array of strings to search for the given content</param>
|
||||
/// <returns>Matched item on success, null on error</returns>
|
||||
public string? Match(string[]? stack)
|
||||
=> Match(stack == null ? null : new List<string>(stack));
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to search for the given content</param>
|
||||
/// <returns>Matched item on success, null on error</returns>
|
||||
public string? Match(IEnumerable<string>? stack)
|
||||
public string? Match(List<string>? stack)
|
||||
{
|
||||
// If either array is null or empty, we can't do anything
|
||||
#if NET20 || NET35
|
||||
if (stack == null || new List<string>(stack).Count == 0 || Needle == null || Needle.Length == 0)
|
||||
#else
|
||||
if (stack == null || !stack.Any() || Needle == null || Needle.Length == 0)
|
||||
#endif
|
||||
// If either set is null or empty
|
||||
if (stack == null || stack.Count == 0 || Needle.Length == 0)
|
||||
return null;
|
||||
|
||||
// Preprocess the needle, if necessary
|
||||
string procNeedle = MatchExact ? Needle : Needle.ToLowerInvariant();
|
||||
string procNeedle = _matchCase ? Needle : Needle.ToLowerInvariant();
|
||||
|
||||
foreach (string stackItem in stack)
|
||||
{
|
||||
// Preprocess the stack item, if necessary
|
||||
string procStackItem = MatchExact ? stackItem : stackItem.ToLowerInvariant();
|
||||
string procStackItem = _matchCase ? stackItem : stackItem.ToLowerInvariant();
|
||||
|
||||
if (UseEndsWith && procStackItem.EndsWith(procNeedle))
|
||||
if (_useEndsWith && procStackItem.EndsWith(procNeedle))
|
||||
return stackItem;
|
||||
else if (!UseEndsWith && procStackItem.Contains(procNeedle))
|
||||
else if (!_useEndsWith && procStackItem.Contains(procNeedle))
|
||||
return stackItem;
|
||||
}
|
||||
|
||||
@@ -78,4 +89,4 @@ namespace SabreTools.Matching.Paths
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Matching.Paths
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of path matches that work together
|
||||
/// </summary>
|
||||
public class PathMatchSet : MatchSet<PathMatch, string>
|
||||
public class PathMatchSet : IMatchSet<PathMatch, string>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public List<PathMatch> Matchers { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string SetName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Function to get a path version for this Matcher
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A path version method takes the matched path and an enumerable of files
|
||||
/// and returns a single string. That string is either a version string,
|
||||
/// in which case it will be appended to the protection name, or `null`,
|
||||
/// in which case it will cause the protection to be omitted.
|
||||
/// in which case it will be appended to the match name, or `null`,
|
||||
/// in which case it will cause the match name to be omitted.
|
||||
/// </remarks>
|
||||
public Func<string, IEnumerable<string>?, string?>? GetVersion { get; private set; }
|
||||
public GetPathVersion? GetVersion { get; }
|
||||
|
||||
#region Constructors
|
||||
#region Generic Constructors
|
||||
|
||||
public PathMatchSet(string needle, string protectionName)
|
||||
: this(new List<string> { needle }, null, protectionName) { }
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">PathMatch representing the comparisons</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public PathMatchSet(PathMatch needle, string setName)
|
||||
: this([needle], setName) { }
|
||||
|
||||
public PathMatchSet(List<string> needles, string protectionName)
|
||||
: this(needles, null, protectionName) { }
|
||||
|
||||
public PathMatchSet(string needle, Func<string, IEnumerable<string>?, string?>? getVersion, string protectionName)
|
||||
: this(new List<string> { needle }, getVersion, protectionName) { }
|
||||
|
||||
#if NET20 || NET35
|
||||
public PathMatchSet(List<string> needles, Func<string, IEnumerable<string>?, string?>? getVersion, string protectionName)
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of PathMatch objects representing the comparisons</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public PathMatchSet(List<PathMatch> needles, string setName)
|
||||
{
|
||||
var matchers = new List<PathMatch>();
|
||||
foreach (var n in needles)
|
||||
{
|
||||
matchers.Add(new PathMatch(n));
|
||||
}
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
Matchers = matchers;
|
||||
GetVersion = getVersion;
|
||||
ProtectionName = protectionName;
|
||||
}
|
||||
#else
|
||||
public PathMatchSet(List<string> needles, Func<string, IEnumerable<string>?, string?>? getVersion, string protectionName)
|
||||
: this(needles.Select(n => new PathMatch(n)).ToList(), getVersion, protectionName) { }
|
||||
#endif
|
||||
|
||||
public PathMatchSet(PathMatch needle, string protectionName)
|
||||
: this(new List<PathMatch>() { needle }, null, protectionName) { }
|
||||
|
||||
public PathMatchSet(List<PathMatch> needles, string protectionName)
|
||||
: this(needles, null, protectionName) { }
|
||||
|
||||
public PathMatchSet(PathMatch needle, Func<string, IEnumerable<string>?, string?>? getVersion, string protectionName)
|
||||
: this(new List<PathMatch>() { needle }, getVersion, protectionName) { }
|
||||
|
||||
public PathMatchSet(List<PathMatch> needles, Func<string, IEnumerable<string>?, string?>? getVersion, string protectionName)
|
||||
{
|
||||
Matchers = needles;
|
||||
SetName = setName;
|
||||
GetVersion = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Version Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needle">PathMatch representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public PathMatchSet(PathMatch needle, GetPathVersion getVersion, string setName)
|
||||
: this([needle], getVersion, setName) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="needles">List of PathMatch objects representing the comparisons</param>
|
||||
/// <param name="getVersion">Delegate for deriving a version on match</param>
|
||||
/// <param name="setName">Unique name for the set</param>
|
||||
public PathMatchSet(List<PathMatch> needles, GetPathVersion getVersion, string setName)
|
||||
{
|
||||
// Validate the inputs
|
||||
if (needles.Count == 0)
|
||||
throw new InvalidDataException(nameof(needles));
|
||||
|
||||
Matchers = needles;
|
||||
SetName = setName;
|
||||
GetVersion = getVersion;
|
||||
ProtectionName = protectionName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to search for the given content</param>
|
||||
/// <returns>Matched item on success, null on error</returns>
|
||||
public List<string> MatchesAll(string[]? stack)
|
||||
=> MatchesAll(stack == null ? null : new List<string>(stack));
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether all path matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to try to match</param>
|
||||
/// <returns>List of matching values, if any</returns>
|
||||
public List<string> MatchesAll(IEnumerable<string>? stack)
|
||||
public List<string> MatchesAll(List<string>? stack)
|
||||
{
|
||||
// If no path matches are defined, we fail out
|
||||
#if NET20 || NET35
|
||||
if (Matchers == null || new List<PathMatch>(Matchers).Count == 0)
|
||||
#else
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
#endif
|
||||
// If either set is null or empty, we can't do anything
|
||||
if (stack == null || stack.Count == 0 || Matchers.Count == 0)
|
||||
return [];
|
||||
|
||||
// Initialize the value list
|
||||
@@ -102,19 +120,23 @@ namespace SabreTools.Matching.Paths
|
||||
return values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get if this match can be found in a stack
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to search for the given content</param>
|
||||
/// <returns>Matched item on success, null on error</returns>
|
||||
public string? MatchesAny(string[]? stack)
|
||||
=> MatchesAny(stack == null ? null : new List<string>(stack));
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether any path matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to try to match</param>
|
||||
/// <returns>First matching value on success, null on error</returns>
|
||||
public string? MatchesAny(IEnumerable<string>? stack)
|
||||
public string? MatchesAny(List<string>? stack)
|
||||
{
|
||||
// If no path matches are defined, we fail out
|
||||
#if NET20 || NET35
|
||||
if (Matchers == null || new List<PathMatch>(Matchers).Count == 0)
|
||||
#else
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
#endif
|
||||
// If either set is null or empty, we can't do anything
|
||||
if (stack == null || stack.Count == 0 || Matchers.Count == 0)
|
||||
return null;
|
||||
|
||||
// Loop through all path matches and make sure all pass
|
||||
@@ -130,4 +152,4 @@ namespace SabreTools.Matching.Paths
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.3.2</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>Byte array and stream matching library</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2018-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Matching</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>byte array stream match matching</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../README.md" Pack="true" PackagePath=""/>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>NU1903</NoWarn>
|
||||
<Nullable>enable</Nullable>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.6.0</Version>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`))">
|
||||
<PackageReference Include="Net30.LinqBridge" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>Byte array and stream matching library</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2018-2025</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Matching</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>byte array stream match matching</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="SabreTools.Matching.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
4
publish-nix.sh
Normal file → Executable file
4
publish-nix.sh
Normal file → Executable file
@@ -1,14 +1,14 @@
|
||||
#! /bin/bash
|
||||
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.0 (or newer) SDK is installed and in PATH
|
||||
# - .NET 9.0 (or newer) SDK is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
# Optional parameters
|
||||
NO_BUILD=false
|
||||
while getopts "uba" OPTION
|
||||
while getopts "b" OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
b)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.0 (or newer) SDK is installed and in PATH
|
||||
# - .NET 9.0 (or newer) SDK is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
Reference in New Issue
Block a user