16 Commits
1.1.1 ... 1.3.1

Author SHA1 Message Date
Matt Nadareski
08ef69e7c1 Bump version 2024-02-29 20:32:12 -05:00
Matt Nadareski
19971ec62c Combine ArrayExtensions into Extensions 2024-02-29 00:31:07 -05:00
Matt Nadareski
4650399bc1 Migrate ArrayExtensions from SabreTools.Core 2024-02-29 00:23:57 -05:00
Matt Nadareski
aad97d03fd Port NaturalSort classes from SabreTools 2024-02-28 19:54:50 -05:00
Matt Nadareski
85bc696ba9 Update copyright date 2024-02-27 19:14:15 -05:00
Matt Nadareski
8fcce5b450 Add nuget package and PR workflows 2024-02-27 19:13:59 -05:00
Matt Nadareski
cd04d2d64c Bump version 2023-11-21 11:23:35 -05:00
Matt Nadareski
3e0267eedb Support .NET Framework 2.0 2023-11-21 00:27:36 -05:00
Matt Nadareski
70aeabd355 Support .NET Framework 3.5 2023-11-20 22:17:21 -05:00
Matt Nadareski
13494ed941 Bump version 2023-11-14 12:35:08 -05:00
Matt Nadareski
bf67574d6e Fix whitespace in project file 2023-11-08 11:00:30 -05:00
Matt Nadareski
9b637e2fe8 Cut off at .NET Framework 4.0 2023-11-08 10:56:18 -05:00
Matt Nadareski
f86d06ce1a Support ancient .NET 2023-11-08 02:18:04 -05:00
Matt Nadareski
3658e923d8 Expand supported RIDs 2023-11-08 01:02:31 -05:00
Matt Nadareski
3588994d30 Enable latest language version 2023-11-07 22:22:04 -05:00
Matt Nadareski
13435d15b3 Add some more nullability to MatchUtil 2023-09-18 15:41:38 -04:00
14 changed files with 492 additions and 257 deletions

43
.github/workflows/build_nupkg.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Nuget Pack
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Pack
run: dotnet pack
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: 'Nuget Package'
path: 'bin/Release/*.nupkg'
- name: Upload to rolling
uses: ncipollo/release-action@v1.14.0
with:
allowUpdates: True
artifacts: 'bin/Release/*.nupkg'
body: 'Last built commit: ${{ github.sha }}'
name: 'Rolling Release'
prerelease: True
replacesArtifacts: True
tag: "rolling"
updateOnlyUnreleased: True

17
.github/workflows/check_pr.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Build PR
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Build
run: dotnet build

View File

