mirror of
https://github.com/SabreTools/SabreTools.Matching.git
synced 2026-02-05 13:49:48 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85bc696ba9 | ||
|
|
8fcce5b450 | ||
|
|
cd04d2d64c | ||
|
|
3e0267eedb | ||
|
|
70aeabd355 | ||
|
|
13494ed941 | ||
|
|
bf67574d6e | ||
|
|
9b637e2fe8 | ||
|
|
f86d06ce1a | ||
|
|
3658e923d8 | ||
|
|
3588994d30 | ||
|
|
13435d15b3 | ||
|
|
fb27246fef |
43
.github/workflows/build_nupkg.yml
vendored
Normal file
43
.github/workflows/build_nupkg.yml
vendored
Normal 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
17
.github/workflows/check_pr.yml
vendored
Normal 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
|
||||
@@ -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,7 +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>
|
||||
public (bool success, int position) Match(byte[] stack, bool reverse = false)
|
||||
public (bool success, int position) 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)
|
||||
@@ -132,7 +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>
|
||||
public (bool success, int position) Match(Stream stack, bool reverse = false)
|
||||
public (bool success, int position) 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)
|
||||
|
||||
@@ -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,77 +52,41 @@ namespace SabreTools.Matching
|
||||
|
||||
#region Array Constructors
|
||||
|
||||
#if NET48
|
||||
public ContentMatchSet(byte?[] needle, Func<string, byte[], List<int>, string> getArrayVersion, string protectionName)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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) { }
|
||||
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Constructors
|
||||
|
||||
#if NET48
|
||||
public ContentMatchSet(byte?[] needle, Func<string, Stream, List<int>, string> getStreamVersion, string protectionName)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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) { }
|
||||
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -141,7 +97,7 @@ namespace SabreTools.Matching
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <returns>Tuple of passing status and matching positions</returns>
|
||||
public (bool, List<int>) MatchesAll(byte[] stack)
|
||||
public (bool, List<int>) MatchesAll(byte[]? stack)
|
||||
{
|
||||
// If no content matches are defined, we fail out
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
@@ -168,7 +124,7 @@ namespace SabreTools.Matching
|
||||
/// </summary>
|
||||
/// <param name="stack">Array to search</param>
|
||||
/// <returns>Tuple of passing status and first matching position</returns>
|
||||
public (bool, int) MatchesAny(byte[] stack)
|
||||
public (bool, int) MatchesAny(byte[]? stack)
|
||||
{
|
||||
// If no content matches are defined, we fail out
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
@@ -194,14 +150,14 @@ namespace SabreTools.Matching
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <returns>Tuple of passing status and matching positions</returns>
|
||||
public (bool, List<int>) MatchesAll(Stream stack)
|
||||
public (bool, List<int>) MatchesAll(Stream? stack)
|
||||
{
|
||||
// If no content matches are defined, we fail out
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
return (false, new List<int>());
|
||||
|
||||
// Initialize the position list
|
||||
List<int> positions = new List<int>();
|
||||
var positions = new List<int>();
|
||||
|
||||
// Loop through all content matches and make sure all pass
|
||||
foreach (var contentMatch in Matchers)
|
||||
@@ -221,7 +177,7 @@ namespace SabreTools.Matching
|
||||
/// </summary>
|
||||
/// <param name="stack">Stream to search</param>
|
||||
/// <returns>Tuple of passing status and first matching position</returns>
|
||||
public (bool, int) MatchesAny(Stream stack)
|
||||
public (bool, int) MatchesAny(Stream? stack)
|
||||
{
|
||||
// If no content matches are defined, we fail out
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
|
||||
@@ -8,10 +8,10 @@ namespace SabreTools.Matching
|
||||
/// <summary>
|
||||
/// Find all positions of one array in another, if possible, if possible
|
||||
/// </summary>
|
||||
public static List<int> FindAllPositions(this byte[] stack, byte?[] needle, int start = 0, int end = -1)
|
||||
public static List<int> FindAllPositions(this byte[] stack, byte?[]? needle, int start = 0, int end = -1)
|
||||
{
|
||||
// Get the outgoing list
|
||||
List<int> positions = new List<int>();
|
||||
List<int> positions = [];
|
||||
|
||||
// Initialize the loop variables
|
||||
bool found = true;
|
||||
@@ -33,28 +33,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);
|
||||
@@ -65,11 +53,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);
|
||||
@@ -80,32 +64,44 @@ 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)
|
||||
{
|
||||
if (needle == null)
|
||||
return false;
|
||||
|
||||
return stack.FirstPosition(needle, out int _, start: 0, end: 1);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (needle == null)
|
||||
return false;
|
||||
|
||||
return stack.FirstPosition(needle, out int _, start: 0, end: 1);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (needle == null)
|
||||
return false;
|
||||
|
||||
return stack.FirstPosition(needle, out int _, start: stack.Length - needle.Length);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (needle == null)
|
||||
return false;
|
||||
|
||||
return stack.FirstPosition(needle, out int _, start: stack.Length - needle.Length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
{
|
||||
public interface IMatch<T>
|
||||
{
|
||||
#if NET48
|
||||
T Needle { get; set; }
|
||||
#else
|
||||
T? Needle { get; init; }
|
||||
#endif
|
||||
T? Needle { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
112
MatchUtil.cs
112
MatchUtil.cs
@@ -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,10 +22,10 @@ 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)
|
||||
public static ConcurrentQueue<string>? GetAllMatches(string file, byte[]? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
#endif
|
||||
{
|
||||
return FindAllMatches(file, stack, matchers, includeDebug, false);
|
||||
@@ -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
|
||||
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())
|
||||
@@ -59,10 +57,10 @@ 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)
|
||||
private static ConcurrentQueue<string>? FindAllMatches(string file, byte[]? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug, bool stopAfterFirst)
|
||||
#endif
|
||||
{
|
||||
// If there's no mappings, we can't match
|
||||
@@ -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)
|
||||
@@ -93,7 +104,7 @@ namespace SabreTools.Matching
|
||||
else
|
||||
{
|
||||
// A null version returned means the check didn't pass at the version step
|
||||
string version = matcher.GetArrayVersion(file, stack, positions);
|
||||
var version = matcher.GetArrayVersion(file, stack, positions);
|
||||
if (version == null)
|
||||
continue;
|
||||
|
||||
@@ -120,10 +131,10 @@ 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)
|
||||
public static ConcurrentQueue<string>? GetAllMatches(string file, Stream? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug = false)
|
||||
#endif
|
||||
{
|
||||
return FindAllMatches(file, stack, matchers, includeDebug, false);
|
||||
@@ -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
|
||||
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())
|
||||
@@ -159,10 +166,10 @@ 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)
|
||||
private static ConcurrentQueue<string>? FindAllMatches(string file, Stream? stack, IEnumerable<ContentMatchSet>? matchers, bool includeDebug, bool stopAfterFirst)
|
||||
#endif
|
||||
{
|
||||
// If there's no mappings, we can't match
|
||||
@@ -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)
|
||||
@@ -193,7 +213,7 @@ namespace SabreTools.Matching
|
||||
else
|
||||
{
|
||||
// A null version returned means the check didn't pass at the version step
|
||||
string version = matcher.GetStreamVersion(file, stack, positions);
|
||||
var version = matcher.GetStreamVersion(file, stack, positions);
|
||||
if (version == null)
|
||||
continue;
|
||||
|
||||
@@ -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,32 +304,32 @@ 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;
|
||||
#if NET48
|
||||
string firstMatchedString;
|
||||
#else
|
||||
string? firstMatchedString;
|
||||
#endif
|
||||
if (any)
|
||||
{
|
||||
#if NET48
|
||||
(bool anyPasses, string matchedString) = matcher.MatchesAny(files);
|
||||
#else
|
||||
(bool anyPasses, string? matchedString) = matcher.MatchesAny(files);
|
||||
#endif
|
||||
(bool anyPasses, var matchedString) = matcher.MatchesAny(files);
|
||||
passes = anyPasses;
|
||||
firstMatchedString = matchedString;
|
||||
}
|
||||
@@ -334,7 +354,7 @@ namespace SabreTools.Matching
|
||||
else
|
||||
{
|
||||
// A null version returned means the check didn't pass at the version step
|
||||
string version = matcher.GetVersion(firstMatchedString, files);
|
||||
var version = matcher.GetVersion(firstMatchedString, files);
|
||||
if (version == null)
|
||||
continue;
|
||||
|
||||
|
||||
20
PathMatch.cs
20
PathMatch.cs
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.0</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.0</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>
|
||||
|
||||
Reference in New Issue
Block a user