Clean up filter code; detuple

This commit is contained in:
Matt Nadareski
2024-10-24 01:33:41 -04:00
parent c6109fdf97
commit 3b30ed2ebb
10 changed files with 134 additions and 142 deletions

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using SabreTools.Core.Tools; using SabreTools.Core.Tools;
using SabreTools.Models.Metadata; using SabreTools.Models.Metadata;
@@ -8,36 +7,6 @@ namespace SabreTools.Core.Filter
{ {
public static class FieldManipulator public static class FieldManipulator
{ {
/// <summary>
/// Regex pattern to match scene dates
/// </summary>
private const string _sceneDateRegex = @"([0-9]{2}\.[0-9]{2}\.[0-9]{2}-)(.*?-.*?)";
/// <summary>
/// Replace the machine name with the description
/// </summary>
/// <returns>Original machine name on success, null on error</returns>
public static string? DescriptionToName(Machine? machine)
{
// If the machine is missing, we can't do anything
if (machine == null)
return null;
// Get both the current name and description
string? name = machine.ReadString(Header.NameKey);
string? description = machine.ReadString(Header.DescriptionKey);
// Sanitize the description string
description = description?
.Replace('/', '_')
.Replace("\"", "''")
.Replace(":", " -");
// Replace the name with the description
machine[Header.NameKey] = description;
return name;
}
/// <summary> /// <summary>
/// Remove a field from a given DictionaryBase /// Remove a field from a given DictionaryBase
/// </summary> /// </summary>
@@ -96,27 +65,5 @@ namespace SabreTools.Core.Filter
dictionaryBase[fieldName!] = value; dictionaryBase[fieldName!] = value;
return true; return true;
} }
/// <summary>
/// Strip the dates from the beginning of scene-style machine name and description
/// </summary>
public static bool StripSceneDates(Machine? machine)
{
// If the machine is missing, we can't do anything
if (machine == null)
return false;
// Strip dates from the name field
string? name = machine.ReadString(Header.NameKey);
if (name != null && Regex.IsMatch(name, _sceneDateRegex))
machine[Header.NameKey] = Regex.Replace(name, _sceneDateRegex, @"$2");
// Strip dates from the description field
string? description = machine.ReadString(Header.DescriptionKey);
if (description != null && Regex.IsMatch(description, _sceneDateRegex))
machine[Header.DescriptionKey] = Regex.Replace(description, _sceneDateRegex, @"$2");
return true;
}
} }
} }

View File