@@ -10,8 +10,8 @@ namespace SabreTools.Matching
/// <summary>
/// Content to match
/// </summary>
#if NET48
public byte?[] Needle { get; set; }
#if NETFRAMEWORK || NETCOREAPP
public byte?[]? Needle { get; private set; }
#else
public byte?[]? Needle { get; init; }
#endif
@@ -24,11 +24,7 @@ namespace SabreTools.Matching
/// <summary>
/// Ending index for matching
/// </summary>
#if NET48
public int End { get; private set; }
#else
public int End { get; init; }
#endif
/// <summary>
/// Constructor
@@ -36,11 +32,7 @@ namespace SabreTools.Matching
/// <param name="needle">Byte array representing the search</param>
/// <param name="start">Optional starting index</param>
/// <param name="end">Optional ending index</param>
#if NET48
public ContentMatch(byte?[] needle, int start = -1, int end = -1)
#else
public ContentMatch(byte?[]? needle, int start = -1, int end = -1)
#endif
{
this.Needle = needle;
this.Start = start;
@@ -55,11 +47,7 @@ namespace SabreTools.Matching
/// <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>
#if NET48
public (bool success, int position) Match(byte[] stack, bool reverse = false)
#else
public (bool success, int position) Match(byte[]? stack, bool reverse = false)
#endif
{
// 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)
@@ -136,11 +124,7 @@ namespace SabreTools.Matching
/// <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>
#if NET48
public (bool success, int position) Match(Stream stack, bool reverse = false)
#else
public (bool success, int position) Match(Stream? stack, bool reverse = false)
#endif
{
// 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)

View File

@@ -20,11 +20,7 @@ namespace SabreTools.Matching
/// to the protection name, or `null`, in which case it will cause
/// the protection to be omitted.
/// </remarks>
#if NET48
public Func<string, byte[], List<int>, string> GetArrayVersion { get; private set; }
#else
public Func<string, byte[]?, List<int>, string?>? GetArrayVersion { get; init; }
#endif
public Func<string, byte[]?, List<int>, string?>? GetArrayVersion { get; private set; }
/// <summary>
/// Function to get a content version
@@ -36,11 +32,7 @@ namespace SabreTools.Matching
/// to the protection name, or `null`, in which case it will cause
/// the protection to be omitted.
/// </remarks>
#if NET48
public Func<string, Stream, List<int>, string> GetStreamVersion { get; private set; }
#else
public Func<string, Stream?, List<int>, string?>? GetStreamVersion { get; init; }
#endif
public Func<string, Stream?, List<int>, string?>? GetStreamVersion { get; private set; }
#region Generic Constructors
@@ -60,23 +52,6 @@ namespace SabreTools.Matching
#region Array Constructors
#if NET48
public ContentMatchSet(byte?[] needle, Func<string, byte[], List<int>, string> getArrayVersion, string protectionName)
: this(new List<byte?[]> { needle }, getArrayVersion, protectionName) { }
public ContentMatchSet(List<byte?[]> needles, Func<string, byte[], List<int>, string> getArrayVersion, string protectionName)
: this(needles.Select(n => new ContentMatch(n)).ToList(), getArrayVersion, protectionName) { }
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;
}
#else
public ContentMatchSet(byte?[] needle, Func<string, byte[]?, List<int>, string?>? getArrayVersion, string protectionName)
: this(new List<byte?[]> { needle }, getArrayVersion, protectionName) { }
@@ -92,29 +67,11 @@ namespace SabreTools.Matching
GetArrayVersion = getArrayVersion;
ProtectionName = protectionName;
}
#endif
#endregion
#region Stream Constructors
#if NET48
public ContentMatchSet(byte?[] needle, Func<string, Stream, List<int>, string> getStreamVersion, string protectionName)
: this(new List<byte?[]> { needle }, getStreamVersion, protectionName) { }
public ContentMatchSet(List<byte?[]> needles, Func<string, Stream, List<int>, string> getStreamVersion, string protectionName)
: this(needles.Select(n => new ContentMatch(n)).ToList(), getStreamVersion, protectionName) { }
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;
}
#else
public ContentMatchSet(byte?[] needle, Func<string, Stream?, List<int>, string?>? getStreamVersion, string protectionName)
: this(new List<byte?[]> { needle }, getStreamVersion, protectionName) { }
@@ -130,7 +87,6 @@ namespace SabreTools.Matching
GetStreamVersion = getStreamVersion;
ProtectionName = protectionName;
}
#endif
#endregion
@@ -141,11 +97,7 @@ namespace SabreTools.Matching
/// </summary>
/// <param name="stack">Array to search</param>
/// <returns>Tuple of passing status and matching positions</returns>
#if NET48
public (bool, List<int>) MatchesAll(byte[] stack)
#else
public (bool, List<int>) MatchesAll(byte[]? stack)
#endif
{
// If no content matches are defined, we fail out
if (Matchers == null || !Matchers.Any())
@@ -172,11 +124,7 @@ namespace SabreTools.Matching
/// </summary>
/// <param name="stack">Array to search</param>
/// <returns>Tuple of passing status and first matching position</returns>
#if NET48
public (bool, int) MatchesAny(byte[] stack)
#else
public (bool, int) MatchesAny(byte[]? stack)
#endif
{
// If no content matches are defined, we fail out
if (Matchers == null || !Matchers.Any())
@@ -202,11 +150,7 @@ namespace SabreTools.Matching
/// </summary>
/// <param name="stack">Stream to search</param>
/// <returns>Tuple of passing status and matching positions</returns>
#if NET48
public (bool, List<int>) MatchesAll(Stream stack)
#else
public (bool, List<int>) MatchesAll(Stream? stack)
#endif
{
// If no content matches are defined, we fail out
if (Matchers == null || !Matchers.Any())
@@ -233,11 +177,7 @@ namespace SabreTools.Matching
/// </summary>
/// <param name="stack">Stream to search</param>
/// <returns>Tuple of passing status and first matching position</returns>
#if NET48
public (bool, int) MatchesAny(Stream stack)
#else
public (bool, int) MatchesAny(Stream? stack)
#endif
{
// If no content matches are defined, we fail out
if (Matchers == null || !Matchers.Any())

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
@@ -5,17 +6,21 @@ 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>
#if NET48
public static List<int> FindAllPositions(this byte[] stack, byte?[] needle, int start = 0, int end = -1)
#else
public static List<int> FindAllPositions(this byte[] stack, byte?[]? needle, int start = 0, int end = -1)
#endif
{
// Get the outgoing list
List<int> positions = new List<int>();
List<int> positions = [];
// Initialize the loop variables
bool found = true;
@@ -37,28 +42,16 @@ namespace SabreTools.Matching
/// <summary>
/// Find the first position of one array in another, if possible
/// </summary>
#if NET48
public static bool FirstPosition(this byte[] stack, byte[] needle, out int position, int start = 0, int end = -1)
{
byte?[] nullableNeedle = needle != null ? needle.Select(b => (byte?)b).ToArray() : null;
return stack.FirstPosition(nullableNeedle, out position, start, end);
}
#else
public static bool FirstPosition(this byte[] stack, byte[]? needle, out int position, int start = 0, int end = -1)
{
byte?[]? nullableNeedle = needle != null ? needle.Select(b => (byte?)b).ToArray() : null;
byte?[]? nullableNeedle = needle?.Select(b => (byte?)b).ToArray();
return stack.FirstPosition(nullableNeedle, out position, start, end);
}
#endif
/// <summary>
/// Find the first position of one array in another, if possible
/// </summary>
#if NET48
public static bool FirstPosition(this byte[] stack, byte?[] needle, out int position, int start = 0, int end = -1)
#else
public static bool FirstPosition(this byte[] stack, byte?[]? needle, out int position, int start = 0, int end = -1)
#endif
{
var matcher = new ContentMatch(needle, start, end);
(bool found, int foundPosition) = matcher.Match(stack, false);
@@ -69,11 +62,7 @@ namespace SabreTools.Matching
/// <summary>
/// Find the last position of one array in another, if possible
/// </summary>
#if NET48
public static bool LastPosition(this byte[] stack, byte?[] needle, out int position, int start = 0, int end = -1)
#else
public static bool LastPosition(this byte[] stack, byte?[]? needle, out int position, int start = 0, int end = -1)
#endif
{
var matcher = new ContentMatch(needle, start, end);
(bool found, int foundPosition) = matcher.Match(stack, true);
@@ -84,14 +73,16 @@ namespace SabreTools.Matching
/// <summary>
/// See if a byte array starts with another
/// </summary>
#if NET48
public static bool StartsWith(this byte[] stack, byte[] needle)
#else
public static bool StartsWith(this byte[] stack, byte[]? needle)
#endif
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);
}
@@ -99,14 +90,16 @@ namespace SabreTools.Matching
/// <summary>
/// See if a byte array starts with another
/// </summary>
#if NET48
public static bool StartsWith(this byte[] stack, byte?[] needle)
#else
public static bool StartsWith(this byte[] stack, byte?[]? needle)
#endif
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);
}
@@ -114,14 +107,16 @@ namespace SabreTools.Matching
/// <summary>
/// See if a byte array ends with another
/// </summary>
#if NET48
public static bool EndsWith(this byte[] stack, byte[] needle)
#else
public static bool EndsWith(this byte[] stack, byte[]? needle)
#endif
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);
}
@@ -129,14 +124,16 @@ namespace SabreTools.Matching
/// <summary>
/// See if a byte array ends with another
/// </summary>
#if NET48
public static bool EndsWith(this byte[] stack, byte?[] needle)
#else
public static bool EndsWith(this byte[] stack, byte?[]? needle)
#endif
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);
}

