mirror of
https://github.com/SabreTools/SabreTools.Matching.git
synced 2026-02-06 21:29:41 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
883dd00e8f | ||
|
|
aa8506e0ec | ||
|
|
e25fb02f03 | ||
|
|
aa16a83426 | ||
|
|
cbf6523a8f | ||
|
|
e947687f42 | ||
|
|
b4510f00e1 | ||
|
|
058923a39d | ||
|
|
113e8c9151 | ||
|
|
cb3a6f8992 | ||
|
|
5a3de8e124 | ||
|
|
08ef69e7c1 | ||
|
|
19971ec62c | ||
|
|
4650399bc1 | ||
|
|
aad97d03fd |
4
.github/workflows/build_nupkg.yml
vendored
4
.github/workflows/build_nupkg.yml
vendored
@@ -28,13 +28,13 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'Nuget Package'
|
||||
path: 'bin/Release/*.nupkg'
|
||||
path: 'SabreTools.Matching/bin/Release/*.nupkg'
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: 'bin/Release/*.nupkg'
|
||||
artifacts: 'SabreTools.Matching/bin/Release/*.nupkg'
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
|
||||
BIN
SabreTools.Matching.1.3.2.nupkg
Normal file
BIN
SabreTools.Matching.1.3.2.nupkg
Normal file
Binary file not shown.
38
SabreTools.Matching.Test/Compare/NaturalComparerTests.cs
Normal file
38
SabreTools.Matching.Test/Compare/NaturalComparerTests.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using SabreTools.Matching.Compare;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test.Compare
|
||||
{
|
||||
public class NaturalComparerTests
|
||||
{
|
||||
[Fact]
|
||||
public void NaturalComparerListSortTest()
|
||||
{
|
||||
// Setup arrays
|
||||
string[] sortable = ["0", "100", "5", "2", "1000"];
|
||||
string[] expected = ["0", "2", "5", "100", "1000"];
|
||||
|
||||
// Run sorting on array
|
||||
Array.Sort(sortable, new NaturalComparer());
|
||||
|
||||
// Check the output
|
||||
Assert.True(sortable.SequenceEqual(expected));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NaturalReversedComparerListSortTest()
|
||||
{
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
59
SabreTools.Matching.Test/Compare/NaturalComparerUtilTests.cs
Normal file
59
SabreTools.Matching.Test/Compare/NaturalComparerUtilTests.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using SabreTools.Matching.Compare;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Matching.Test.Compare
|
||||
{
|
||||
public class NaturalComparerUtilTests
|
||||
{
|
||||
[Fact]
|
||||
public void CompareNumericBothNullTest()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric(null, null);
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericSingleNullTest()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric(null, "notnull");
|
||||
Assert.Equal(-1, actual);
|
||||
|
||||
actual = NaturalComparerUtil.CompareNumeric("notnull", null);
|
||||
Assert.Equal(1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericBothEqualTest()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric("notnull", "notnull");
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericBothEqualWithPathTest()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric("notnull/file.ext", "notnull/file.ext");
|
||||
Assert.Equal(0, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericNumericNonDecimalStringTest()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric("100", "10");
|
||||
Assert.Equal(1, actual);
|
||||
|
||||
actual = NaturalComparerUtil.CompareNumeric("10", "100");
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareNumericNumericDecimalStringTest()
|
||||
{
|
||||
int actual = NaturalComparerUtil.CompareNumeric("100.100", "100.10");
|
||||
Assert.Equal(1, actual);
|
||||
|
||||
actual = NaturalComparerUtil.CompareNumeric("100.10", "100.100");
|
||||
Assert.Equal(-1, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
SabreTools.Matching.Test/SabreTools.Matching.Test.csproj
Normal file
29
SabreTools.Matching.Test/SabreTools.Matching.Test.csproj
Normal file
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
|
||||
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Matching", "SabreTools.Matching.csproj", "{9555EB79-4E81-4988-9ABE-D6CE6042197E}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Matching", "SabreTools.Matching\SabreTools.Matching.csproj", "{9555EB79-4E81-4988-9ABE-D6CE6042197E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Matching.Test", "SabreTools.Matching.Test\SabreTools.Matching.Test.csproj", "{B17EB9F4-E041-4E5C-A966-D2BEEDEA621F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -18,5 +20,9 @@ Global
|
||||
{9555EB79-4E81-4988-9ABE-D6CE6042197E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9555EB79-4E81-4988-9ABE-D6CE6042197E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9555EB79-4E81-4988-9ABE-D6CE6042197E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B17EB9F4-E041-4E5C-A966-D2BEEDEA621F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B17EB9F4-E041-4E5C-A966-D2BEEDEA621F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B17EB9F4-E041-4E5C-A966-D2BEEDEA621F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B17EB9F4-E041-4E5C-A966-D2BEEDEA621F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
121
SabreTools.Matching/Compare/NaturalComparer.cs
Normal file
121
SabreTools.Matching/Compare/NaturalComparer.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
*
|
||||
* Links for info and original source code:
|
||||
*
|
||||
* https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
|
||||
* http://www.codeproject.com/Articles/22517/Natural-Sort-Comparer
|
||||
*
|
||||
* Exact code implementation used with permission, originally by motoschifo
|
||||
*
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
public NaturalComparer()
|
||||
{
|
||||
table = [];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
table.Clear();
|
||||
}
|
||||
|
||||
public override int Compare(string? x, string? y)
|
||||
{
|
||||
if (x == null || y == null)
|
||||
{
|
||||
if (x == null && y != null)
|
||||
return -1;
|
||||
else if (x != null && y == null)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (x.ToLowerInvariant() == y.ToLowerInvariant())
|
||||
return x.CompareTo(y);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
for (int i = 0; i < x1.Length && i < y1.Length; i++)
|
||||
{
|
||||
if (x1[i] != y1[i])
|
||||
return PartCompare(x1[i], y1[i]);
|
||||
}
|
||||
|
||||
if (y1.Length > x1.Length)
|
||||
return 1;
|
||||
else if (x1.Length > y1.Length)
|
||||
return -1;
|
||||
else
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
|
||||
private static int PartCompare(string left, string right)
|
||||
{
|
||||
if (!long.TryParse(left, out long x))
|
||||
return NaturalComparerUtil.CompareNumeric(left, right);
|
||||
|
||||
if (!long.TryParse(right, out long y))
|
||||
return NaturalComparerUtil.CompareNumeric(left, right);
|
||||
|
||||
// If we have an equal part, then make sure that "longer" ones are taken into account
|
||||
if (x.CompareTo(y) == 0)
|
||||
return left.Length - right.Length;
|
||||
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
SabreTools.Matching/Compare/NaturalComparerUtil.cs
Normal file
85
SabreTools.Matching/Compare/NaturalComparerUtil.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Matching.Compare
|
||||
{
|
||||
public static class NaturalComparerUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two strings by numeric parts
|
||||
/// </summary>
|
||||
public static int CompareNumeric(string? s1, string? s2)
|
||||
{
|
||||
// If both strings are null, return
|
||||
if (s1 == null && s2 == null)
|
||||
return 0;
|
||||
|
||||
// If one is null, then say that's less than
|
||||
if (s1 == null)
|
||||
return -1;
|
||||
if (s2 == null)
|
||||
return 1;
|
||||
|
||||
// Save the orginal strings, for later comparison
|
||||
string s1orig = s1;
|
||||
string s2orig = s2;
|
||||
|
||||
// We want to normalize the strings, so we set both to lower case
|
||||
s1 = s1.ToLowerInvariant();
|
||||
s2 = s2.ToLowerInvariant();
|
||||
|
||||
// If the strings are the same exactly, return
|
||||
if (s1 == s2)
|
||||
return s1orig.CompareTo(s2orig);
|
||||
|
||||
// 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);
|
||||
|
||||
// Then compare each part in turn
|
||||
for (int j = 0; j < s1parts.Length && j < s2parts.Length; j++)
|
||||
{
|
||||
int compared = CompareNumericPart(s1parts[j], s2parts[j]);
|
||||
if (compared != 0)
|
||||
return compared;
|
||||
}
|
||||
|
||||
// If we got out here, then it looped through at least one of the strings
|
||||
if (s1parts.Length > s2parts.Length)
|
||||
return 1;
|
||||
if (s1parts.Length < s2parts.Length)
|
||||
return -1;
|
||||
|
||||
return s1orig.CompareTo(s2orig);
|
||||
}
|
||||
|
||||
private static int CompareNumericPart(string s1, string s2)
|
||||
{
|
||||
// Otherwise, loop through until we have an answer
|
||||
for (int i = 0; i < s1.Length && i < s2.Length; i++)
|
||||
{
|
||||
int s1c = s1[i];
|
||||
int s2c = s2[i];
|
||||
|
||||
// If the characters are the same, continue
|
||||
if (s1c == s2c)
|
||||
continue;
|
||||
|
||||
// If they're different, check which one was larger
|
||||
if (s1c > s2c)
|
||||
return 1;
|
||||
if (s1c < s2c)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If we got out here, then it looped through at least one of the strings
|
||||
if (s1.Length > s2.Length)
|
||||
return 1;
|
||||
if (s1.Length < s2.Length)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
SabreTools.Matching/Compare/NaturalReversedComparer.cs
Normal file
121
SabreTools.Matching/Compare/NaturalReversedComparer.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
*
|
||||
* Links for info and original source code:
|
||||
*
|
||||
* https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
|
||||
* http://www.codeproject.com/Articles/22517/Natural-Sort-Comparer
|
||||
*
|
||||
* Exact code implementation used with permission, originally by motoschifo
|
||||
*
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
public NaturalReversedComparer()
|
||||
{
|
||||
table = [];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
table.Clear();
|
||||
}
|
||||
|
||||
public override int Compare(string? x, string? y)
|
||||
{
|
||||
if (x == null || y == null)
|
||||
{
|
||||
if (x == null && y != null)
|
||||
return -1;
|
||||
else if (x != null && y == null)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (y.ToLowerInvariant() == x.ToLowerInvariant())
|
||||
return y.CompareTo(x);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
for (int i = 0; i < x1.Length && i < y1.Length; i++)
|
||||
{
|
||||
if (x1[i] != y1[i])
|
||||
return PartCompare(x1[i], y1[i]);
|
||||
}
|
||||
|
||||
if (y1.Length > x1.Length)
|
||||
return 1;
|
||||
else if (x1.Length > y1.Length)
|
||||
return -1;
|
||||
else
|
||||
return y.CompareTo(x);
|
||||
}
|
||||
|
||||
private static int PartCompare(string left, string right)
|
||||
{
|
||||
if (!long.TryParse(left, out long x))
|
||||
return NaturalComparerUtil.CompareNumeric(right, left);
|
||||
|
||||
if (!long.TryParse(right, out long y))
|
||||
return NaturalComparerUtil.CompareNumeric(right, left);
|
||||
|
||||
// If we have an equal part, then make sure that "longer" ones are taken into account
|
||||
if (y.CompareTo(x) == 0)
|
||||
return right.Length - left.Length;
|
||||
|
||||
return y.CompareTo(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Matching
|
||||
namespace SabreTools.Matching.Content
|
||||
{
|
||||
/// <summary>
|
||||
/// Content matching criteria
|
||||
@@ -46,16 +46,16 @@ namespace SabreTools.Matching
|
||||
/// </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>Tuple of success and found position</returns>
|
||||
public (bool success, int position) Match(byte[]? stack, bool reverse = false)
|
||||
/// <returns>Found position on success, -1 on error</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)
|
||||
return (false, -1);
|
||||
return -1;
|
||||
|
||||
// If the needle array is larger than the stack array, it can't be contained within
|
||||
if (this.Needle.Length > stack.Length)
|
||||
return (false, -1);
|
||||
return -1;
|
||||
|
||||
// Set the default start and end values
|
||||
int start = this.Start;
|
||||
@@ -71,14 +71,14 @@ namespace SabreTools.Matching
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
return (false, -1);
|
||||
return -1;
|
||||
|
||||
// Check to see if the values are equal
|
||||
if (EqualAt(stack, i))
|
||||
return (true, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
return (false, -1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,16 +123,16 @@ namespace SabreTools.Matching
|
||||
/// </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>Tuple of success and found position</returns>
|
||||
public (bool success, int position) Match(Stream? stack, bool reverse = false)
|
||||
/// <returns>Found position on success, -1 on error</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)
|
||||
return (false, -1);
|
||||
return -1;
|
||||
|
||||
// If the needle array is larger than the stack array, it can't be contained within
|
||||
if (this.Needle.Length > stack.Length)
|
||||
return (false, -1);
|
||||
return -1;
|
||||
|
||||
// Set the default start and end values
|
||||
int start = this.Start;
|
||||
@@ -148,14 +148,14 @@ namespace SabreTools.Matching
|
||||
{
|
||||
// If we somehow have an invalid end and we haven't matched, return
|
||||
if (i > stack.Length)
|
||||
return (false, -1);
|
||||
return -1;
|
||||
|
||||
// Check to see if the values are equal
|
||||
if (EqualAt(stack, i))
|
||||
return (true, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
return (false, -1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
|
||||
namespace SabreTools.Matching
|
||||
namespace SabreTools.Matching.Content
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of content matches that work together
|
||||
@@ -55,8 +57,23 @@ namespace SabreTools.Matching
|
||||
public ContentMatchSet(byte?[] needle, Func<string, byte[]?, List<int>, string?>? getArrayVersion, string protectionName)
|
||||
: this(new List<byte?[]> { needle }, getArrayVersion, protectionName) { }
|
||||
|
||||
#if NET20 || NET35
|
||||
public ContentMatchSet(List<byte?[]> needles, Func<string, byte[]?, List<int>, string?>? getArrayVersion, string protectionName)
|
||||
{
|
||||
var matchers = new List<ContentMatch>();
|
||||
foreach (var n in needles)
|
||||
{
|
||||
matchers.Add(new ContentMatch(n));
|
||||
}
|
||||
|
||||
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) { }
|
||||
@@ -75,8 +92,23 @@ namespace SabreTools.Matching
|
||||
public ContentMatchSet(byte?[] needle, Func<string, Stream?, List<int>, string?>? getStreamVersion, string protectionName)
|
||||
: this(new List<byte?[]> { needle }, getStreamVersion, protectionName) { }
|
||||
|
||||
#if NET20 || NET35
|
||||
public ContentMatchSet(List<byte?[]> needles, Func<string, Stream?, List<int>, string?>? getStreamVersion, string protectionName)
|
||||
{
|
||||
var matchers = new List<ContentMatch>();
|
||||
foreach (var n in needles)
|
||||
{
|
||||
matchers.Add(new ContentMatch(n));
|
||||
}
|
||||
|
||||
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) { }
|
||||
@@ -96,12 +128,16 @@ namespace SabreTools.Matching
|
||||
/// Determine whether all content matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <returns>Tuple of passing status and matching positions</returns>
|
||||
public (bool, List<int>) MatchesAll(byte[]? stack)
|
||||
/// <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())
|
||||
return (false, new List<int>());
|
||||
#endif
|
||||
return [];
|
||||
|
||||
// Initialize the position list
|
||||
var positions = new List<int>();
|
||||
@@ -109,36 +145,40 @@ namespace SabreTools.Matching
|
||||
// Loop through all content matches and make sure all pass
|
||||
foreach (var contentMatch in Matchers)
|
||||
{
|
||||
(bool match, int position) = contentMatch.Match(stack);
|
||||
if (!match)
|
||||
return (false, new List<int>());
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position < 0)
|
||||
return [];
|
||||
else
|
||||
positions.Add(position);
|
||||
}
|
||||
|
||||
return (true, positions);
|
||||
return positions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether any content matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <returns>Tuple of passing status and first matching position</returns>
|
||||
public (bool, int) MatchesAny(byte[]? stack)
|
||||
/// <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())
|
||||
return (false, -1);
|
||||
#endif
|
||||
return -1;
|
||||
|
||||
// Loop through all content matches and make sure all pass
|
||||
foreach (var contentMatch in Matchers)
|
||||
{
|
||||
(bool match, int position) = contentMatch.Match(stack);
|
||||
if (match)
|
||||
return (true, position);
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position >= 0)
|
||||
return position;
|
||||
}
|
||||
|
||||
return (false, -1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -149,12 +189,16 @@ namespace SabreTools.Matching
|
||||
/// Determine whether all content matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <returns>Tuple of passing status and matching positions</returns>
|
||||
public (bool, List<int>) MatchesAll(Stream? stack)
|
||||
/// <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())
|
||||
return (false, new List<int>());
|
||||
#endif
|
||||
return [];
|
||||
|
||||
// Initialize the position list
|
||||
var positions = new List<int>();
|
||||
@@ -162,36 +206,40 @@ namespace SabreTools.Matching
|
||||
// Loop through all content matches and make sure all pass
|
||||
foreach (var contentMatch in Matchers)
|
||||
{
|
||||
(bool match, int position) = contentMatch.Match(stack);
|
||||
if (!match)
|
||||
return (false, new List<int>());
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position < 0)
|
||||
return [];
|
||||
else
|
||||
positions.Add(position);
|
||||
}
|
||||
|
||||
return (true, positions);
|
||||
return positions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether any content matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <returns>Tuple of passing status and first matching position</returns>
|
||||
public (bool, int) MatchesAny(Stream? stack)
|
||||
/// <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())
|
||||
return (false, -1);
|
||||
#endif
|
||||
return -1;
|
||||
|
||||
// Loop through all content matches and make sure all pass
|
||||
foreach (var contentMatch in Matchers)
|
||||
{
|
||||
(bool match, int position) = contentMatch.Match(stack);
|
||||
if (match)
|
||||
return (true, position);
|
||||
int position = contentMatch.Match(stack);
|
||||
if (position >= 0)
|
||||
return position;
|
||||
}
|
||||
|
||||
return (false, -1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1,10 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using SabreTools.Matching.Content;
|
||||
|
||||
namespace SabreTools.Matching
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the specified array is null or has a length of zero
|
||||
/// </summary>
|
||||
public static bool IsNullOrEmpty(this Array? array)
|
||||
{
|
||||
return array == null || array.Length == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all positions of one array in another, if possible, if possible
|
||||
/// </summary>
|
||||
@@ -14,17 +26,18 @@ namespace SabreTools.Matching
|
||||
List<int> positions = [];
|
||||
|
||||
// Initialize the loop variables
|
||||
bool found = true;
|
||||
int lastPosition = start;
|
||||
var matcher = new ContentMatch(needle, end: end);
|
||||
|
||||
// Loop over and get all positions
|
||||
while (found)
|
||||
while (true)
|
||||
{
|
||||
matcher.Start = lastPosition;
|
||||
(found, lastPosition) = matcher.Match(stack, false);
|
||||
if (found)
|
||||
positions.Add(lastPosition);
|
||||
lastPosition = matcher.Match(stack, false);
|
||||
if (lastPosition < 0)
|
||||
break;
|
||||
|
||||
positions.Add(lastPosition);
|
||||
}
|
||||
|
||||
return positions;
|
||||
@@ -35,7 +48,23 @@ namespace SabreTools.Matching
|
||||
/// </summary>
|
||||
public static bool FirstPosition(this byte[] stack, byte[]? needle, out int position, 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);
|
||||
}
|
||||
|
||||
@@ -45,9 +74,8 @@ namespace SabreTools.Matching
|
||||
public static bool FirstPosition(this byte[] stack, byte?[]? needle, out int position, int start = 0, int end = -1)
|
||||
{
|
||||
var matcher = new ContentMatch(needle, start, end);
|
||||
(bool found, int foundPosition) = matcher.Match(stack, false);
|
||||
position = foundPosition;
|
||||
return found;
|
||||
position = matcher.Match(stack, false);
|
||||
return position >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,18 +84,23 @@ namespace SabreTools.Matching
|
||||
public static bool LastPosition(this byte[] stack, byte?[]? needle, out int position, int start = 0, int end = -1)
|
||||
{
|
||||
var matcher = new ContentMatch(needle, start, end);
|
||||
(bool found, int foundPosition) = matcher.Match(stack, true);
|
||||
position = foundPosition;
|
||||
return found;
|
||||
position = matcher.Match(stack, true);
|
||||
return position >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See if a byte array starts with another
|
||||
/// </summary>
|
||||
public static bool StartsWith(this byte[] stack, byte[]? needle)
|
||||
public static bool StartsWith(this byte[] stack, byte[]? needle, bool exact = false)
|
||||
{
|
||||
if (needle == null)
|
||||
// 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);
|
||||
}
|
||||
@@ -75,10 +108,16 @@ namespace SabreTools.Matching
|
||||
/// <summary>
|
||||
/// See if a byte array starts with another
|
||||
/// </summary>
|
||||
public static bool StartsWith(this byte[] stack, byte?[]? needle)
|
||||
public static bool StartsWith(this byte[] stack, byte?[]? needle, bool exact = false)
|
||||
{
|
||||
if (needle == null)
|
||||
// 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);
|
||||
}
|
||||
@@ -86,10 +125,16 @@ namespace SabreTools.Matching
|
||||
/// <summary>
|
||||
/// See if a byte array ends with another
|
||||
/// </summary>
|
||||
public static bool EndsWith(this byte[] stack, byte[]? needle)
|
||||
public static bool EndsWith(this byte[] stack, byte[]? needle, bool exact = false)
|
||||
{
|
||||
if (needle == null)
|
||||
// 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);
|
||||
}
|
||||
@@ -97,10 +142,16 @@ namespace SabreTools.Matching
|
||||
/// <summary>
|
||||
/// See if a byte array ends with another
|
||||
/// </summary>
|
||||
public static bool EndsWith(this byte[] stack, byte?[]? needle)
|
||||
public static bool EndsWith(this byte[] stack, byte?[]? needle, bool exact = false)
|
||||
{
|
||||
if (needle == null)
|
||||
// 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);
|
||||
}
|
||||
@@ -3,7 +3,11 @@ using System.Collections.Concurrent;
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using SabreTools.Matching.Content;
|
||||
using SabreTools.Matching.Paths;
|
||||
|
||||
namespace SabreTools.Matching
|
||||
{
|
||||
@@ -42,10 +46,14 @@ namespace SabreTools.Matching
|
||||
public static string? GetFirstMatch(string file, byte[]? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true);
|
||||
if (contentMatches == null || !contentMatches.Any())
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
#if NET20 || NET35
|
||||
return contentMatches.Peek();
|
||||
#else
|
||||
return contentMatches.First();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,8 +72,12 @@ namespace SabreTools.Matching
|
||||
#endif
|
||||
{
|
||||
// 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())
|
||||
return null;
|
||||
#endif
|
||||
return null;
|
||||
|
||||
// Initialize the queue of matched protections
|
||||
#if NET20 || NET35
|
||||
@@ -78,8 +90,8 @@ namespace SabreTools.Matching
|
||||
foreach (var matcher in matchers)
|
||||
{
|
||||
// Determine if the matcher passes
|
||||
(bool passes, List<int> positions) = matcher.MatchesAll(stack);
|
||||
if (!passes)
|
||||
var positions = matcher.MatchesAll(stack);
|
||||
if (positions.Count == 0)
|
||||
continue;
|
||||
|
||||
// Format the list of all positions found
|
||||
@@ -151,10 +163,14 @@ namespace SabreTools.Matching
|
||||
public static string? GetFirstMatch(string file, Stream? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true);
|
||||
if (contentMatches == null || !contentMatches.Any())
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
#if NET20 || NET35
|
||||
return contentMatches.Peek();
|
||||
#else
|
||||
return contentMatches.First();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -173,7 +189,11 @@ namespace SabreTools.Matching
|
||||
#endif
|
||||
{
|
||||
// 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;
|
||||
|
||||
// Initialize the queue of matched protections
|
||||
@@ -187,8 +207,8 @@ namespace SabreTools.Matching
|
||||
foreach (var matcher in matchers)
|
||||
{
|
||||
// Determine if the matcher passes
|
||||
(bool passes, List<int> positions) = matcher.MatchesAll(stack);
|
||||
if (!passes)
|
||||
var positions = matcher.MatchesAll(stack);
|
||||
if (positions.Count == 0)
|
||||
continue;
|
||||
|
||||
// Format the list of all positions found
|
||||
@@ -274,10 +294,14 @@ namespace SabreTools.Matching
|
||||
public static string? GetFirstMatch(string file, IEnumerable<PathMatchSet> matchers, bool any = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(new List<string> { file }, matchers, any, true);
|
||||
if (contentMatches == null || !contentMatches.Any())
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
#if NET20 || NET35
|
||||
return contentMatches.Peek();
|
||||
#else
|
||||
return contentMatches.First();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -290,10 +314,14 @@ namespace SabreTools.Matching
|
||||
public static string? GetFirstMatch(IEnumerable<string> files, IEnumerable<PathMatchSet> matchers, bool any = false)
|
||||
{
|
||||
var contentMatches = FindAllMatches(files, matchers, any, true);
|
||||
if (contentMatches == null || !contentMatches.Any())
|
||||
if (contentMatches == null || contentMatches.Count == 0)
|
||||
return null;
|
||||
|
||||
#if NET20 || NET35
|
||||
return contentMatches.Peek();
|
||||
#else
|
||||
return contentMatches.First();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -311,7 +339,11 @@ namespace SabreTools.Matching
|
||||
#endif
|
||||
{
|
||||
// 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();
|
||||
|
||||
// Initialize the list of matched protections
|
||||
@@ -329,15 +361,15 @@ namespace SabreTools.Matching
|
||||
string? firstMatchedString;
|
||||
if (any)
|
||||
{
|
||||
(bool anyPasses, var matchedString) = matcher.MatchesAny(files);
|
||||
passes = anyPasses;
|
||||
string? matchedString = matcher.MatchesAny(files);
|
||||
passes = matchedString != null;
|
||||
firstMatchedString = matchedString;
|
||||
}
|
||||
else
|
||||
{
|
||||
(bool allPasses, List<string> matchedStrings) = matcher.MatchesAll(files);
|
||||
passes = allPasses;
|
||||
firstMatchedString = matchedStrings.FirstOrDefault();
|
||||
List<string> matchedStrings = matcher.MatchesAll(files);
|
||||
passes = matchedStrings.Count > 0;
|
||||
firstMatchedString = passes ? matchedStrings[0] : null;
|
||||
}
|
||||
|
||||
// If we don't have a pass, just continue
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Matching
|
||||
namespace SabreTools.Matching.Paths
|
||||
{
|
||||
/// <summary>
|
||||
/// File path matching criteria
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
|
||||
namespace SabreTools.Matching
|
||||
namespace SabreTools.Matching.Paths
|
||||
{
|
||||
/// <summary>
|
||||
/// Path matching criteria
|
||||
@@ -35,9 +37,9 @@ namespace SabreTools.Matching
|
||||
/// <param name="useEndsWith">True to match the end only, false for all contents</param>
|
||||
public PathMatch(string? needle, bool matchExact = false, bool useEndsWith = false)
|
||||
{
|
||||
this.Needle = needle;
|
||||
this.MatchExact = matchExact;
|
||||
this.UseEndsWith = useEndsWith;
|
||||
Needle = needle;
|
||||
MatchExact = matchExact;
|
||||
UseEndsWith = useEndsWith;
|
||||
}
|
||||
|
||||
#region Matching
|
||||
@@ -46,30 +48,34 @@ namespace SabreTools.Matching
|
||||
/// 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>Tuple of success and matched item</returns>
|
||||
public (bool, string?) Match(IEnumerable<string>? stack)
|
||||
/// <returns>Matched item on success, null on error</returns>
|
||||
public string? Match(IEnumerable<string>? stack)
|
||||
{
|
||||
// If either array is null or empty, we can't do anything
|
||||
if (stack == null || !stack.Any() || this.Needle == null || this.Needle.Length == 0)
|
||||
return (false, null);
|
||||
#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
|
||||
return null;
|
||||
|
||||
// Preprocess the needle, if necessary
|
||||
string procNeedle = this.MatchExact ? this.Needle : this.Needle.ToLowerInvariant();
|
||||
string procNeedle = MatchExact ? Needle : Needle.ToLowerInvariant();
|
||||
|
||||
foreach (string stackItem in stack)
|
||||
{
|
||||
// Preprocess the stack item, if necessary
|
||||
string procStackItem = this.MatchExact ? stackItem : stackItem.ToLowerInvariant();
|
||||
string procStackItem = MatchExact ? stackItem : stackItem.ToLowerInvariant();
|
||||
|
||||
if (this.UseEndsWith && procStackItem.EndsWith(procNeedle))
|
||||
return (true, stackItem);
|
||||
else if (!this.UseEndsWith && procStackItem.Contains(procNeedle))
|
||||
return (true, stackItem);
|
||||
if (UseEndsWith && procStackItem.EndsWith(procNeedle))
|
||||
return stackItem;
|
||||
else if (!UseEndsWith && procStackItem.Contains(procNeedle))
|
||||
return stackItem;
|
||||
}
|
||||
|
||||
return (false, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
|
||||
namespace SabreTools.Matching
|
||||
namespace SabreTools.Matching.Paths
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of path matches that work together
|
||||
@@ -31,8 +33,23 @@ namespace SabreTools.Matching
|
||||
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)
|
||||
{
|
||||
var matchers = new List<PathMatch>();
|
||||
foreach (var n in needles)
|
||||
{
|
||||
matchers.Add(new PathMatch(n));
|
||||
}
|
||||
|
||||
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) { }
|
||||
@@ -58,12 +75,16 @@ namespace SabreTools.Matching
|
||||
/// Determine whether all path matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to try to match</param>
|
||||
/// <returns>Tuple of passing status and matching values</returns>
|
||||
public (bool, List<string>) MatchesAll(IEnumerable<string>? stack)
|
||||
/// <returns>List of matching values, if any</returns>
|
||||
public List<string> MatchesAll(IEnumerable<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())
|
||||
return (false, new List<string>());
|
||||
#endif
|
||||
return [];
|
||||
|
||||
// Initialize the value list
|
||||
List<string> values = [];
|
||||
@@ -71,36 +92,40 @@ namespace SabreTools.Matching
|
||||
// Loop through all path matches and make sure all pass
|
||||
foreach (var pathMatch in Matchers)
|
||||
{
|
||||
(bool match, string? value) = pathMatch.Match(stack);
|
||||
if (!match || value == null)
|
||||
return (false, new List<string>());
|
||||
string? value = pathMatch.Match(stack);
|
||||
if (value == null)
|
||||
return [];
|
||||
else
|
||||
values.Add(value);
|
||||
}
|
||||
|
||||
return (true, values);
|
||||
return values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether any path matches pass
|
||||
/// </summary>
|
||||
/// <param name="stack">List of strings to try to match</param>
|
||||
/// <returns>Tuple of passing status and first matching value</returns>
|
||||
public (bool, string?) MatchesAny(IEnumerable<string>? stack)
|
||||
/// <returns>First matching value on success, null on error</returns>
|
||||
public string? MatchesAny(IEnumerable<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())
|
||||
return (false, null);
|
||||
#endif
|
||||
return null;
|
||||
|
||||
// Loop through all path matches and make sure all pass
|
||||
foreach (var pathMatch in Matchers)
|
||||
{
|
||||
(bool match, string? value) = pathMatch.Match(stack);
|
||||
if (match)
|
||||
return (true, value);
|
||||
string? value = pathMatch.Match(stack);
|
||||
if (value != null)
|
||||
return value;
|
||||
}
|
||||
|
||||
return (false, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -7,7 +7,7 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.3.0</Version>
|
||||
<Version>1.3.2</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
@@ -22,16 +22,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath=""/>
|
||||
<None Include="../README.md" Pack="true" PackagePath=""/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))">
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`))">
|
||||
<PackageReference Include="Net30.LinqBridge" Version="1.3.0" />
|
||||
<PackageReference Include="MinValueTupleBridge" Version="0.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`))">
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
36
publish-nix.sh
Normal file
36
publish-nix.sh
Normal file
@@ -0,0 +1,36 @@
|
||||
#! /bin/bash
|
||||
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.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
|
||||
do
|
||||
case $OPTION in
|
||||
b)
|
||||
NO_BUILD=true
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option provided"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set the current directory as a variable
|
||||
BUILD_FOLDER=$PWD
|
||||
|
||||
# Only build if requested
|
||||
if [ $NO_BUILD = false ]
|
||||
then
|
||||
# Restore Nuget packages for all builds
|
||||
echo "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Create Nuget Package
|
||||
dotnet pack SabreTools.Matching/SabreTools.Matching.csproj --output $BUILD_FOLDER
|
||||
fi
|
||||
26
publish-win.ps1
Normal file
26
publish-win.ps1
Normal file
@@ -0,0 +1,26 @@
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.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
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[Alias("NoBuild")]
|
||||
[switch]$NO_BUILD
|
||||
)
|
||||
|
||||
# Set the current directory as a variable
|
||||
$BUILD_FOLDER = $PSScriptRoot
|
||||
|
||||
# Only build if requested
|
||||
if (!$NO_BUILD.IsPresent)
|
||||
{
|
||||
# Restore Nuget packages for all builds
|
||||
Write-Host "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Create Nuget Package
|
||||
dotnet pack SabreTools.Matching\SabreTools.Matching.csproj --output $BUILD_FOLDER
|
||||
}
|
||||
Reference in New Issue
Block a user