@@ -14,26 +14,24 @@ namespace SabreTools.Core.Filter
/// <summary> /// <summary>
/// Key name for the filter /// Key name for the filter
/// </summary> /// </summary>
public string[] Key { get; } public readonly string[] Key;
/// <summary> /// <summary>
/// Value to match in the filter /// Value to match in the filter
/// </summary> /// </summary>
public string? Value { get; } public readonly string? Value;
/// <summary> /// <summary>
/// Operation on how to match the filter /// Operation on how to match the filter
/// </summary> /// </summary>
public Operation Operation { get; } public readonly Operation Operation;
public FilterObject(string filterString) public FilterObject(string filterString)
{ {
(string? keyItem, Operation operation, string? value) = SplitFilterString(filterString); if (!SplitFilterString(filterString, out var keyItem, out Operation operation, out var value))
if (keyItem == null)
throw new ArgumentOutOfRangeException(nameof(filterString)); throw new ArgumentOutOfRangeException(nameof(filterString));
(string? itemName, string? fieldName) = FilterParser.ParseFilterId(keyItem); if (!FilterParser.ParseFilterId(keyItem, out var itemName, out var fieldName))
if (itemName == null || fieldName == null)
throw new ArgumentOutOfRangeException(nameof(filterString)); throw new ArgumentOutOfRangeException(nameof(filterString));
Key = [itemName, fieldName]; Key = [itemName, fieldName];
@@ -43,8 +41,7 @@ namespace SabreTools.Core.Filter
public FilterObject(string itemField, string? value, string? operation) public FilterObject(string itemField, string? value, string? operation)
{ {
(string? itemName, string? fieldName) = FilterParser.ParseFilterId(itemField); if (!FilterParser.ParseFilterId(itemField, out var itemName, out var fieldName))
if (itemName == null || fieldName == null)
throw new ArgumentOutOfRangeException(nameof(value)); throw new ArgumentOutOfRangeException(nameof(value));
Key = [itemName, fieldName]; Key = [itemName, fieldName];
@@ -54,11 +51,10 @@ namespace SabreTools.Core.Filter
public FilterObject(string itemField, string? value, Operation operation) public FilterObject(string itemField, string? value, Operation operation)
{ {
(string? itemName, string? fieldName) = FilterParser.ParseFilterId(itemField); if (!FilterParser.ParseFilterId(itemField, out var itemName, out var fieldName))
if (itemName == null || fieldName == null)
throw new ArgumentOutOfRangeException(nameof(value)); throw new ArgumentOutOfRangeException(nameof(value));
Key = [itemName, fieldName]; Key = [itemName!, fieldName!];
Value = value; Value = value;
Operation = operation; Operation = operation;
} }
@@ -396,10 +392,13 @@ namespace SabreTools.Core.Filter
/// <summary> /// <summary>
/// Derive a key, operation, and value from the input string, if possible /// Derive a key, operation, and value from the input string, if possible
/// </summary> /// </summary>
private static (string?, Operation, string?) SplitFilterString(string? filterString) private static bool SplitFilterString(string? filterString, out string? key, out Operation operation, out string? value)
{ {
// Set default values
key = null; operation = Operation.NONE; value = null;
if (filterString == null) if (filterString == null)
return (null, Operation.NONE, null); return false;
// Trim quotations, if necessary // Trim quotations, if necessary
if (filterString.StartsWith("\"")) if (filterString.StartsWith("\""))
@@ -408,13 +407,13 @@ namespace SabreTools.Core.Filter
// Split the string using regex // Split the string using regex
var match = Regex.Match(filterString, @"^(?<itemField>[a-zA-Z.]+)(?<operation>[=!:><]{1,2})(?<value>.*)$"); var match = Regex.Match(filterString, @"^(?<itemField>[a-zA-Z.]+)(?<operation>[=!:><]{1,2})(?<value>.*)$");
if (!match.Success) if (!match.Success)
return (null, Operation.NONE, null); return false;
string itemField = match.Groups["itemField"].Value; key = match.Groups["itemField"].Value;
Operation operation = GetOperation(match.Groups["operation"].Value); operation = GetOperation(match.Groups["operation"].Value);
string value = match.Groups["value"].Value; value = match.Groups["value"].Value;
return (itemField, operation, value); return true;
} }
#endregion #endregion

View File

@@ -10,125 +10,166 @@ namespace SabreTools.Core.Filter
/// <summary> /// <summary>
/// Parse a filter ID string into the item name and field name, if possible /// Parse a filter ID string into the item name and field name, if possible
/// </summary> /// </summary>
public static (string?, string?) ParseFilterId(string itemFieldString) public static bool ParseFilterId(string? itemFieldString, out string itemName, out string fieldName)
{ {
// Set default values
itemName = string.Empty; fieldName = string.Empty;
// If we don't have a filter ID, we can't do anything // If we don't have a filter ID, we can't do anything
if (string.IsNullOrEmpty(itemFieldString)) if (itemFieldString == null)
return (null, null); return false;
// If we only have one part, we can't do anything // If we only have one part, we can't do anything
string[] splitFilter = itemFieldString.Split('.'); string[] splitFilter = itemFieldString.Split('.');
if (splitFilter.Length != 2) if (splitFilter.Length != 2)
return (null, null); return false;
return ParseFilterId(splitFilter[0], splitFilter[1]); // Set and sanitize the filter ID
itemName = splitFilter[0];
fieldName = splitFilter[1];
return ParseFilterId(ref itemName, ref fieldName);
} }
/// <summary> /// <summary>
/// Parse a filter ID string into the item name and field name, if possible /// Parse a filter ID string into the item name and field name, if possible
/// </summary> /// </summary>
public static (string?, string?) ParseFilterId(string itemName, string? fieldName) public static bool ParseFilterId(ref string itemName, ref string fieldName)
{ {
// If we don't have a filter ID, we can't do anything // If we don't have a filter ID, we can't do anything
if (string.IsNullOrEmpty(itemName) || string.IsNullOrEmpty(fieldName)) if (string.IsNullOrEmpty(itemName) || string.IsNullOrEmpty(fieldName))
return (null, null); return false;
// Return santized values based on the split ID // Return santized values based on the split ID
return itemName.ToLowerInvariant() switch return itemName.ToLowerInvariant() switch
{ {
// Header // Header
"header" => ParseHeaderFilterId(fieldName!), "header" => ParseHeaderFilterId(ref itemName, ref fieldName),
// Machine // Machine
"game" => ParseMachineFilterId(fieldName!), "game" => ParseMachineFilterId(ref itemName, ref fieldName),
"machine" => ParseMachineFilterId(fieldName!), "machine" => ParseMachineFilterId(ref itemName, ref fieldName),
"resource" => ParseMachineFilterId(fieldName!), "resource" => ParseMachineFilterId(ref itemName, ref fieldName),
"set" => ParseMachineFilterId(fieldName!), "set" => ParseMachineFilterId(ref itemName, ref fieldName),
// DatItem // DatItem
"datitem" => ParseDatItemFilterId(fieldName!), "datitem" => ParseDatItemFilterId(ref itemName, ref fieldName),
"item" => ParseDatItemFilterId(fieldName!), "item" => ParseDatItemFilterId(ref itemName, ref fieldName),
_ => ParseDatItemFilterId(itemName, fieldName!), _ => ParseDatItemFilterId(ref itemName, ref fieldName),
}; };
} }
/// <summary> /// <summary>
/// Parse and validate header fields /// Parse and validate header fields
/// </summary> /// </summary>
private static (string?, string?) ParseHeaderFilterId(string fieldName) private static bool ParseHeaderFilterId(ref string itemName, ref string fieldName)
{ {
// Get the set of constants // Get the set of constants
var constants = TypeHelper.GetConstants(typeof(Header)); var constants = TypeHelper.GetConstants(typeof(Header));
if (constants == null) if (constants == null)
return (null, null); return false;
// Get if there's a match to the constant // Get if there's a match to the constant
string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase)); string localFieldName = fieldName;
string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase));
if (constantMatch == null) if (constantMatch == null)
return (null, null); return false;
// Return the sanitized ID // Return the sanitized ID
return (MetadataFile.HeaderKey, constantMatch); itemName = MetadataFile.HeaderKey;
fieldName = constantMatch;
return true;
} }
/// <summary> /// <summary>
/// Parse and validate machine/game fields /// Parse and validate machine/game fields
/// </summary> /// </summary>
private static (string?, string?) ParseMachineFilterId(string fieldName) private static bool ParseMachineFilterId(ref string itemName, ref string fieldName)
{ {
// Get the set of constants // Get the set of constants
var constants = TypeHelper.GetConstants(typeof(Machine)); var constants = TypeHelper.GetConstants(typeof(Machine));
if (constants == null) if (constants == null)
return (null, null); return false;
// Get if there's a match to the constant // Get if there's a match to the constant
string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase)); string localFieldName = fieldName;
string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase));
if (constantMatch == null) if (constantMatch == null)
return (null, null); return false;
// Return the sanitized ID // Return the sanitized ID
return (MetadataFile.MachineKey, constantMatch); itemName = MetadataFile.MachineKey;
fieldName = constantMatch;
return true;
} }
/// <summary> /// <summary>
/// Parse and validate item fields /// Parse and validate item fields
/// </summary> /// </summary>
private static (string?, string?) ParseDatItemFilterId(string fieldName) private static bool ParseDatItemFilterId(ref string itemName, ref string fieldName)
{ {
// Get all item types // Special case if the item name is reserved
var itemTypes = TypeHelper.GetDatItemTypeNames(); if (string.Equals(itemName, "datitem", StringComparison.OrdinalIgnoreCase)
|| string.Equals(itemName, "item", StringComparison.OrdinalIgnoreCase))
{
// Get all item types
var itemTypes = TypeHelper.GetDatItemTypeNames();
// If we get any matches // If we get any matches
string? match = itemTypes.FirstOrDefault(t => t != null && ParseDatItemFilterId(t, fieldName) != (null, null)); string localFieldName = fieldName;
if (match != null) string? matchedType = itemTypes.FirstOrDefault(t => DatItemContainsField(t, localFieldName));
return ("item", ParseDatItemFilterId(match, fieldName).Item2); if (matchedType != null)
{
// Check for a matching field
string? matchedField = GetMatchingField(matchedType, fieldName);
if (matchedField == null)
return false;
itemName = "item";
fieldName = matchedField;
return true;
}
}
else
{
// Check for a matching field
string? matchedField = GetMatchingField(itemName, fieldName);
if (matchedField == null)
return false;
itemName = itemName.ToLowerInvariant();
fieldName = matchedField;
return true;
}
// Nothing was found // Nothing was found
return (null, null); return false;
} }
/// <summary> /// <summary>
/// Parse and validate item fields /// Determine if an item type contains a field
/// </summary> /// </summary>
private static (string?, string?) ParseDatItemFilterId(string itemName, string fieldName) private static bool DatItemContainsField(string itemName, string fieldName)
=> GetMatchingField(itemName, fieldName) != null;
/// <summary>
/// Determine if an item type contains a field
/// </summary>
private static string? GetMatchingField(string itemName, string fieldName)
{ {
// Get the correct item type // Get the correct item type
var itemType = TypeHelper.GetDatItemType(itemName.ToLowerInvariant()); var itemType = TypeHelper.GetDatItemType(itemName.ToLowerInvariant());
if (itemType == null) if (itemType == null)
return (null, null); return null;
// Get the set of constants // Get the set of constants
var constants = TypeHelper.GetConstants(itemType); var constants = TypeHelper.GetConstants(itemType);
if (constants == null) if (constants == null)
return (null, null); return null;
// Get if there's a match to the constant // Get if there's a match to the constant
string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase)); string localFieldName = fieldName;
if (constantMatch == null) string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase));
return (null, null); return constantMatch;
// Return the sanitized ID
return (itemName.ToLowerInvariant(), constantMatch);
} }
} }
} }