View File

@@ -2,10 +2,6 @@
{
public interface IMatch<T>
{
#if NET48
T Needle { get; set; }
#else
T? Needle { get; init; }
#endif
T? Needle { get; }
}
}

View File

@@ -10,19 +10,11 @@ namespace SabreTools.Matching
/// <summary>
/// Set of all matchers
/// </summary>
#if NET48
public IEnumerable<T> Matchers { get; set; }
#else
public IEnumerable<T>? Matchers { get; set; }
#endif
/// <summary>
/// Name of the protection to show
/// </summary>
#if NET48
public string ProtectionName { get; set; }
#else
public string? ProtectionName { get; set; }
#endif
}
}

View File

@@ -1,4 +1,6 @@
#if NET40_OR_GREATER || NETCOREAPP
using System.Collections.Concurrent;
#endif
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -20,8 +22,8 @@ namespace SabreTools.Matching
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</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 NET48
public static ConcurrentQueue<string> GetAllMatches(string file, byte[] stack, IEnumerable<ContentMatchSet> matchers, bool includeDebug = false)
#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
@@ -37,11 +39,7 @@ namespace SabreTools.Matching
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</param>
/// <param name="includeDebug">True to include positional data, false otherwise</param>
/// <returns>String representing the matched protection, null otherwise</returns>
#if NET48
public static string GetFirstMatch(string file, byte[] stack, IEnumerable<ContentMatchSet> matchers, bool includeDebug = false)
#else
public static string? GetFirstMatch(string file, byte[]? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
#endif
{
var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true);
if (contentMatches == null || !contentMatches.Any())
@@ -59,8 +57,8 @@ namespace SabreTools.Matching
/// <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 NET48
private static ConcurrentQueue<string> FindAllMatches(string file, byte[] stack, IEnumerable<ContentMatchSet> matchers, bool includeDebug, bool stopAfterFirst)
#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
@@ -70,7 +68,11 @@ namespace SabreTools.Matching
return null;
// Initialize the queue of matched protections
#if NET20 || NET35
var matchedProtections = new Queue<string>();
#else
var matchedProtections = new ConcurrentQueue<string>();
#endif
// Loop through and try everything otherwise
foreach (var matcher in matchers)
@@ -81,7 +83,16 @@ namespace SabreTools.Matching
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
// If we there is no version method, just return the protection name
if (matcher.GetArrayVersion == null)
@@ -120,8 +131,8 @@ namespace SabreTools.Matching
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</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 NET48
public static ConcurrentQueue<string> GetAllMatches(string file, Stream stack, IEnumerable<ContentMatchSet> matchers, bool includeDebug = false)
#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
@@ -137,11 +148,7 @@ namespace SabreTools.Matching
/// <param name="matchers">Enumerable of ContentMatchSets to be run on the file</param>
/// <param name="includeDebug">True to include positional data, false otherwise</param>
/// <returns>String representing the matched protection, null otherwise</returns>
#if NET48
public static string GetFirstMatch(string file, Stream stack, IEnumerable<ContentMatchSet> matchers, bool includeDebug = false)
#else
public static string? GetFirstMatch(string file, Stream? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
#endif
{
var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true);
if (contentMatches == null || !contentMatches.Any())
@@ -159,8 +166,8 @@ namespace SabreTools.Matching
/// <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 NET48
private static ConcurrentQueue<string> FindAllMatches(string file, Stream stack, IEnumerable<ContentMatchSet> matchers, bool includeDebug, bool stopAfterFirst)
#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
@@ -170,7 +177,11 @@ namespace SabreTools.Matching
return null;
// Initialize the queue of matched protections
#if NET20 || NET35
var matchedProtections = new Queue<string>();
#else
var matchedProtections = new ConcurrentQueue<string>();
#endif
// Loop through and try everything otherwise
foreach (var matcher in matchers)
@@ -181,7 +192,16 @@ namespace SabreTools.Matching
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
// If we there is no version method, just return the protection name
if (matcher.GetStreamVersion == null)
@@ -219,7 +239,11 @@ namespace SabreTools.Matching
/// <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>List of strings representing the matched protections, null or empty otherwise</returns>
public static ConcurrentQueue<string> GetAllMatches(string file, IEnumerable<PathMatchSet> matchers, bool any = false)
#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);
}
@@ -231,7 +255,11 @@ namespace SabreTools.Matching
/// <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>List of strings representing the matched protections, null or empty otherwise</returns>
public static ConcurrentQueue<string> GetAllMatches(IEnumerable<string> files, IEnumerable<PathMatchSet> matchers, bool any = false)
#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
{
return FindAllMatches(files, matchers, any, false);
}
@@ -243,11 +271,7 @@ namespace SabreTools.Matching
/// <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>
#if NET48
public static string GetFirstMatch(string file, IEnumerable<PathMatchSet> matchers, bool any = false)
#else
public static string? GetFirstMatch(string file, IEnumerable<PathMatchSet> matchers, bool any = false)
#endif
{
var contentMatches = FindAllMatches(new List<string> { file }, matchers, any, true);
if (contentMatches == null || !contentMatches.Any())
@@ -263,11 +287,7 @@ namespace SabreTools.Matching
/// <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>
#if NET48
public static string GetFirstMatch(IEnumerable<string> files, IEnumerable<PathMatchSet> matchers, bool any = false)
#else
public static string? GetFirstMatch(IEnumerable<string> files, IEnumerable<PathMatchSet> matchers, bool any = false)
#endif
{
var contentMatches = FindAllMatches(files, matchers, any, true);
if (contentMatches == null || !contentMatches.Any())
@@ -284,21 +304,29 @@ namespace SabreTools.Matching
/// <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>
private static ConcurrentQueue<string> FindAllMatches(IEnumerable<string> files, IEnumerable<PathMatchSet> matchers, bool any, bool stopAfterFirst)
#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
{
// If there's no mappings, we can't match
if (matchers == null || !matchers.Any())
return new ConcurrentQueue<string>();
return new();
// Initialize the list of matched protections
#if NET20 || NET35
var matchedProtections = new Queue<string>();
#else
var matchedProtections = new ConcurrentQueue<string>();
#endif
// Loop through and try everything otherwise
foreach (var matcher in matchers)
{
// Determine if the matcher passes
bool passes;
var firstMatchedString = string.Empty;
string? firstMatchedString;
if (any)
{
(bool anyPasses, var matchedString) = matcher.MatchesAny(files);

103
NaturalComparer.cs Normal file
View File

@@ -0,0 +1,103 @@
/*
*
* 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;
using System.Linq;
using System.Text.RegularExpressions;
namespace SabreTools.Matching
{
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]+)");
x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)").Where(s => !string.IsNullOrEmpty(s)).ToArray();
table.Add(x, x1);
}
if (!table.TryGetValue(y, out string[]? y1))
{
//y1 = Regex.Split(y.Replace(" ", string.Empty), "([0-9]+)");
y1 = Regex.Split(y.ToLowerInvariant(), "([0-9]+)").Where(s => !string.IsNullOrEmpty(s)).ToArray();
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);
}
}
}

78
NaturalComparerUtil.cs Normal file
View File

@@ -0,0 +1,78 @@
using System.IO;
namespace SabreTools.Matching
{
public static class NaturalComparerUtil
{
public static int CompareNumeric(string s1, string s2)
{
// 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);
// If one is null, then say that's less than
if (s1 == null)
return -1;
if (s2 == null)
return 1;
// 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;
}
}
}

103
NaturalReversedComparer.cs Normal file
View File

@@ -0,0 +1,103 @@
/*
*
* 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;
using System.Linq;
using System.Text.RegularExpressions;
namespace SabreTools.Matching
{
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]+)");
x1 = Regex.Split(x.ToLowerInvariant(), "([0-9]+)").Where(s => !string.IsNullOrEmpty(s)).ToArray();
table.Add(x, x1);
}
if (!table.TryGetValue(y, out string[]? y1))
{
//y1 = Regex.Split(y.Replace(" ", string.Empty), "([0-9]+)");
y1 = Regex.Split(y.ToLowerInvariant(), "([0-9]+)").Where(s => !string.IsNullOrEmpty(s)).ToArray();
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);
}
}
}

View File

@@ -11,8 +11,8 @@ namespace SabreTools.Matching
/// <summary>
/// String to match
/// </summary>
#if NET48
public string Needle { get; set; }
#if NETFRAMEWORK || NETCOREAPP
public string? Needle { get; private set; }
#else
public string? Needle { get; init; }
#endif
@@ -20,20 +20,12 @@ namespace SabreTools.Matching
/// <summary>
/// Match exact casing instead of invariant
/// </summary>
#if NET48
public bool MatchExact { get; private set; }
#else
public bool MatchExact { get; init; }
#endif
/// <summary>
/// Match that values end with the needle and not just contains
/// </summary>
#if NET48
public bool UseEndsWith { get; private set; }
#else
public bool UseEndsWith { get; init; }
#endif
/// <summary>
/// Constructor
@@ -41,11 +33,7 @@ namespace SabreTools.Matching
/// <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>
#if NET48
public PathMatch(string needle, bool matchExact = false, bool useEndsWith = false)
#else
public PathMatch(string? needle, bool matchExact = false, bool useEndsWith = false)
#endif
{
this.Needle = needle;
this.MatchExact = matchExact;
@@ -59,11 +47,7 @@ namespace SabreTools.Matching
/// </summary>
/// <param name="stack">List of strings to search for the given content</param>
/// <returns>Tuple of success and matched item</returns>
#if NET48
public (bool, string) Match(IEnumerable<string> stack)
#else
public (bool, string?) Match(IEnumerable<string>? stack)
#endif
{
// If either array is null or empty, we can't do anything
if (stack == null || !stack.Any() || this.Needle == null || this.Needle.Length == 0)

View File

@@ -18,11 +18,7 @@ namespace SabreTools.Matching
/// 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>
#if NET48
public Func<string, IEnumerable<string>, string> GetVersion { get; private set; }
#else
public Func<string, IEnumerable<string>, string?>? GetVersion { get; init; }
#endif
public Func<string, IEnumerable<string>?, string?>? GetVersion { get; private set; }
#region Constructors
@@ -32,19 +28,11 @@ namespace SabreTools.Matching
public PathMatchSet(List<string> needles, string protectionName)
: this(needles, null, protectionName) { }
#if NET48
public PathMatchSet(string needle, Func<string, IEnumerable<string>, string> getVersion, string protectionName)
public PathMatchSet(string needle, Func<string, IEnumerable<string>?, string?>? getVersion, string protectionName)
: this(new List<string> { needle }, getVersion, protectionName) { }
public PathMatchSet(List<string> needles, Func<string, IEnumerable<string>, string> getVersion, string protectionName)
public PathMatchSet(List<string> needles, Func<string, IEnumerable<string>?, string?>? getVersion, string protectionName)
: this(needles.Select(n => new PathMatch(n)).ToList(), getVersion, protectionName) { }
#else
public PathMatchSet(string needle, Func<string, IEnumerable<string>, string>? getVersion, string protectionName)
: this(new List<string> { needle }, getVersion, protectionName) { }
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) { }
@@ -52,27 +40,15 @@ namespace SabreTools.Matching
public PathMatchSet(List<PathMatch> needles, string protectionName)
: this(needles, null, protectionName) { }
#if NET48
public PathMatchSet(PathMatch needle, Func<string, IEnumerable<string>, string> getVersion, string 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)
public PathMatchSet(List<PathMatch> needles, Func<string, IEnumerable<string>?, string?>? getVersion, string protectionName)
{
Matchers = needles;
GetVersion = getVersion;
ProtectionName = protectionName;
}
#else
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;
GetVersion = getVersion;
ProtectionName = protectionName;
}
#endif
#endregion
@@ -83,23 +59,19 @@ namespace SabreTools.Matching
/// </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)
public (bool, List<string>) MatchesAll(IEnumerable<string>? stack)
{
// If no path matches are defined, we fail out
if (Matchers == null || !Matchers.Any())
return (false, new List<string>());
// Initialize the value list
List<string> values = new List<string>();
List<string> values = [];
// Loop through all path matches and make sure all pass
foreach (var pathMatch in Matchers)
{
#if NET48
(bool match, string value) = pathMatch.Match(stack);
#else
(bool match, string? value) = pathMatch.Match(stack);
#endif
if (!match || value == null)
return (false, new List<string>());
else
@@ -114,11 +86,7 @@ namespace SabreTools.Matching
/// </summary>
/// <param name="stack">List of strings to try to match</param>
/// <returns>Tuple of passing status and first matching value</returns>
#if NET48
public (bool, string) MatchesAny(IEnumerable<string> stack)
#else
public (bool, string?) MatchesAny(IEnumerable<string> stack)
#endif
public (bool, string?) MatchesAny(IEnumerable<string>? stack)
{
// If no path matches are defined, we fail out
if (Matchers == null || !Matchers.Any())
@@ -127,11 +95,7 @@ namespace SabreTools.Matching
// Loop through all path matches and make sure all pass
foreach (var pathMatch in Matchers)
{
#if NET48
(bool match, string value) = pathMatch.Match(stack);
#else
(bool match, string? value) = pathMatch.Match(stack);
#endif
if (match)
return (true, value);
}

View File

@@ -1,31 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net48;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<Version>1.1.1</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<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.1</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Byte array and stream matching library</Description>
<Copyright>Copyright (c) Matt Nadareski 2018-2023</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>
<!-- 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>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath=""/>
</ItemGroup>
</Project>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath=""/>
</ItemGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))">
<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>