mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
Extract out Filtering namespace
This commit is contained in:
77
SabreTools.Filtering/Cleaner.cs
Normal file
77
SabreTools.Filtering/Cleaner.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using SabreTools.Core;
|
||||
|
||||
namespace SabreTools.Filtering
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the cleaning operations that need to be performed on a set of items, usually a DAT
|
||||
/// </summary>
|
||||
public class Cleaner
|
||||
{
|
||||
/// <summary>
|
||||
/// Clean all names to WoD standards
|
||||
/// </summary>
|
||||
public bool Clean { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deduplicate items using the given method
|
||||
/// </summary>
|
||||
public DedupeType DedupeRoms { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set Machine Description from Machine Name
|
||||
/// </summary>
|
||||
public bool DescriptionAsName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of fields in machine and items to exclude from writing
|
||||
/// </summary>
|
||||
public List<Field> ExcludeFields { get; set; } = new List<Field>();
|
||||
|
||||
/// <summary>
|
||||
/// Keep machines that don't contain any items
|
||||
/// </summary>
|
||||
public bool KeepEmptyGames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable "One Rom, One Region (1G1R)" mode
|
||||
/// </summary>
|
||||
public bool OneGamePerRegion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ordered list of regions for "One Rom, One Region (1G1R)" mode
|
||||
/// </summary>
|
||||
public List<string> RegionList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ensure each rom is in their own game
|
||||
/// </summary>
|
||||
public bool OneRomPerGame { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remove all unicode characters
|
||||
/// </summary>
|
||||
public bool RemoveUnicode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Include root directory when determing trim sizes
|
||||
/// </summary>
|
||||
public string Root { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remove scene dates from the beginning of machine names
|
||||
/// </summary>
|
||||
public bool SceneDateStrip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Change all machine names to "!"
|
||||
/// </summary>
|
||||
public bool Single { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Trim total machine and item name to not exceed NTFS limits
|
||||
/// </summary>
|
||||
public bool Trim { get; set; }
|
||||
}
|
||||
}
|
||||
71
SabreTools.Filtering/ExtraIni.cs
Normal file
71
SabreTools.Filtering/ExtraIni.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using SabreTools.Core;
|
||||
using SabreTools.Logging;
|
||||
|
||||
namespace SabreTools.Filtering
|
||||
{
|
||||
public class ExtraIni
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// List of extras to apply
|
||||
/// </summary>
|
||||
public List<ExtraIniItem> Items { get; set; } = new List<ExtraIniItem>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Logging
|
||||
|
||||
/// <summary>
|
||||
/// Logging object
|
||||
/// </summary>
|
||||
private readonly Logger logger;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public ExtraIni()
|
||||
{
|
||||
logger = new Logger(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extras Population
|
||||
|
||||
/// <summary>
|
||||
/// Populate item using field:file inputs
|
||||
/// </summary>
|
||||
/// <param name="inputs">Field and file combinations</param>
|
||||
public void PopulateFromList(List<string> inputs)
|
||||
{
|
||||
foreach (string input in inputs)
|
||||
{
|
||||
ExtraIniItem item = new ExtraIniItem();
|
||||
|
||||
// If we don't even have a possible field and file combination
|
||||
if (!input.Contains(":"))
|
||||
{
|
||||
logger.Warning($"'{input}` is not a valid INI extras string. Valid INI extras strings are of the form 'key:value'. Please refer to README.1ST or the help feature for more details.");
|
||||
return;
|
||||
}
|
||||
|
||||
string inputTrimmed = input.Trim('"', ' ', '\t');
|
||||
string fieldString = inputTrimmed.Split(':')[0].ToLowerInvariant().Trim('"', ' ', '\t');
|
||||
string fileString = inputTrimmed.Substring(fieldString.Length + 1).Trim('"', ' ', '\t');
|
||||
|
||||
item.Field = fieldString.AsField();
|
||||
if (item.PopulateFromFile(fileString))
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
106
SabreTools.Filtering/ExtraIniItem.cs
Normal file
106
SabreTools.Filtering/ExtraIniItem.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using SabreTools.Core;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.Logging;
|
||||
|
||||
namespace SabreTools.Filtering
|
||||
{
|
||||
public class ExtraIniItem
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Field to update with INI information
|
||||
/// </summary>
|
||||
public Field Field { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mappings from value to machine name
|
||||
/// </summary>
|
||||
public Dictionary<string, List<string>> Mappings { get; set; } = new Dictionary<string, List<string>>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extras Population
|
||||
|
||||
/// <summary>
|
||||
/// Populate the dictionary from an INI file
|
||||
/// </summary>
|
||||
/// <param name="ini">Path to INI file to populate from</param>
|
||||
/// <remarks>
|
||||
/// The INI file format that is supported here is not exactly the same
|
||||
/// as a traditional one. This expects a MAME extras format, which usually
|
||||
/// doesn't contain key value pairs and always at least contains one section
|
||||
/// called `ROOT_FOLDER`. If that's the name of a section, then we assume
|
||||
/// the value is boolean. If there's another section name, then that is set
|
||||
/// as the value instead.
|
||||
/// </remarks>
|
||||
public bool PopulateFromFile(string ini)
|
||||
{
|
||||
// Prepare all intenral variables
|
||||
IniReader ir = new IniReader(ini) { ValidateRows = false };
|
||||
bool foundRootFolder = false;
|
||||
|
||||
// If we got a null reader, just return
|
||||
if (ir == null)
|
||||
return false;
|
||||
|
||||
// Otherwise, read the file to the end
|
||||
try
|
||||
{
|
||||
while (!ir.EndOfStream)
|
||||
{
|
||||
// Read in the next line and process
|
||||
ir.ReadNextLine();
|
||||
|
||||
// We don't care about whitespace or comments
|
||||
if (ir.RowType == IniRowType.None || ir.RowType == IniRowType.Comment)
|
||||
continue;
|
||||
|
||||
// If we have a section, just read it in
|
||||
if (ir.RowType == IniRowType.SectionHeader)
|
||||
{
|
||||
// If we've found the start of the extras, set the flag
|
||||
if (string.Equals(ir.Section, "ROOT_FOLDER", StringComparison.OrdinalIgnoreCase))
|
||||
foundRootFolder = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have a value, then we start populating the dictionary
|
||||
else if (foundRootFolder)
|
||||
{
|
||||
// Get the key and value
|
||||
string key = ir.Section;
|
||||
string value = ir.CurrentLine.Trim();
|
||||
|
||||
// If the section is "ROOT_FOLDER", then we use the value "true" instead.
|
||||
// This is done because some INI files use the name of the file as the
|
||||
// category to be assigned to the items included.
|
||||
if (key == "ROOT_FOLDER")
|
||||
key = "true";
|
||||
|
||||
// Ensure the key exists
|
||||
if (!Mappings.ContainsKey(key))
|
||||
Mappings[key] = new List<string>();
|
||||
|
||||
// Add the new mapping
|
||||
Mappings[key].Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerImpl.Warning(ex, $"Exception found while parsing '{ini}'");
|
||||
return false;
|
||||
}
|
||||
|
||||
ir.Dispose();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1304
SabreTools.Filtering/Filter.cs
Normal file
1304
SabreTools.Filtering/Filter.cs
Normal file
File diff suppressed because it is too large
Load Diff
179
SabreTools.Filtering/FilterItem.cs
Normal file
179
SabreTools.Filtering/FilterItem.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SabreTools.Filtering
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single filter within the overall filter
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Generic type representing the filtered object</typeparam>
|
||||
public class FilterItem<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Single positive value for this filter
|
||||
/// </summary>
|
||||
public T Positive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of positive values for this filter
|
||||
/// </summary>
|
||||
public List<T> PositiveSet { get; set; } = new List<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Single negative value for this filter
|
||||
/// </summary>
|
||||
public T Negative { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of negative values for this filter
|
||||
/// </summary>
|
||||
public List<T> NegativeSet { get; set; } = new List<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Single neutral value for this filter
|
||||
/// </summary>
|
||||
public T Neutral { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of neutral values for this filter
|
||||
/// </summary>
|
||||
public List<T> NeutralSet { get; set; } = new List<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Check if a value matches the positive filter
|
||||
/// </summary>
|
||||
/// <param name="def">Default value to check filter value</param>
|
||||
/// <param name="value">Value to check</param>
|
||||
/// <returns>True if the value was found in the positive filter, null on default value, false otherwise</returns>
|
||||
public bool? MatchesPositive(T def, T value)
|
||||
{
|
||||
return Matches(this.Positive, def, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a value matches the negative filter
|
||||
/// </summary>
|
||||
/// <param name="def">Default value to check filter value</param>
|
||||
/// <param name="value">Value to check</param>
|
||||
/// <returns>True if the value was found in the negative filter, null on default value, false otherwise</returns>
|
||||
public bool? MatchesNegative(T def, T value)
|
||||
{
|
||||
return Matches(this.Negative, def, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a value matches the neutral filter
|
||||
/// </summary>
|
||||
/// <param name="def">Default value to check filter value</param>
|
||||
/// <param name="value">Value to check</param>
|
||||
/// <returns>True if the value was found in the neutral filter, null on default value, false otherwise</returns>
|
||||
public bool? MatchesNeutral(T def, T value)
|
||||
{
|
||||
return Matches(this.Neutral, def, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given value matches any of the positive filters
|
||||
/// </summary>
|
||||
/// <param name="value">Value to check</param>
|
||||
/// <returns>True if the value was found in a positive filter, null on an empty set, false otherwise</returns>
|
||||
public bool? MatchesPositiveSet(T value)
|
||||
{
|
||||
return MatchesSet(this.PositiveSet, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given value matches any of the negative filters
|
||||
/// </summary>
|
||||
/// <param name="value">Value to check</param>
|
||||
/// <returns>True if the value was found in a negative filter, null on an empty set, false otherwise</returns>
|
||||
public bool? MatchesNegativeSet(T value)
|
||||
{
|
||||
return MatchesSet(this.NegativeSet, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given value matches any of the neutral filters
|
||||
/// </summary>
|
||||
/// <param name="value">Value to check</param>
|
||||
/// <returns>True if the value was found in a neutral filter, null on an empty set, false otherwise</returns>
|
||||
public bool? MatchesNeutralSet(T value)
|
||||
{
|
||||
return MatchesSet(this.NeutralSet, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a value matches the supplied filter
|
||||
/// </summary>
|
||||
/// <param name="single">Value to check against</param>
|
||||
/// <param name="def">Default value to check filter value</param>
|
||||
/// <param name="value">Value to check</param>
|
||||
/// <returns>True if the value was found in the supplied filter, null on default value, false otherwise</returns>
|
||||
private bool? Matches(T single, T def, T value)
|
||||
{
|
||||
// If the filter is default, we ignore
|
||||
if (single.Equals(def))
|
||||
return null;
|
||||
|
||||
// If we have a flag
|
||||
if (typeof(T).IsEnum && (single as Enum).HasFlag(value as Enum))
|
||||
return true;
|
||||
|
||||
return single.Equals(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a value matches the supplied filter
|
||||
/// </summary>
|
||||
/// <param name="set">Set to check against</param>
|
||||
/// <param name="value">Value to check</param>
|
||||
/// <returns>True if the value was found in the supplied filter, null on an empty set, false otherwise</returns>
|
||||
private bool? MatchesSet(List<T> set, T value)
|
||||
{
|
||||
if (set.Count == 0)
|
||||
return null;
|
||||
|
||||
if (this.FindValueInList(set, value))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic code to check if a specific value is in the list given
|
||||
/// </summary>
|
||||
/// <param name="haystack">List to search for the value in</param>
|
||||
/// <param name="needle">Value to search the list for</param>
|
||||
/// <returns>True if the value could be found, false otherwise</returns>
|
||||
private bool FindValueInList(List<T> haystack, T needle)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (T straw in haystack)
|
||||
{
|
||||
if (straw is string)
|
||||
{
|
||||
string needleString = needle as string;
|
||||
string strawString = straw as string;
|
||||
if (!string.IsNullOrWhiteSpace(strawString) && needleString != null)
|
||||
{
|
||||
string regexStraw = strawString;
|
||||
|
||||
// If the straw has no special characters at all (excluding whitespace), treat it as an exact match
|
||||
if (regexStraw == Regex.Escape(regexStraw).Replace("\\ ", " "))
|
||||
regexStraw = $"^{regexStraw}$";
|
||||
|
||||
// Check if a match is found with the regex
|
||||
found |= Regex.IsMatch(needleString, regexStraw, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
found |= (needle.Equals(straw));
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
SabreTools.Filtering/SabreTools.Filtering.csproj
Normal file
20
SabreTools.Filtering/SabreTools.Filtering.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;netcoreapp3.1;net5.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win10-x64;win7-x86</RuntimeIdentifiers>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net48'">
|
||||
<DefineConstants>NET_FRAMEWORK</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.Core\SabreTools.Core.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.IO\SabreTools.IO.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Logging\SabreTools.Logging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user