View File

@@ -20,7 +20,7 @@ namespace SabreTools.Core.Filter
if (filters == null) if (filters == null)
throw new ArgumentNullException(nameof(filters)); throw new ArgumentNullException(nameof(filters));
this.Filters = filters; Filters = filters;
} }
public FilterRunner(string[]? filterStrings) public FilterRunner(string[]? filterStrings)
@@ -34,7 +34,7 @@ namespace SabreTools.Core.Filter
filters.Add(new FilterObject(filterString)); filters.Add(new FilterObject(filterString));
} }
this.Filters = [.. filters]; Filters = [.. filters];
} }
/// <summary> /// <summary>
@@ -51,7 +51,7 @@ namespace SabreTools.Core.Filter
}; };
// Loop through and run each filter in order // Loop through and run each filter in order
foreach (var filter in this.Filters) foreach (var filter in Filters)
{ {
// If the filter isn't for this object type, skip // If the filter isn't for this object type, skip
if (filter.Key[0] != itemName) if (filter.Key[0] != itemName)

View File

@@ -42,7 +42,7 @@ namespace SabreTools.Core.Tools
/// <summary> /// <summary>
/// Attempt to get all DatItem types /// Attempt to get all DatItem types
/// </summary> /// </summary>
public static string?[] GetDatItemTypeNames() public static string[] GetDatItemTypeNames()
{ {
List<string> typeNames = []; List<string> typeNames = [];

View File

@@ -114,8 +114,7 @@ namespace SabreTools.DatFiles
return false; return false;
// Get the parser pair out of it, if possible // Get the parser pair out of it, if possible
(string? type, string? key) = FilterParser.ParseFilterId(field); if (!FilterParser.ParseFilterId(field, out string type, out string key))
if (type == null || key == null)
return false; return false;
switch (type) switch (type)

View File

@@ -68,7 +68,8 @@ namespace SabreTools.Filtering
string fieldString = inputTrimmed.Split(':')[0].ToLowerInvariant().Trim('"', ' ', '\t'); string fieldString = inputTrimmed.Split(':')[0].ToLowerInvariant().Trim('"', ' ', '\t');
string fileString = inputTrimmed.Substring(fieldString.Length + 1).Trim('"', ' ', '\t'); string fileString = inputTrimmed.Substring(fieldString.Length + 1).Trim('"', ' ', '\t');
item.FieldName = FilterParser.ParseFilterId(fieldString); FilterParser.ParseFilterId(fieldString, out string itemName, out string fieldName);
item.FieldName = (itemName, fieldName);
if (item.PopulateFromFile(fileString)) if (item.PopulateFromFile(fileString))
Items.Add(item); Items.Add(item);
} }

View File

@@ -92,8 +92,7 @@ namespace SabreTools.Filtering
return false; return false;
// Get the parser pair out of it, if possible // Get the parser pair out of it, if possible
(string? type, string? key) = FilterParser.ParseFilterId(field); if (!FilterParser.ParseFilterId(field, out string type, out string key))
if (type == null || key == null)
return false; return false;
switch (type) switch (type)

View File

@@ -2023,9 +2023,13 @@ Some special strings that can be used:
List<string> updateFields = []; List<string> updateFields = [];
foreach (string fieldName in GetList(features, UpdateFieldListValue)) foreach (string fieldName in GetList(features, UpdateFieldListValue))
{ {
(string? itemType, string? key) = FilterParser.ParseFilterId(fieldName); // Ensure the field is valid
if (itemType == Models.Metadata.MetadataFile.MachineKey && key != null) if (!FilterParser.ParseFilterId(fieldName, out string itemType, out string key))
updateFields.Add(key); continue;
if (itemType != Models.Metadata.MetadataFile.MachineKey)
continue;
updateFields.Add(key);
} }
return updateFields; return updateFields;
@@ -2039,14 +2043,16 @@ Some special strings that can be used:
Dictionary<string, List<string>> updateFields = []; Dictionary<string, List<string>> updateFields = [];
foreach (string fieldName in GetList(features, UpdateFieldListValue)) foreach (string fieldName in GetList(features, UpdateFieldListValue))
{ {
(string? itemType, string? key) = FilterParser.ParseFilterId(fieldName); // Ensure the field is valid
if (itemType != null && itemType != Models.Metadata.MetadataFile.HeaderKey && itemType != Models.Metadata.MetadataFile.MachineKey && key != null) if (!FilterParser.ParseFilterId(fieldName, out string itemType, out string key))
{ continue;
if (!updateFields.ContainsKey(itemType)) if (itemType == Models.Metadata.MetadataFile.HeaderKey || itemType == Models.Metadata.MetadataFile.MachineKey)
updateFields[itemType] = []; continue;
updateFields[itemType].Add(key); if (!updateFields.ContainsKey(itemType))
} updateFields[itemType] = [];
updateFields[itemType].Add(key);
} }
return updateFields; return updateFields;

View File

@@ -315,11 +315,11 @@ Reset the internal state: reset();";
} }
// Read in the individual arguments // Read in the individual arguments
(string? type, string? key) = FilterParser.ParseFilterId(Arguments[0]); bool parsed = FilterParser.ParseFilterId(Arguments[0], out _, out _);
string extraFile = Arguments[1]; string extraFile = Arguments[1];
// If we had an invalid input, log and continue // If we had an invalid input, log and continue
if (type == null && key == null) if (!parsed)
{ {
string message = $"{Arguments[0]} was an invalid field name"; string message = $"{Arguments[0]} was an invalid field name";
return (false, message); return (false, message);
@@ -337,12 +337,12 @@ Reset the internal state: reset();";
public override void Process(BatchState batchState) public override void Process(BatchState batchState)
{ {
// Read in the individual arguments // Read in the individual arguments
(string?, string?) fieldName = FilterParser.ParseFilterId(Arguments[0]); FilterParser.ParseFilterId(Arguments[0], out string itemName, out string fieldName);
string extraFile = Arguments[1]; string extraFile = Arguments[1];
// Create the extra INI // Create the extra INI
var extraIni = new ExtraIni(); var extraIni = new ExtraIni();
var extraIniItem = new ExtraIniItem() { FieldName = fieldName }; var extraIniItem = new ExtraIniItem() { FieldName = (itemName, fieldName) };
extraIniItem.PopulateFromFile(extraFile); extraIniItem.PopulateFromFile(extraFile);
extraIni.Items.Add(extraIniItem); extraIni.Items.Add(extraIniItem);
@@ -376,7 +376,7 @@ Reset the internal state: reset();";
} }
// Read in the individual arguments // Read in the individual arguments
(string? type, string? key) = FilterParser.ParseFilterId(Arguments[0]); bool parsed = FilterParser.ParseFilterId(Arguments[0], out _, out _);
bool? filterRemove = false; bool? filterRemove = false;
if (Arguments.Count >= 3) if (Arguments.Count >= 3)
filterRemove = Arguments[2].AsYesNo(); filterRemove = Arguments[2].AsYesNo();
@@ -385,7 +385,7 @@ Reset the internal state: reset();";
filterPerMachine = Arguments[3].AsYesNo(); filterPerMachine = Arguments[3].AsYesNo();
// If we had an invalid input, log and continue // If we had an invalid input, log and continue
if (type == null && key == null) if (!parsed)
{ {
string message = $"{Arguments[0]} was an invalid field name"; string message = $"{Arguments[0]} was an invalid field name";
return (false, message); return (false, message);
@@ -805,10 +805,10 @@ Reset the internal state: reset();";
} }
// Read in the individual arguments // Read in the individual arguments
(string? type, string? key) = FilterParser.ParseFilterId(Arguments[0]); bool parsed = FilterParser.ParseFilterId(Arguments[0], out string type, out _);
// If we had an invalid input, log and continue // If we had an invalid input, log and continue
if ((type == null || !string.Equals(type, Models.Metadata.MetadataFile.HeaderKey, StringComparison.OrdinalIgnoreCase)) && key == null) if (!parsed || !string.Equals(type, Models.Metadata.MetadataFile.HeaderKey, StringComparison.OrdinalIgnoreCase))
{ {
string message = $"{Arguments[0]} was an invalid field name"; string message = $"{Arguments[0]} was an invalid field name";
return (false, message); return (false, message);