diff --git a/SabreTools.Core/Globals.cs b/SabreTools.Core/Globals.cs
index 5f569745..6f8cd156 100644
--- a/SabreTools.Core/Globals.cs
+++ b/SabreTools.Core/Globals.cs
@@ -56,7 +56,7 @@ namespace SabreTools.Core
public static void SetConsoleHeader(string program)
{
// Dynamically create the header string, adapted from http://stackoverflow.com/questions/8200661/how-to-align-string-in-fixed-length-string
- int width = Console.WindowWidth - 3;
+ int width = (Console.WindowWidth == 0 ? 80 : Console.WindowWidth) - 3;
string border = $"+{new string('-', width)}+";
string mid = $"{program} {Globals.Version}";
mid = $"|{mid.PadLeft(((width - mid.Length) / 2) + mid.Length).PadRight(width)}|";
diff --git a/SabreTools.Help/Enums.cs b/SabreTools.Help/Enums.cs
deleted file mode 100644
index 1464eb72..00000000
--- a/SabreTools.Help/Enums.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace SabreTools.Help
-{
- ///
- /// Determines the parameter type to check for
- ///
- public enum ParameterType
- {
- Flag = 0,
- String,
- Int32,
- Int64,
- List,
- }
-}
diff --git a/SabreTools.Help/Feature.cs b/SabreTools.Help/Feature.cs
index ae02d5e4..f599989b 100644
--- a/SabreTools.Help/Feature.cs
+++ b/SabreTools.Help/Feature.cs
@@ -1,49 +1,61 @@
using System;
using System.Collections.Generic;
+using System.Text;
namespace SabreTools.Help
{
- public class Feature
+ public abstract class Feature
{
- #region Protected instance variables
+ #region Fields
- protected ParameterType _featureType;
+ //
+ /// Indicates if the feature has been seen already
+ ///
protected bool _foundOnce = false;
- protected object? _value = null;
#endregion
- #region Publicly facing variables
+ #region Properties
- public string? Name { get; protected set; }
+ ///
+ /// Display name for the feature
+ ///
+ public string Name { get; protected set; }
+
+ ///
+ /// Set of flags associated with the feature
+ ///
public readonly List Flags = [];
- public string? Description { get; protected set; }
+
+ ///
+ /// Short description of the feature
+ ///
+ public string Description { get; protected set; }
+
+ ///
+ /// Optional long description of the feature
+ ///
public string? LongDescription { get; protected set; }
+
+ ///
+ /// Set of subfeatures associated with this feature
+ ///
public readonly Dictionary Features = [];
#endregion
#region Constructors
- ///
- /// Only used by inheriting classes
- ///
- protected Feature()
+ internal Feature(string name, string flag, string description, string? longDescription = null)
{
- }
-
- public Feature(string name, string flag, string description, ParameterType featureType, string? longDescription = null)
- {
- _featureType = featureType;
Name = name;
Flags.Add(flag);
Description = description;
LongDescription = longDescription;
}
- public Feature(string name, List flags, string description, ParameterType featureType, string? longDescription = null)
+ internal Feature(string name, string[] flags, string description, string? longDescription = null)
{
- _featureType = featureType;
Name = name;
Flags.AddRange(flags);
Description = description;
@@ -66,9 +78,9 @@ namespace SabreTools.Help
///
/// Directly address a given subfeature
///
- public Feature? this[Feature? subfeature]
+ public Feature? this[Feature subfeature]
{
- get { return Features.ContainsKey(subfeature?.Name ?? string.Empty) ? Features[subfeature?.Name ?? string.Empty] : null; }
+ get { return Features.ContainsKey(subfeature.Name) ? Features[subfeature.Name] : null; }
set { Features[subfeature?.Name ?? string.Empty] = value; }
}
@@ -84,30 +96,6 @@ namespace SabreTools.Help
}
}
- ///
- /// Add a new flag for this feature
- ///
- /// Flag to add for this feature
- public void AddFlag(string flag)
- {
- lock (Flags)
- {
- Flags.Add(flag);
- }
- }
-
- ///
- /// Add a set of new flags for this feature
- ///
- /// List of flags to add to this feature
- public void AddFlags(List flags)
- {
- lock (Flags)
- {
- Flags.AddRange(flags);
- }
- }
-
///
/// Returns if a flag exists for the current feature
///
@@ -144,54 +132,39 @@ namespace SabreTools.Help
List outputList = [];
// Build the output string first
- string output = string.Empty;
+ var output = new StringBuilder();
// Add the pre-space first
- output += CreatePadding(pre);
+ output.Append(CreatePadding(pre));
- // Preprocess the flags, if necessary
- string[] newflags = new string[Flags.Count];
- Flags.CopyTo(newflags);
- switch (_featureType)
- {
- case ParameterType.Int32:
- case ParameterType.Int64:
- case ParameterType.List:
- case ParameterType.String:
- for (int i = 0; i < newflags.Length; i++)
- {
- newflags[i] += "=";
- }
- break;
- case ParameterType.Flag:
- default:
- // No-op
- break;
- }
-
- // Now add all flags
- output += string.Join(", ", newflags);
+ // Preprocess and add the flags
+ output.Append(FormatFlags());
// If we have a midpoint set, check to see if the string needs padding
- if (midpoint > 0 && output.Length < midpoint)
- output += CreatePadding(midpoint - output.Length);
+ if (midpoint > 0 && output.ToString().Length < midpoint)
+ output.Append(CreatePadding(midpoint - output.ToString().Length));
else
- output += " ";
+ output.Append(" ");
// Append the description
- output += Description;
+ output.Append(Description);
// Now append it to the list
- outputList.Add(output);
+ outputList.Add(output.ToString());
// If we are outputting the long description, format it and then add it as well
if (includeLongDescription)
{
// Get the width of the console for wrapping reference
- int width = Console.WindowWidth - 1;
+ int width = (Console.WindowWidth == 0 ? 80 : Console.WindowWidth) - 1;
// Prepare the output string
- output = CreatePadding(pre + 4);
+#if NET20 || NET35
+ output = new();
+#else
+ output.Clear();
+#endif
+ output.Append(CreatePadding(pre + 4));
// Now split the input description and start processing
string[]? split = LongDescription?.Split(' ');
@@ -207,58 +180,58 @@ namespace SabreTools.Help
for (int j = 0; j < subsplit.Length - 1; j++)
{
// Add the next word only if the total length doesn't go above the width of the screen
- if (output.Length + subsplit[j].Length < width)
+ if (output.ToString().Length + subsplit[j].Length < width)
{
- output += (output.Length == pre + 4 ? string.Empty : " ") + subsplit[j];
+ output.Append((output.ToString().Length == pre + 4 ? string.Empty : " ") + subsplit[j]);
}
// Otherwise, we want to cache the line to output and create a new blank string
else
{
- outputList.Add(output);
- output = CreatePadding(pre + 4);
- output += (output.Length == pre + 4 ? string.Empty : " ") + subsplit[j];
+ outputList.Add(output.ToString());
+#if NET20 || NET35
+ output = new();
+#else
+ output.Clear();
+#endif
+ output.Append(CreatePadding(pre + 4));
+ output.Append((output.ToString().Length == pre + 4 ? string.Empty : " ") + subsplit[j]);
}
- outputList.Add(output);
- output = CreatePadding(pre + 4);
+ outputList.Add(output.ToString());
+#if NET20 || NET35
+ output = new();
+#else
+ output.Clear();
+#endif
+ output.Append(CreatePadding(pre + 4));
}
- output += subsplit[subsplit.Length - 1];
+ output.Append(subsplit[subsplit.Length - 1]);
continue;
}
// Add the next word only if the total length doesn't go above the width of the screen
- if (output.Length + split[i].Length < width)
+ if (output.ToString().Length + split[i].Length < width)
{
- output += (output.Length == pre + 4 ? string.Empty : " ") + split[i];
+ output.Append((output.ToString().Length == pre + 4 ? string.Empty : " ") + split[i]);
}
// Otherwise, we want to cache the line to output and create a new blank string
else
{
- outputList.Add(output);
- output = CreatePadding(pre + 4);
- output += (output.Length == pre + 4 ? string.Empty : " ") + split[i];
+ outputList.Add(output.ToString());
+ output.Append(CreatePadding(pre + 4));
+ output.Append((output.ToString().Length == pre + 4 ? string.Empty : " ") + split[i]);
}
}
// Add the last created output and a blank line for clarity
- outputList.Add(output);
+ outputList.Add(output.ToString());
outputList.Add(string.Empty);
}
return outputList;
}
- ///
- /// Create a padding space based on the given length
- ///
- /// Number of padding spaces to add
- /// String with requested number of blank spaces
- private static string CreatePadding(int spaces)
- {
- return string.Empty.PadRight(spaces);
- }
-
///
/// Output this feature and all subfeatures
///
@@ -272,7 +245,7 @@ namespace SabreTools.Help
List outputList = [];
// Build the output string first
- string output = string.Empty;
+ var output = new StringBuilder();
// Normalize based on the tab level
int preAdjusted = pre;
@@ -284,51 +257,36 @@ namespace SabreTools.Help
}
// Add the pre-space first
- output += CreatePadding(preAdjusted);
+ output.Append(CreatePadding(preAdjusted));
- // Preprocess the flags, if necessary
- string[] newflags = new string[Flags.Count];
- Flags.CopyTo(newflags);
- switch (_featureType)
- {
- case ParameterType.Int32:
- case ParameterType.Int64:
- case ParameterType.List:
- case ParameterType.String:
- for (int i = 0; i < newflags.Length; i++)
- {
- newflags[i] += "=";
- }
- break;
- case ParameterType.Flag:
- default:
- // No-op
- break;
- }
-
- // Now add all flags
- output += string.Join(", ", newflags);
+ // Preprocess and add the flags
+ output.Append(FormatFlags());
// If we have a midpoint set, check to see if the string needs padding
- if (midpoint > 0 && output.Length < midpointAdjusted)
- output += CreatePadding(midpointAdjusted - output.Length);
+ if (midpoint > 0 && output.ToString().Length < midpointAdjusted)
+ output.Append(CreatePadding(midpointAdjusted - output.ToString().Length));
else
- output += " ";
+ output.Append(" ");
// Append the description
- output += Description;
+ output.Append(Description);
// Now append it to the list
- outputList.Add(output);
+ outputList.Add(output.ToString());
// If we are outputting the long description, format it and then add it as well
if (includeLongDescription)
{
// Get the width of the console for wrapping reference
- int width = Console.WindowWidth - 1;
+ int width = (Console.WindowWidth == 0 ? 80 : Console.WindowWidth) - 1;
// Prepare the output string
- output = CreatePadding(preAdjusted + 4);
+#if NET20 || NET35
+ output = new();
+#else
+ output.Clear();
+#endif
+ output.Append(CreatePadding(preAdjusted + 4));
// Now split the input description and start processing
string[]? split = LongDescription?.Split(' ');
@@ -344,42 +302,57 @@ namespace SabreTools.Help
for (int j = 0; j < subsplit.Length - 1; j++)
{
// Add the next word only if the total length doesn't go above the width of the screen
- if (output.Length + subsplit[j].Length < width)
+ if (output.ToString().Length + subsplit[j].Length < width)
{
- output += (output.Length == preAdjusted + 4 ? string.Empty : " ") + subsplit[j];
+ output.Append((output.ToString().Length == preAdjusted + 4 ? string.Empty : " ") + subsplit[j]);
}
// Otherwise, we want to cache the line to output and create a new blank string
else
{
- outputList.Add(output);
- output = CreatePadding(preAdjusted + 4);
- output += (output.Length == preAdjusted + 4 ? string.Empty : " ") + subsplit[j];
+ outputList.Add(output.ToString());
+#if NET20 || NET35
+ output = new();
+#else
+ output.Clear();
+#endif
+ output.Append(CreatePadding(preAdjusted + 4));
+ output.Append((output.ToString().Length == preAdjusted + 4 ? string.Empty : " ") + subsplit[j]);
}
- outputList.Add(output);
- output = CreatePadding(preAdjusted + 4);
+ outputList.Add(output.ToString());
+#if NET20 || NET35
+ output = new();
+#else
+ output.Clear();
+#endif
+ output.Append(CreatePadding(preAdjusted + 4));
}
- output += subsplit[subsplit.Length - 1];
+ output.Append(subsplit[subsplit.Length - 1]);
continue;
}
// Add the next word only if the total length doesn't go above the width of the screen
- if (output.Length + split[i].Length < width)
+ if (output.ToString().Length + split[i].Length < width)
{
- output += (output.Length == preAdjusted + 4 ? string.Empty : " ") + split[i];
+ output.Append((output.ToString().Length == preAdjusted + 4 ? string.Empty : " ") + split[i]);
}
// Otherwise, we want to cache the line to output and create a new blank string
else
{
- outputList.Add(output);
- output = CreatePadding(preAdjusted + 4);
- output += (output.Length == preAdjusted + 4 ? string.Empty : " ") + split[i];
+ outputList.Add(output.ToString());
+#if NET20 || NET35
+ output = new();
+#else
+ output.Clear();
+#endif
+ output.Append(CreatePadding(preAdjusted + 4));
+ output.Append((output.ToString().Length == preAdjusted + 4 ? string.Empty : " ") + split[i]);
}
}
// Add the last created output and a blank line for clarity
- outputList.Add(output);
+ outputList.Add(output.ToString());
outputList.Add(string.Empty);
}
@@ -399,179 +372,58 @@ namespace SabreTools.Help
/// True if just this feature should be checked, false if all subfeatures are checked as well
/// True if the existing flag should be ignored, false otherwise
/// True if the flag was valid, false otherwise
- public bool ValidateInput(string input, bool exact = false, bool ignore = false)
- {
- bool valid = false;
-
- // Pre-split the input for efficiency
- string[] splitInput = input.Split('=');
-
- // Determine what we should be looking for
- switch (_featureType)
- {
- // If we have a flag, make sure it doesn't have an equal sign in it
- case ParameterType.Flag:
- valid = !input.Contains("=") && Flags.Contains(input);
- if (valid)
- {
- _value = true;
-
- // If we've already found this feature before
- if (_foundOnce && !ignore)
- valid = false;
-
- _foundOnce = true;
- }
-
- break;
-
- // If we have an Int32, try to parse it if at all possible
- case ParameterType.Int32:
- valid = input.Contains("=") && Flags.Contains(splitInput[0]);
- if (valid)
- {
- if (!Int32.TryParse(splitInput[1], out int value))
- value = Int32.MinValue;
-
- _value = value;
-
- // If we've already found this feature before
- if (_foundOnce && !ignore)
- valid = false;
-
- _foundOnce = true;
- }
-
- break;
-
- // If we have an Int32, try to parse it if at all possible
- case ParameterType.Int64:
- valid = input.Contains("=") && Flags.Contains(splitInput[0]);
- if (valid)
- {
- if (!Int64.TryParse(splitInput[1], out long value))
- value = Int64.MinValue;
-
- _value = value;
-
- // If we've already found this feature before
- if (_foundOnce && !ignore)
- valid = false;
-
- _foundOnce = true;
- }
-
- break;
-
- // If we have an input, make sure it has an equals sign in it
- case ParameterType.List:
- valid = input.Contains("=") && Flags.Contains(splitInput[0]);
- if (valid)
- {
- _value ??= new List();
- (_value as List)?.Add(string.Join("=", splitInput, 1, splitInput.Length - 1));
- }
-
- break;
-
- case ParameterType.String:
- valid = input.Contains("=") && Flags.Contains(input.Split('=')[0]);
- if (valid)
- {
- _value = string.Join("=", splitInput, 1, splitInput.Length - 1);
-
- // If we've already found this feature before
- if (_foundOnce && !ignore)
- valid = false;
-
- _foundOnce = true;
- }
-
- break;
- }
-
- // If we haven't found a valid flag and we're not looking for just this feature, check to see if any of the subfeatures are valid
- if (!valid && !exact)
- {
- string[] featureKeys = [.. Features.Keys];
- valid = Array.Exists(featureKeys, k => Features[k]!.ValidateInput(input));
- }
-
- return valid;
- }
-
- ///
- /// Get the boolean value associated with this feature
- ///
- public bool GetBoolValue()
- {
- if (_featureType != ParameterType.Flag)
- throw new ArgumentException("Feature is not a flag");
-
- return (_value as bool?) ?? false;
- }
-
- ///
- /// Get the string value associated with this feature
- ///
- public string? GetStringFieldValue()
- {
- if (_featureType != ParameterType.String)
- throw new ArgumentException("Feature is not a string");
-
- return _value as string;
- }
-
- ///
- /// Get the Int32 value associated with this feature
- ///
- public int GetInt32Value()
- {
- if (_featureType != ParameterType.Int32)
- throw new ArgumentException("Feature is not an int");
-
- return (_value as int?) ?? int.MinValue;
- }
-
- ///
- /// Get the Int64 value associated with this feature
- ///
- public long GetInt64Value()
- {
- if (_featureType != ParameterType.Int64)
- throw new ArgumentException("Feature is not a long");
-
- return (_value as long?) ?? long.MinValue;
- }
-
- ///
- /// Get the List\ value associated with this feature
- ///
- public List GetListValue()
- {
- if (_featureType != ParameterType.List)
- throw new ArgumentException("Feature is not a list");
-
- return (_value as List) ?? [];
- }
+ public abstract bool ValidateInput(string input, bool exact = false, bool ignore = false);
///
/// Returns if this feature has a valid value or not
///
/// True if the feature is enabled, false otherwise
- public bool IsEnabled()
+ public abstract bool IsEnabled();
+
+ ///
+ /// Pre-format the flags for output
+ ///
+ protected abstract string FormatFlags();
+
+ ///
+ /// Create a padding space based on the given length
+ ///
+ /// Number of padding spaces to add
+ /// String with requested number of blank spaces
+ private static string CreatePadding(int spaces) => string.Empty.PadRight(spaces);
+
+ #endregion
+ }
+
+ public abstract class Feature : Feature
+ {
+ public T? Value { get; protected set; }
+
+ #region Constructors
+
+ internal Feature(string name, string flag, string description, string? longDescription = null)
+ : base(name, flag, description, longDescription)
{
- return _featureType switch
- {
- ParameterType.Flag => (_value as bool?) == true,
- ParameterType.String => (_value as string) != null,
- ParameterType.Int32 => (_value as int?).HasValue && (_value as int?)!.Value != int.MinValue,
- ParameterType.Int64 => (_value as long?).HasValue && (_value as long?)!.Value != long.MinValue,
- ParameterType.List => (_value as List) != null,
- _ => false,
- };
}
+ internal Feature(string name, string[] flags, string description, string? longDescription = null)
+ : base(name, flags, description, longDescription)
+ {
+ }
+
+ #endregion
+
+ #region Instance Methods
+
+ ///
+ public override abstract bool ValidateInput(string input, bool exact = false, bool ignore = false);
+
+ ///
+ public override abstract bool IsEnabled();
+
+ ///
+ protected override abstract string FormatFlags();
+
#endregion
}
}
diff --git a/SabreTools.Help/FlagFeature.cs b/SabreTools.Help/FlagFeature.cs
new file mode 100644
index 00000000..7716754a
--- /dev/null
+++ b/SabreTools.Help/FlagFeature.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Text;
+
+namespace SabreTools.Help
+{
+ public class FlagFeature : Feature
+ {
+ #region Constructors
+
+ public FlagFeature(string name, string flag, string description, string? longDescription = null)
+ : base(name, flag, description, longDescription)
+ {
+ }
+
+ public FlagFeature(string name, string[] flags, string description, string? longDescription = null)
+ : base(name, flags, description, longDescription)
+ {
+ }
+
+ #endregion
+
+ #region Instance Methods
+
+ ///
+ public override bool ValidateInput(string input, bool exact = false, bool ignore = false)
+ {
+ // Pre-split the input for efficiency
+ string[] splitInput = input.Split('=');
+
+ bool valid = !input.Contains("=") && Flags.Contains(input);
+ if (valid)
+ {
+ Value = true;
+
+ // If we've already found this feature before
+ if (_foundOnce && !ignore)
+ valid = false;
+
+ _foundOnce = true;
+ }
+
+ // If we haven't found a valid flag and we're not looking for just this feature, check to see if any of the subfeatures are valid
+ if (!valid && !exact)
+ {
+ string[] featureKeys = [.. Features.Keys];
+ valid = Array.Exists(featureKeys, k => Features[k]!.ValidateInput(input));
+ }
+
+ return valid;
+ }
+
+ ///
+ public override bool IsEnabled() => Value;
+
+ ///
+ protected override string FormatFlags()
+ {
+ var sb = new StringBuilder();
+ Flags.ForEach(flag => sb.Append($"{flag}, "));
+ return sb.ToString().TrimEnd(' ', ',');
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.Help/Int32Feature.cs b/SabreTools.Help/Int32Feature.cs
new file mode 100644
index 00000000..932539cf
--- /dev/null
+++ b/SabreTools.Help/Int32Feature.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Text;
+
+namespace SabreTools.Help
+{
+ public class Int32Feature : Feature
+ {
+ #region Constructors
+
+ public Int32Feature(string name, string flag, string description, string? longDescription = null)
+ : base(name, flag, description, longDescription)
+ {
+ }
+
+ public Int32Feature(string name, string[] flags, string description, string? longDescription = null)
+ : base(name, flags, description, longDescription)
+ {
+ }
+
+ #endregion
+
+ #region Instance Methods
+
+ ///
+ public override bool ValidateInput(string input, bool exact = false, bool ignore = false)
+ {
+ // Pre-split the input for efficiency
+ string[] splitInput = input.Split('=');
+
+ bool valid = input.Contains("=") && Flags.Contains(splitInput[0]);
+ if (valid)
+ {
+ if (!int.TryParse(splitInput[1], out int value))
+ value = int.MinValue;
+
+ Value = value;
+
+ // If we've already found this feature before
+ if (_foundOnce && !ignore)
+ valid = false;
+
+ _foundOnce = true;
+ }
+
+ // If we haven't found a valid flag and we're not looking for just this feature, check to see if any of the subfeatures are valid
+ if (!valid && !exact)
+ {
+ string[] featureKeys = [.. Features.Keys];
+ valid = Array.Exists(featureKeys, k => Features[k]!.ValidateInput(input));
+ }
+
+ return valid;
+ }
+
+ ///
+ public override bool IsEnabled() => Value != int.MinValue;
+
+ ///
+ protected override string FormatFlags()
+ {
+ var sb = new StringBuilder();
+ Flags.ForEach(flag => sb.Append($"{flag}=, "));
+ return sb.ToString().TrimEnd(' ', ',');
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.Help/Int64Feature.cs b/SabreTools.Help/Int64Feature.cs
new file mode 100644
index 00000000..9bf79979
--- /dev/null
+++ b/SabreTools.Help/Int64Feature.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Text;
+
+namespace SabreTools.Help
+{
+ public class Int64Feature : Feature
+ {
+ #region Constructors
+
+ public Int64Feature(string name, string flag, string description, string? longDescription = null)
+ : base(name, flag, description, longDescription)
+ {
+ }
+
+ public Int64Feature(string name, string[] flags, string description, string? longDescription = null)
+ : base(name, flags, description, longDescription)
+ {
+ }
+
+ #endregion
+
+ #region Instance Methods
+
+ ///
+ public override bool ValidateInput(string input, bool exact = false, bool ignore = false)
+ {
+ // Pre-split the input for efficiency
+ string[] splitInput = input.Split('=');
+
+ bool valid = input.Contains("=") && Flags.Contains(splitInput[0]);
+ if (valid)
+ {
+ if (!long.TryParse(splitInput[1], out long value))
+ value = long.MinValue;
+
+ Value = value;
+
+ // If we've already found this feature before
+ if (_foundOnce && !ignore)
+ valid = false;
+
+ _foundOnce = true;
+ }
+
+ // If we haven't found a valid flag and we're not looking for just this feature, check to see if any of the subfeatures are valid
+ if (!valid && !exact)
+ {
+ string[] featureKeys = [.. Features.Keys];
+ valid = Array.Exists(featureKeys, k => Features[k]!.ValidateInput(input));
+ }
+
+ return valid;
+ }
+
+ ///
+ public override bool IsEnabled() => Value != long.MinValue;
+
+ ///
+ protected override string FormatFlags()
+ {
+ var sb = new StringBuilder();
+ Flags.ForEach(flag => sb.Append($"{flag}=, "));
+ return sb.ToString().TrimEnd(' ', ',');
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.Help/ListFeature.cs b/SabreTools.Help/ListFeature.cs
new file mode 100644
index 00000000..1473a86b
--- /dev/null
+++ b/SabreTools.Help/ListFeature.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SabreTools.Help
+{
+ public class ListFeature : Feature>
+ {
+ #region Constructors
+
+ public ListFeature(string name, string flag, string description, string? longDescription = null)
+ : base(name, flag, description, longDescription)
+ {
+ }
+
+ public ListFeature(string name, string[] flags, string description, string? longDescription = null)
+ : base(name, flags, description, longDescription)
+ {
+ }
+
+ #endregion
+
+ #region Instance Methods
+
+ ///
+ public override bool ValidateInput(string input, bool exact = false, bool ignore = false)
+ {
+ // Pre-split the input for efficiency
+ string[] splitInput = input.Split('=');
+
+ bool valid = input.Contains("=") && Flags.Contains(splitInput[0]);
+ if (valid)
+ {
+ Value ??= [];
+ Value.Add(string.Join("=", splitInput, 1, splitInput.Length - 1));
+ }
+
+ // If we haven't found a valid flag and we're not looking for just this feature, check to see if any of the subfeatures are valid
+ if (!valid && !exact)
+ {
+ string[] featureKeys = [.. Features.Keys];
+ valid = Array.Exists(featureKeys, k => Features[k]!.ValidateInput(input));
+ }
+
+ return valid;
+ }
+
+ ///
+ public override bool IsEnabled() => Value != null;
+
+ ///
+ protected override string FormatFlags()
+ {
+ var sb = new StringBuilder();
+ Flags.ForEach(flag => sb.Append($"{flag}=, "));
+ return sb.ToString().TrimEnd(' ', ',');
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.Help/StringFeature.cs b/SabreTools.Help/StringFeature.cs
new file mode 100644
index 00000000..ddc95d7f
--- /dev/null
+++ b/SabreTools.Help/StringFeature.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Text;
+
+namespace SabreTools.Help
+{
+ public class StringFeature : Feature
+ {
+ #region Constructors
+
+ public StringFeature(string name, string flag, string description, string? longDescription = null)
+ : base(name, flag, description, longDescription)
+ {
+ }
+
+ public StringFeature(string name, string[] flags, string description, string? longDescription = null)
+ : base(name, flags, description, longDescription)
+ {
+ }
+
+ #endregion
+
+ #region Instance Methods
+
+ ///
+ public override bool ValidateInput(string input, bool exact = false, bool ignore = false)
+ {
+ // Pre-split the input for efficiency
+ string[] splitInput = input.Split('=');
+
+ bool valid = input.Contains("=") && Flags.Contains(input.Split('=')[0]);
+ if (valid)
+ {
+ Value = string.Join("=", splitInput, 1, splitInput.Length - 1);
+
+ // If we've already found this feature before
+ if (_foundOnce && !ignore)
+ valid = false;
+
+ _foundOnce = true;
+ }
+
+ // If we haven't found a valid flag and we're not looking for just this feature, check to see if any of the subfeatures are valid
+ if (!valid && !exact)
+ {
+ string[] featureKeys = [.. Features.Keys];
+ valid = Array.Exists(featureKeys, k => Features[k]!.ValidateInput(input));
+ }
+
+ return valid;
+ }
+
+ ///
+ public override bool IsEnabled() => Value != null;
+
+ ///
+ protected override string FormatFlags()
+ {
+ var sb = new StringBuilder();
+ Flags.ForEach(flag => sb.Append($"{flag}=, "));
+ return sb.ToString().TrimEnd(' ', ',');
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.Help/TopLevel.cs b/SabreTools.Help/TopLevel.cs
index 9dce0a13..d7d27444 100644
--- a/SabreTools.Help/TopLevel.cs
+++ b/SabreTools.Help/TopLevel.cs
@@ -8,7 +8,7 @@ namespace SabreTools.Help
///
/// Represents an actionable top-level feature
///
- public abstract class TopLevel : Feature
+ public abstract class TopLevel : FlagFeature
{
#region Fields
@@ -30,10 +30,14 @@ namespace SabreTools.Help
#region Constructors
- ///
- /// Constructor
- ///
- public TopLevel()
+ public TopLevel(string name, string flag, string description, string? longDescription = null)
+ : base(name, flag, description, longDescription)
+ {
+ _logger = new Logger(this);
+ }
+
+ public TopLevel(string name, string[] flags, string description, string? longDescription = null)
+ : base(name, flags, description, longDescription)
{
_logger = new Logger(this);
}
@@ -109,9 +113,12 @@ namespace SabreTools.Help
protected static int GetInt32(Dictionary features, string key)
{
if (!features.ContainsKey(key))
- return Int32.MinValue;
+ return int.MinValue;
- return features[key]!.GetInt32Value();
+ if (features[key] is not Int32Feature i)
+ throw new ArgumentException("Feature is not an int");
+
+ return i.Value;
}
///
@@ -120,9 +127,12 @@ namespace SabreTools.Help
protected static long GetInt64(Dictionary features, string key)
{
if (!features.ContainsKey(key))
- return Int64.MinValue;
+ return long.MinValue;
- return features[key]!.GetInt64Value();
+ if (features[key] is not Int64Feature l)
+ throw new ArgumentException("Feature is not a long");
+
+ return l.Value;
}
///
@@ -133,7 +143,10 @@ namespace SabreTools.Help
if (!features.ContainsKey(key))
return [];
- return features[key]!.GetListValue() ?? [];
+ if (features[key] is not ListFeature l)
+ throw new ArgumentException("Feature is not a list");
+
+ return l.Value ?? [];
}
///
@@ -144,7 +157,10 @@ namespace SabreTools.Help
if (!features.ContainsKey(key))
return null;
- return features[key]!.GetStringFieldValue();
+ if (features[key] is not StringFeature s)
+ throw new ArgumentException("Feature is not a string");
+
+ return s.Value;
}
#endregion
diff --git a/SabreTools/Features/BaseFeature.cs b/SabreTools/Features/BaseFeature.cs
index 08e819d0..d4ef6352 100644
--- a/SabreTools/Features/BaseFeature.cs
+++ b/SabreTools/Features/BaseFeature.cs
@@ -11,7 +11,7 @@ using SabreTools.Reports;
namespace SabreTools.Features
{
- internal class BaseFeature : TopLevel
+ internal abstract class BaseFeature : TopLevel
{
#region Logging
@@ -22,643 +22,578 @@ namespace SabreTools.Features
#endregion
+ #region Constructors
+
+ public BaseFeature(string name, string flag, string description, string? longDescription = null)
+ : base(name, flag, description, longDescription)
+ {
+ }
+
+ public BaseFeature(string name, string[] flags, string description, string? longDescription = null)
+ : base(name, flags, description, longDescription)
+ {
+ }
+
+ #endregion
+
#region Features
#region Flag features
internal const string AaruFormatsAsFilesValue = "aaruformats-as-files";
- internal static Feature AaruFormatsAsFilesFlag => new(
+ internal static FlagFeature AaruFormatsAsFilesFlag => new(
AaruFormatsAsFilesValue,
["-caf", "--aaruformats-as-files"],
"Treat AaruFormats as files",
- ParameterType.Flag,
longDescription: "Normally, AaruFormats would be processed using their internal hash to compare against the input DATs. This flag forces all AaruFormats to be treated like regular files.");
internal const string AddBlankFilesValue = "add-blank-files";
- internal static Feature AddBlankFilesFlag => new(
+ internal static FlagFeature AddBlankFilesFlag => new(
AddBlankFilesValue,
["-ab", "--add-blank-files"],
"Output blank files for folders",
- ParameterType.Flag,
longDescription: "If this flag is set, then blank entries will be created for each of the empty directories in the source. This is useful for tools that require all folders be accounted for in the output DAT.");
internal const string AddDateValue = "add-date";
- internal static Feature AddDateFlag => new(
+ internal static FlagFeature AddDateFlag => new(
AddDateValue,
["-ad", "--add-date"],
"Add dates to items, where possible",
- ParameterType.Flag,
longDescription: "If this flag is set, then the Date will be appended to each file information in the output DAT. The output format is standardized as \"yyyy/MM/dd HH:mm:ss\".");
internal const string ArchivesAsFilesValue = "archives-as-files";
- internal static Feature ArchivesAsFilesFlag => new(
+ internal static FlagFeature ArchivesAsFilesFlag => new(
ArchivesAsFilesValue,
["-aaf", "--archives-as-files"],
"Treat archives as files",
- ParameterType.Flag,
longDescription: "Instead of trying to enumerate the files within archives, treat the archives as files themselves. This is good for uncompressed sets that include archives that should be read as-is.");
internal const string BaddumpColumnValue = "baddump-column";
- internal static Feature BaddumpColumnFlag => new(
+ internal static FlagFeature BaddumpColumnFlag => new(
BaddumpColumnValue,
["-bc", "--baddump-column"],
"Add baddump stats to output",
- ParameterType.Flag,
longDescription: "Add a new column or field for counting the number of baddumps in the DAT.");
internal const string BaseValue = "base";
- internal static Feature BaseFlag => new(
+ internal static FlagFeature BaseFlag => new(
BaseValue,
["-ba", "--base"],
"Use source DAT as base name for outputs",
- ParameterType.Flag,
longDescription: "If splitting an entire folder of DATs, some output files may be normally overwritten since the names would be the same. With this flag, the original DAT name is used in the output name, in the format of \"Original Name(Dir - Name)\". This can be used in conjunction with --short to output in the format of \"Original Name (Name)\" instead.");
internal const string BaseReplaceValue = "base-replace";
- internal static Feature BaseReplaceFlag => new(
+ internal static FlagFeature BaseReplaceFlag => new(
BaseReplaceValue,
["-br", "--base-replace"],
"Replace from base DATs in order",
- ParameterType.Flag,
longDescription: "By default, no item names are changed except when there is a merge occurring. This flag enables users to define a DAT or set of base DATs to use as \"replacements\" for all input DATs. Note that the first found instance of an item in the base DAT(s) will be used and all others will be discarded. If no additional flag is given, it will default to updating names.");
internal const string ByGameValue = "by-game";
- internal static Feature ByGameFlag => new(
+ internal static FlagFeature ByGameFlag => new(
ByGameValue,
["-bg", "--by-game"],
"Diff against by game instead of hashes",
- ParameterType.Flag,
longDescription: "By default, diffing against uses hashes to determine similar files. This flag enables using using each game as a comparision point instead.");
internal const string ChdsAsFilesValue = "chds-as-files";
- internal static Feature ChdsAsFilesFlag => new(
+ internal static FlagFeature ChdsAsFilesFlag => new(
ChdsAsFilesValue,
["-ic", "--chds-as-files"],
"Treat CHDs as regular files",
- ParameterType.Flag,
longDescription: "Normally, CHDs would be processed using their internal hash to compare against the input DATs. This flag forces all CHDs to be treated like regular files.");
internal const string CleanValue = "clean";
- internal static Feature CleanFlag => new(
+ internal static FlagFeature CleanFlag => new(
CleanValue,
["-clean", "--clean"],
"Clean game names according to WoD standards",
- ParameterType.Flag,
longDescription: "Game names will be sanitized to remove what the original WoD standards deemed as unneeded information, such as parenthesized or bracketed strings.");
internal const string DatDeviceNonMergedValue = "dat-device-non-merged";
- internal static Feature DatDeviceNonMergedFlag => new(
+ internal static FlagFeature DatDeviceNonMergedFlag => new(
DatDeviceNonMergedValue,
["-dnd", "--dat-device-non-merged"],
"Create device non-merged sets",
- ParameterType.Flag,
longDescription: "Preprocess the DAT to have child sets contain all items from the device references. This is incompatible with the other --dat-X flags.");
internal const string DatFullMergedValue = "dat-full-merged";
- internal static Feature DatFullMergedFlag => new(
+ internal static FlagFeature DatFullMergedFlag => new(
DatFullMergedValue,
["-dfm", "--dat-full-merged"],
"Create fully merged sets",
- ParameterType.Flag,
longDescription: "Preprocess the DAT to have parent sets contain all items from the children based on the cloneof tag while also performing deduplication within a parent. This is incompatible with the other --dat-X flags.");
internal const string DatFullNonMergedValue = "dat-full-non-merged";
- internal static Feature DatFullNonMergedFlag => new(
+ internal static FlagFeature DatFullNonMergedFlag => new(
DatFullNonMergedValue,
["-df", "--dat-full-non-merged"],
"Create fully non-merged sets",
- ParameterType.Flag,
longDescription: "Preprocess the DAT to have child sets contain all items from the parent sets based on the cloneof and romof tags as well as device references. This is incompatible with the other --dat-X flags.");
internal const string DatMergedValue = "dat-merged";
- internal static Feature DatMergedFlag => new(
+ internal static FlagFeature DatMergedFlag => new(
DatMergedValue,
["-dm", "--dat-merged"],
"Force creating merged sets",
- ParameterType.Flag,
longDescription: "Preprocess the DAT to have parent sets contain all items from the children based on the cloneof tag. This is incompatible with the other --dat-X flags.");
internal const string DatNonMergedValue = "dat-non-merged";
- internal static Feature DatNonMergedFlag => new(
+ internal static FlagFeature DatNonMergedFlag => new(
DatNonMergedValue,
["-dnm", "--dat-non-merged"],
"Force creating non-merged sets",
- ParameterType.Flag,
longDescription: "Preprocess the DAT to have child sets contain all items from the parent set based on the romof and cloneof tags. This is incompatible with the other --dat-X flags.");
internal const string DatSplitValue = "dat-split";
- internal static Feature DatSplitFlag => new(
+ internal static FlagFeature DatSplitFlag => new(
DatSplitValue,
["-ds", "--dat-split"],
"Force creating split sets",
- ParameterType.Flag,
longDescription: "Preprocess the DAT to remove redundant files between parents and children based on the romof and cloneof tags. This is incompatible with the other --dat-X flags.");
internal const string DedupValue = "dedup";
- internal static Feature DedupFlag => new(
+ internal static FlagFeature DedupFlag => new(
DedupValue,
["-dd", "--dedup"],
"Enable deduping in the created DAT",
- ParameterType.Flag,
longDescription: "For all outputted DATs, allow for hash deduping. This makes sure that there are effectively no duplicates in the output files. Cannot be used with game dedup.");
internal const string DeleteValue = "delete";
- internal static Feature DeleteFlag => new(
+ internal static FlagFeature DeleteFlag => new(
DeleteValue,
["-del", "--delete"],
"Delete fully rebuilt input files",
- ParameterType.Flag,
longDescription: "Optionally, the input files, once processed and fully matched, can be deleted. This can be useful when the original file structure is no longer needed or if there is limited space on the source drive.");
internal const string DepotValue = "depot";
- internal static Feature DepotFlag => new(
+ internal static FlagFeature DepotFlag => new(
DepotValue,
["-dep", "--depot"],
"Assume directories are Romba depots",
- ParameterType.Flag,
longDescription: "Normally, input directories will be treated with no special format. If this flag is used, all input directories will be assumed to be Romba-style depots.");
internal const string DeprecatedValue = "deprecated";
- internal static Feature DeprecatedFlag => new(
+ internal static FlagFeature DeprecatedFlag => new(
DeprecatedValue,
["-dpc", "--deprecated"],
"Output 'game' instead of 'machine'",
- ParameterType.Flag,
longDescription: "By default, Logiqx XML DATs output with the more modern \"machine\" tag for each set. This flag allows users to output the older \"game\" tag instead, for compatibility reasons. [Logiqx only]");
internal const string DescriptionAsNameValue = "description-as-name";
- internal static Feature DescriptionAsNameFlag => new(
+ internal static FlagFeature DescriptionAsNameFlag => new(
DescriptionAsNameValue,
["-dan", "--description-as-name"],
"Use description instead of machine name",
- ParameterType.Flag,
longDescription: "By default, all DATs are converted exactly as they are input. Enabling this flag allows for the machine names in the DAT to be replaced by the machine description instead. In most cases, this will result in no change in the output DAT, but a notable example would be a software list DAT where the machine names are generally DOS-friendly while the description is more complete.");
internal const string DiffAgainstValue = "diff-against";
- internal static Feature DiffAgainstFlag => new(
+ internal static FlagFeature DiffAgainstFlag => new(
DiffAgainstValue,
["-dag", "--diff-against"],
"Diff all inputs against a set of base DATs",
- ParameterType.Flag,
"This flag will enable a special type of diffing in which a set of base DATs are used as a comparison point for each of the input DATs. This allows users to get a slightly different output to cascaded diffing, which may be more useful in some cases. This is heavily influenced by the diffing model used by Romba.");
internal const string DiffAllValue = "diff-all";
- internal static Feature DiffAllFlag => new(
+ internal static FlagFeature DiffAllFlag => new(
DiffAllValue,
["-di", "--diff-all"],
"Create diffdats from inputs (all standard outputs)",
- ParameterType.Flag,
longDescription: "By default, all DATs are processed individually with the user-specified flags. With this flag enabled, input DATs are diffed against each other to find duplicates, no duplicates, and only in individuals.");
internal const string DiffCascadeValue = "diff-cascade";
- internal static Feature DiffCascadeFlag => new(
+ internal static FlagFeature DiffCascadeFlag => new(
DiffCascadeValue,
["-dc", "--diff-cascade"],
"Enable cascaded diffing",
- ParameterType.Flag,
longDescription: "This flag allows for a special type of diffing in which the first DAT is considered a base, and for each additional input DAT, it only leaves the files that are not in one of the previous DATs. This can allow for the creation of rollback sets or even just reduce the amount of duplicates across multiple sets.");
internal const string DiffDuplicatesValue = "diff-duplicates";
- internal static Feature DiffDuplicatesFlag => new(
+ internal static FlagFeature DiffDuplicatesFlag => new(
DiffDuplicatesValue,
["-did", "--diff-duplicates"],
"Create diffdat containing just duplicates",
- ParameterType.Flag,
longDescription: "All files that have duplicates outside of the original DAT are included.");
internal const string DiffIndividualsValue = "diff-individuals";
- internal static Feature DiffIndividualsFlag => new(
+ internal static FlagFeature DiffIndividualsFlag => new(
DiffIndividualsValue,
["-dii", "--diff-individuals"],
"Create diffdats for individual DATs",
- ParameterType.Flag,
longDescription: "All files that have no duplicates outside of the original DATs are put into DATs that are named after the source DAT.");
internal const string DiffNoDuplicatesValue = "diff-no-duplicates";
- internal static Feature DiffNoDuplicatesFlag => new(
+ internal static FlagFeature DiffNoDuplicatesFlag => new(
DiffNoDuplicatesValue,
["-din", "--diff-no-duplicates"],
"Create diffdat containing no duplicates",
- ParameterType.Flag,
longDescription: "All files that have no duplicates outside of the original DATs are included.");
internal const string DiffReverseCascadeValue = "diff-reverse-cascade";
- internal static Feature DiffReverseCascadeFlag => new(
+ internal static FlagFeature DiffReverseCascadeFlag => new(
DiffReverseCascadeValue,
["-drc", "--diff-reverse-cascade"],
"Enable reverse cascaded diffing",
- ParameterType.Flag,
longDescription: "This flag allows for a special type of diffing in which the last DAT is considered a base, and for each additional input DAT, it only leaves the files that are not in one of the previous DATs. This can allow for the creation of rollback sets or even just reduce the amount of duplicates across multiple sets.");
internal const string ExtensionValue = "extension";
- internal static Feature ExtensionFlag => new(
+ internal static FlagFeature ExtensionFlag => new(
ExtensionValue,
["-es", "--extension"],
"Split DAT(s) by two file extensions",
- ParameterType.Flag,
longDescription: "For a DAT, or set of DATs, allow for splitting based on a list of input extensions. This can allow for combined DAT files, such as those combining two separate systems, to be split. Files with any extensions not listed in the input lists will be included in both outputted DAT files.");
internal const string GameDedupValue = "game-dedup";
- internal static Feature GameDedupFlag => new(
+ internal static FlagFeature GameDedupFlag => new(
GameDedupValue,
["-gdd", "--game-dedup"],
"Enable deduping within games in the created DAT",
- ParameterType.Flag,
longDescription: "For all outputted DATs, allow for hash deduping but only within the games, and not across the entire DAT. This makes sure that there are effectively no duplicates within each of the output sets. Cannot be used with standard dedup.");
internal const string GamePrefixValue = "game-prefix";
- internal static Feature GamePrefixFlag => new(
+ internal static FlagFeature GamePrefixFlag => new(
GamePrefixValue,
["-gp", "--game-prefix"],
"Add game name as a prefix",
- ParameterType.Flag,
longDescription: "This flag allows for the name of the game to be used as a prefix to each file.");
internal const string HashValue = "hash";
- internal static Feature HashFlag => new(
+ internal static FlagFeature HashFlag => new(
HashValue,
["-hs", "--hash"],
"Split DAT(s) or folder by best-available hashes",
- ParameterType.Flag,
longDescription: "For a DAT, or set of DATs, allow for splitting based on the best available hash for each file within. The order of preference for the outputted DATs is as follows: Nodump, SHA-512, SHA-384, SHA-256, SHA-1, MD5, MD4, MD2, CRC (or worse).");
internal const string HashOnlyValue = "hash-only";
- internal static Feature HashOnlyFlag => new(
+ internal static FlagFeature HashOnlyFlag => new(
HashOnlyValue,
["-ho", "--hash-only"],
"Check files by hash only",
- ParameterType.Flag,
longDescription: "This sets a mode where files are not checked based on name but rather hash alone. This allows verification of (possibly) incorrectly named folders and sets to be verified without worrying about the proper set structure to be there.");
internal const string IncludeCrcValue = "include-crc";
- internal static Feature IncludeCrcFlag => new(
+ internal static FlagFeature IncludeCrcFlag => new(
IncludeCrcValue,
["-crc", "--include-crc"],
"Include CRC32 in output",
- ParameterType.Flag,
longDescription: "This enables CRC32 calculation for each of the files. Adding this flag overrides the default hashing behavior of including CRC32, MD5, and SHA-1 hashes.");
internal const string IncludeMd2Value = "include-md2";
- internal static Feature IncludeMd2Flag => new(
+ internal static FlagFeature IncludeMd2Flag => new(
IncludeMd2Value,
["-md2", "--include-md2"],
"Include MD2 in output",
- ParameterType.Flag,
longDescription: "This enables MD2 calculation for each of the files. Adding this flag overrides the default hashing behavior of including CRC32, MD5, and SHA-1 hashes.");
internal const string IncludeMd4Value = "include-md4";
- internal static Feature IncludeMd4Flag => new(
+ internal static FlagFeature IncludeMd4Flag => new(
IncludeMd4Value,
["-md4", "--include-md4"],
"Include MD4 in output",
- ParameterType.Flag,
longDescription: "This enables MD4 calculation for each of the files. Adding this flag overrides the default hashing behavior of including CRC32, MD5, and SHA-1 hashes.");
internal const string IncludeMd5Value = "include-md5";
- internal static Feature IncludeMd5Flag => new(
+ internal static FlagFeature IncludeMd5Flag => new(
IncludeMd5Value,
["-md5", "--include-md5"],
"Include MD5 in output",
- ParameterType.Flag,
longDescription: "This enables MD5 calculation for each of the files. Adding this flag overrides the default hashing behavior of including CRC32, MD5, and SHA-1 hashes.");
internal const string IncludeSha1Value = "include-sha1";
- internal static Feature IncludeSha1Flag => new(
+ internal static FlagFeature IncludeSha1Flag => new(
IncludeSha1Value,
["-sha1", "--include-sha1"],
"Include SHA-1 in output",
- ParameterType.Flag,
longDescription: "This enables SHA-1 calculation for each of the files. Adding this flag overrides the default hashing behavior of including CRC32, MD5, and SHA-1 hashes.");
internal const string IncludeSha256Value = "include-sha256";
- internal static Feature IncludeSha256Flag => new(
+ internal static FlagFeature IncludeSha256Flag => new(
IncludeSha256Value,
["-sha256", "--include-sha256"],
"Include SHA-256 in output",
- ParameterType.Flag,
longDescription: "This enables SHA-256 calculation for each of the files. Adding this flag overrides the default hashing behavior of including CRC32, MD5, and SHA-1 hashes.");
internal const string IncludeSha384Value = "include-sha384";
- internal static Feature IncludeSha384Flag => new(
+ internal static FlagFeature IncludeSha384Flag => new(
IncludeSha384Value,
["-sha384", "--include-sha384"],
"Include SHA-384 in output",
- ParameterType.Flag,
longDescription: "This enables SHA-384 calculation for each of the files. Adding this flag overrides the default hashing behavior of including CRC32, MD5, and SHA-1 hashes.");
internal const string IncludeSha512Value = "include-sha512";
- internal static Feature IncludeSha512Flag => new(
+ internal static FlagFeature IncludeSha512Flag => new(
IncludeSha512Value,
["-sha512", "--include-sha512"],
"Include SHA-512 in output",
- ParameterType.Flag,
longDescription: "This enables SHA-512 calculation for each of the files. Adding this flag overrides the default hashing behavior of including CRC32, MD5, and SHA-1 hashes.");
internal const string IncludeSpamSumValue = "include-spamsum";
- internal static Feature IncludeSpamSumFlag => new(
+ internal static FlagFeature IncludeSpamSumFlag => new(
IncludeSpamSumValue,
["-spamsum", "--include-spamsum"],
"Include SpamSum in output",
- ParameterType.Flag,
longDescription: "This enables SpamSum calculation for each of the files. Adding this flag overrides the default hashing behavior of including CRC32, MD5, and SHA-1 hashes.");
internal const string IndividualValue = "individual";
- internal static Feature IndividualFlag => new(
+ internal static FlagFeature IndividualFlag => new(
IndividualValue,
["-ind", "--individual"],
"Process input DATs individually",
- ParameterType.Flag,
longDescription: "In cases where DATs would be processed in bulk, this flag allows them to be processed on their own instead.");
internal const string InplaceValue = "inplace";
- internal static Feature InplaceFlag => new(
+ internal static FlagFeature InplaceFlag => new(
InplaceValue,
["-ip", "--inplace"],
"Write to the input directories, where possible",
- ParameterType.Flag,
longDescription: "By default, files are written to the runtime directory (or the output directory, if set). This flag enables users to write out to the directory that the DATs originated from.");
internal const string InverseValue = "inverse";
- internal static Feature InverseFlag => new(
+ internal static FlagFeature InverseFlag => new(
InverseValue,
["-in", "--inverse"],
"Rebuild only files not in DAT",
- ParameterType.Flag,
longDescription: "Instead of the normal behavior of rebuilding using a DAT, this flag allows the user to use the DAT as a filter instead. All files that are found in the DAT will be skipped and everything else will be output in the selected format.");
internal const string KeepEmptyGamesValue = "keep-empty-games";
- internal static Feature KeepEmptyGamesFlag => new(
+ internal static FlagFeature KeepEmptyGamesFlag => new(
KeepEmptyGamesValue,
["-keg", "--keep-empty-games"],
"Keep originally empty sets from the input(s)",
- ParameterType.Flag,
longDescription: "Normally, any sets that are considered empty will not be included in the output, this flag allows these empty sets to be added to the output.");
internal const string LevelValue = "level";
- internal static Feature LevelFlag => new(
+ internal static FlagFeature LevelFlag => new(
LevelValue,
["-ls", "--level"],
"Split a SuperDAT or folder by lowest available level",
- ParameterType.Flag,
longDescription: "For a DAT, or set of DATs, allow for splitting based on the lowest available level of game name. That is, if a game name is top/mid/last, then it will create an output DAT for the parent directory \"mid\" in a folder called \"top\" with a game called \"last\".");
internal const string MatchOfTagsValue = "match-of-tags";
- internal static Feature MatchOfTagsFlag => new(
+ internal static FlagFeature MatchOfTagsFlag => new(
MatchOfTagsValue,
["-ofg", "--match-of-tags"],
"Allow cloneof and romof tags to match game name filters",
- ParameterType.Flag,
longDescription: "If filter or exclude by game name is used, this flag will allow those filters to be checked against the romof and cloneof tags as well. This can allow for more advanced set-building, especially in arcade-based sets.");
internal const string MergeValue = "merge";
- internal static Feature MergeFlag => new(
+ internal static FlagFeature MergeFlag => new(
MergeValue,
["-m", "--merge"],
"Merge the input DATs",
- ParameterType.Flag,
longDescription: "By default, all DATs are processed individually with the user-specified flags. With this flag enabled, all of the input DATs are merged into a single output. This is best used with the dedup flag.");
internal const string NoAutomaticDateValue = "no-automatic-date";
- internal static Feature NoAutomaticDateFlag => new(
+ internal static FlagFeature NoAutomaticDateFlag => new(
NoAutomaticDateValue,
["-b", "--no-automatic-date"],
"Don't include date in file name",
- ParameterType.Flag,
longDescription: "Normally, the DAT will be created with the date in the file name in brackets. This flag removes that instead of the default.");
internal const string NodumpColumnValue = "nodump-column";
- internal static Feature NodumpColumnFlag => new(
+ internal static FlagFeature NodumpColumnFlag => new(
NodumpColumnValue,
["-nc", "--nodump-column"],
"Add statistics for nodumps to output",
- ParameterType.Flag,
longDescription: "Add a new column or field for counting the number of nodumps in the DAT.");
internal const string OneGamePerRegionValue = "one-game-per-region";
- internal static Feature OneGamePerRegionFlag => new(
+ internal static FlagFeature OneGamePerRegionFlag => new(
OneGamePerRegionValue,
["-1g1r", "--one-game-per-region"],
"[EXPERIMENTAL] Try to ensure one game per user-defined region",
- ParameterType.Flag,
longDescription: "This allows users to input a list of regions to use to filter on in order so only one game from each set of parent and clones will be included. This requires either cloneof or romof tags to function properly.");
internal const string OneRomPerGameValue = "one-rom-per-game";
- internal static Feature OneRomPerGameFlag => new(
+ internal static FlagFeature OneRomPerGameFlag => new(
OneRomPerGameValue,
["-orpg", "--one-rom-per-game"],
"Try to ensure each rom has its own game",
- ParameterType.Flag,
longDescription: "In some cases, it is beneficial to have every rom put into its own output set as a subfolder of the original parent. This flag enables outputting each rom to its own game for this purpose.");
internal const string OnlySameValue = "only-same";
- internal static Feature OnlySameFlag => new(
+ internal static FlagFeature OnlySameFlag => new(
OnlySameValue,
["-ons", "--only-same"],
"Only update description if machine name matches description",
- ParameterType.Flag,
longDescription: "Normally, updating the description will always overwrite if the machine names are the same. With this flag, descriptions will only be overwritten if they are the same as the machine names.");
internal const string QuickValue = "quick";
- internal static Feature QuickFlag => new(
+ internal static FlagFeature QuickFlag => new(
QuickValue,
["-qs", "--quick"],
"Enable quick scanning of archives",
- ParameterType.Flag,
longDescription: "For all archives, if this flag is enabled, it will only use the header information to get the archive entries' file information. The upside to this is that it is the fastest option. On the downside, it can only get the CRC and size from most archive formats, leading to possible issues.");
internal const string QuotesValue = "quotes";
- internal static Feature QuotesFlag => new(
+ internal static FlagFeature QuotesFlag => new(
QuotesValue,
["-q", "--quotes"],
"Double-quote each item",
- ParameterType.Flag,
longDescription: "This flag surrounds the item by double-quotes, not including the prefix or postfix.");
internal const string RemoveExtensionsValue = "remove-extensions";
- internal static Feature RemoveExtensionsFlag => new(
+ internal static FlagFeature RemoveExtensionsFlag => new(
RemoveExtensionsValue,
["-rme", "--remove-extensions"],
"Remove all extensions from all items",
- ParameterType.Flag,
longDescription: "For each item, remove the extension.");
internal const string RemoveUnicodeValue = "remove-unicode";
- internal static Feature RemoveUnicodeFlag => new(
+ internal static FlagFeature RemoveUnicodeFlag => new(
RemoveUnicodeValue,
["-ru", "--remove-unicode"],
"Remove unicode characters from names",
- ParameterType.Flag,
longDescription: "By default, the character set from the original file(s) will be used for item naming. This flag removes all Unicode characters from the item names, machine names, and machine descriptions.");
internal const string ReverseBaseReplaceValue = "reverse-base-replace";
- internal static Feature ReverseBaseReplaceFlag => new(
+ internal static FlagFeature ReverseBaseReplaceFlag => new(
ReverseBaseReplaceValue,
["-rbr", "--reverse-base-replace"],
"Replace item names from base DATs in reverse",
- ParameterType.Flag,
longDescription: "By default, no item names are changed except when there is a merge occurring. This flag enables users to define a DAT or set of base DATs to use as \"replacements\" for all input DATs. Note that the first found instance of an item in the last base DAT(s) will be used and all others will be discarded. If no additional flag is given, it will default to updating names.");
internal const string RombaValue = "romba";
- internal static Feature RombaFlag => new(
+ internal static FlagFeature RombaFlag => new(
RombaValue,
["-ro", "--romba"],
"Treat like a Romba depot (requires SHA-1)",
- ParameterType.Flag,
longDescription: "This flag allows reading and writing of DATs and output files to and from a Romba-style depot. This also implies TorrentGZ input and output for physical files. Where appropriate, Romba depot files will be created as well.");
internal const string RomsValue = "roms";
- internal static Feature RomsFlag => new(
+ internal static FlagFeature RomsFlag => new(
RomsValue,
["-r", "--roms"],
"Output roms to miss instead of sets",
- ParameterType.Flag,
longDescription: "By default, the outputted file will include the name of the game so this flag allows for the name of the rom to be output instead. [Missfile only]");
internal const string SceneDateStripValue = "scene-date-strip";
- internal static Feature SceneDateStripFlag => new(
+ internal static FlagFeature SceneDateStripFlag => new(
SceneDateStripValue,
["-sds", "--scene-date-strip"],
"Remove date from scene-named sets",
- ParameterType.Flag,
longDescription: "If this flag is enabled, sets with \"scene\" names will have the date removed from the beginning. For example \"01.01.01-Game_Name-GROUP\" would become \"Game_Name-Group\".");
internal const string ScriptValue = "script";
- internal static Feature ScriptFlag => new(
+ internal static FlagFeature ScriptFlag => new(
ScriptValue,
["-sc", "--script"],
"Enable script mode (no clear screen)",
- ParameterType.Flag,
"For times when SabreTools is being used in a scripted environment, the user may not want the screen to be cleared every time that it is called. This flag allows the user to skip clearing the screen on run just like if the console was being redirected.");
internal const string ShortValue = "short";
- internal static Feature ShortFlag => new(
+ internal static FlagFeature ShortFlag => new(
ShortValue,
["-s", "--short"],
"Use short output names",
- ParameterType.Flag,
longDescription: "Instead of using ClrMamePro-style long names for DATs, use just the name of the folder as the name of the DAT. This can be used in conjunction with --base to output in the format of \"Original Name (Name)\" instead.");
internal const string SingleSetValue = "single-set";
- internal static Feature SingleSetFlag => new(
+ internal static FlagFeature SingleSetFlag => new(
SingleSetValue,
["-si", "--single-set"],
"All game names replaced by '!'",
- ParameterType.Flag,
longDescription: "This is useful for keeping all roms in a DAT in the same archive or folder.");
internal const string SizeValue = "size";
- internal static Feature SizeFlag => new(
+ internal static FlagFeature SizeFlag => new(
SizeValue,
["-szs", "--size"],
"Split DAT(s) or folder by file sizes",
- ParameterType.Flag,
longDescription: "For a DAT, or set of DATs, allow for splitting based on the sizes of the files, specifically if the type is a Rom (most item types don't have sizes).");
internal const string SkipArchivesValue = "skip-archives";
- internal static Feature SkipArchivesFlag => new(
+ internal static FlagFeature SkipArchivesFlag => new(
SkipArchivesValue,
["-ska", "--skip-archives"],
"Skip all archives",
- ParameterType.Flag,
longDescription: "Skip any files that are treated like archives");
internal const string SkipFilesValue = "skip-files";
- internal static Feature SkipFilesFlag => new(
+ internal static FlagFeature SkipFilesFlag => new(
SkipFilesValue,
["-skf", "--skip-files"],
"Skip all non-archives",
- ParameterType.Flag,
longDescription: "Skip any files that are not treated like archives");
internal const string SkipFirstOutputValue = "skip-first-output";
- internal static Feature SkipFirstOutputFlag => new(
+ internal static FlagFeature SkipFirstOutputFlag => new(
SkipFirstOutputValue,
["-sf", "--skip-first-output"],
"Skip output of first DAT",
- ParameterType.Flag,
longDescription: "In times where the first DAT does not need to be written out a second time, this will skip writing it. This can often speed up the output process.");
// TODO: Should this just skip the item instead of the entire DAT?
// The rationale behind skipping the entire DAT is that if one thing is missing, likely a lot more is missing
// TDOO: Add to documentation
internal const string StrictValue = "strict";
- internal static Feature StrictFlag => new(
+ internal static FlagFeature StrictFlag => new(
StrictValue,
["-str", "--strict"],
"Enable strict DAT creation",
- ParameterType.Flag,
longDescription: "Instead of writing empty strings for null values when set as required, cancel writing the DAT entirely.");
internal const string SuperdatValue = "superdat";
- internal static Feature SuperdatFlag => new(
+ internal static FlagFeature SuperdatFlag => new(
SuperdatValue,
["-sd", "--superdat"],
"Enable SuperDAT creation",
- ParameterType.Flag,
longDescription: "Set the type flag to \"SuperDAT\" for the output DAT as well as preserving the directory structure of the inputted folder, if applicable.");
internal const string TarValue = "tar";
- internal static Feature TarFlag => new(
+ internal static FlagFeature TarFlag => new(
TarValue,
["-tar", "--tar"],
"Enable Tape ARchive output",
- ParameterType.Flag,
longDescription: "Instead of outputting the files to folder, files will be rebuilt to Tape ARchive (TAR) files. This format is a standardized storage archive without any compression, usually used with other compression formats around it. It is widely used in backup applications and source code archives.");
internal const string Torrent7zipValue = "torrent-7zip";
- internal static Feature Torrent7zipFlag => new(
+ internal static FlagFeature Torrent7zipFlag => new(
Torrent7zipValue,
["-t7z", "--torrent-7zip"],
"Enable Torrent7Zip output",
- ParameterType.Flag,
longDescription: "Instead of outputting the files to folder, files will be rebuilt to Torrent7Zip (T7Z) files. This format is based on the LZMA container format 7Zip, but with custom header information. This is currently unused by any major application. Currently does not produce proper Torrent-compatible outputs.");
internal const string TorrentGzipValue = "torrent-gzip";
- internal static Feature TorrentGzipFlag => new(
+ internal static FlagFeature TorrentGzipFlag => new(
TorrentGzipValue,
["-tgz", "--torrent-gzip"],
"Enable Torrent GZip output",
- ParameterType.Flag,
longDescription: "Instead of outputting the files to folder, files will be rebuilt to TorrentGZ (TGZ) files. This format is based on the GZip archive format, but with custom header information and a file name replaced by the SHA-1 of the file inside. This is primarily used by external tool Romba (https://github.com/uwedeportivo/romba), but may be used more widely in the future.");
internal const string TorrentZipValue = "torrent-zip";
- internal static Feature TorrentZipFlag => new(
+ internal static FlagFeature TorrentZipFlag => new(
TorrentZipValue,
["-tzip", "--torrent-zip"],
"Enable Torrent Zip output",
- ParameterType.Flag,
longDescription: "Instead of outputting files to folder, files will be rebuilt to TorrentZip (TZip) files. This format is based on the ZIP archive format, but with custom header information. This is primarily used by external tool RomVault (http://www.romvault.com/) and is already widely used.");
internal const string TotalSizeValue = "total-size";
- internal static Feature TotalSizeFlag => new(
+ internal static FlagFeature TotalSizeFlag => new(
TotalSizeValue,
["-tis", "--total-size"],
"Split DAT(s) or folder by total game sizes",
- ParameterType.Flag,
longDescription: "For a DAT, or set of DATs, allow for splitting based on the combined sizes of the games, splitting into individual chunks.");
internal const string TrimValue = "trim";
- internal static Feature TrimFlag => new(
+ internal static FlagFeature TrimFlag => new(
TrimValue,
["-trim", "--trim"],
"Trim file names to fit NTFS length",
- ParameterType.Flag,
longDescription: "In the cases where files will have too long a name, this allows for trimming the name of the files to the NTFS maximum length at most.");
internal const string TypeValue = "type";
- internal static Feature TypeFlag => new(
+ internal static FlagFeature TypeFlag => new(
TypeValue,
["-ts", "--type"],
"Split DAT(s) or folder by file types (rom/disk)",
- ParameterType.Flag,
longDescription: "For a DAT, or set of DATs, allow for splitting based on the types of the files, specifically if the type is a rom or a disk.");
internal const string UpdateDatValue = "update-dat";
- internal static Feature UpdateDatFlag => new(
+ internal static FlagFeature UpdateDatFlag => new(
UpdateDatValue,
["-ud", "--update-dat"],
"Output updated DAT to output directory",
- ParameterType.Flag,
longDescription: "Once the files that were able to rebuilt are taken care of, a DAT of the files that could not be matched will be output to the output directory.");
#endregion
@@ -666,28 +601,25 @@ namespace SabreTools.Features
#region Int32 features
internal const string DepotDepthInt32Value = "depot-depth";
- internal static Feature DepotDepthInt32Input => new(
+ internal static Int32Feature DepotDepthInt32Input => new(
DepotDepthInt32Value,
["-depd", "--depot-depth"],
"Set depth of depot for inputs",
- ParameterType.Int32,
longDescription: "Optionally, set the depth of input depots. Defaults to 4 deep otherwise.");
internal const string RombaDepthInt32Value = "romba-depth";
- internal static Feature RombaDepthInt32Input => new(
+ internal static Int32Feature RombaDepthInt32Input => new(
RombaDepthInt32Value,
["-depr", "--romba-depth"],
"Set depth of depot for outputs",
- ParameterType.Int32,
longDescription: "Optionally, set the depth of output depots. Defaults to 4 deep otherwise.");
#if NET452_OR_GREATER || NETCOREAPP
internal const string ThreadsInt32Value = "threads";
- internal static Feature ThreadsInt32Input => new(
+ internal static Int32Feature ThreadsInt32Input => new(
ThreadsInt32Value,
["-mt", "--threads"],
"Amount of threads to use (default = # cores)",
- ParameterType.Int32,
longDescription: "Optionally, set the number of threads to use for the multithreaded operations. The default is the number of available machine threads; -1 means unlimited threads created.");
#endif
@@ -696,19 +628,17 @@ namespace SabreTools.Features
#region Int64 features
internal const string ChunkSizeInt64Value = "chunk-size";
- internal static Feature ChunkSizeInt64Input => new(
+ internal static Int64Feature ChunkSizeInt64Input => new(
ChunkSizeInt64Value,
["-cs", "--chunk-size"],
"Set a chunk size to output",
- ParameterType.Int64,
longDescription: "Set the total game size to cut off at for each chunked DAT. It is recommended to use a sufficiently large size such as 1GB or else you may run into issues, especially if a single game could be larger than the size provided.");
internal const string RadixInt64Value = "radix";
- internal static Feature RadixInt64Input => new(
+ internal static Int64Feature RadixInt64Input => new(
RadixInt64Value,
["-rad", "--radix"],
"Set the midpoint to split at",
- ParameterType.Int64,
longDescription: "Set the size at which all roms less than the size are put in the first DAT, and everything greater than or equal goes in the second.");
#endregion
@@ -716,67 +646,59 @@ namespace SabreTools.Features
#region List features
internal const string BaseDatListValue = "base-dat";
- internal static Feature BaseDatListInput => new(
+ internal static ListFeature BaseDatListInput => new(
BaseDatListValue,
["-bd", "--base-dat"],
"Add a base DAT for processing",
- ParameterType.List,
longDescription: "Add a DAT or folder of DATs to the base set to be used for all operations. Multiple instances of this flag are allowed.");
internal const string DatListValue = "dat";
- internal static Feature DatListInput => new(
+ internal static ListFeature DatListInput => new(
DatListValue,
["-dat", "--dat"],
"Input DAT to be used",
- ParameterType.List,
longDescription: "User-supplied DAT for use in all operations. Multiple instances of this flag are allowed.");
internal const string ExcludeFieldListValue = "exclude-field";
- internal static Feature ExcludeFieldListInput => new(
+ internal static ListFeature ExcludeFieldListInput => new(
ExcludeFieldListValue,
["-ef", "--exclude-field"],
"Exclude a game/rom field from outputs",
- ParameterType.List,
longDescription: "Exclude any valid item or machine field from outputs. Examples include: romof, publisher, and offset.");
internal const string ExtAListValue = "exta";
- internal static Feature ExtaListInput => new(
+ internal static ListFeature ExtaListInput => new(
ExtAListValue,
["-exta", "--exta"],
"Set extension to be included in first DAT",
- ParameterType.List,
longDescription: "Set the extension to be used to populate the first DAT. Multiple instances of this flag are allowed.");
internal const string ExtBListValue = "extb";
- internal static Feature ExtbListInput => new(
+ internal static ListFeature ExtbListInput => new(
ExtBListValue,
["-extb", "--extb"],
"Set extension to be included in second DAT",
- ParameterType.List,
longDescription: "Set the extension to be used to populate the second DAT. Multiple instances of this flag are allowed.");
internal const string ExtraIniListValue = "extra-ini";
- internal static Feature ExtraIniListInput => new(
+ internal static ListFeature ExtraIniListInput => new(
ExtraIniListValue,
["-ini", "--extra-ini"],
"Apply a MAME INI for given field(s)",
- ParameterType.List,
longDescription: "Apply any valid MAME INI for any valid field in the DatFile. Inputs are of the form 'Field:path\\to\\ini'. Multiple instances of this flag are allowed.");
internal const string FilterListValue = "filter";
- internal static Feature FilterListInput => new(
+ internal static ListFeature FilterListInput => new(
FilterListValue,
["-fi", "--filter"],
"Filter a game/rom field with the given value(s)",
- ParameterType.List,
longDescription: "Filter any valid item or machine field from inputs. Filters are input in the form 'type.key=value' or 'type.key!=value', where the '!' signifies 'not matching'. Numeric values may also use extra operations, namely '>', '>=', '<', and '<='. Key examples include: item.romof, machine.category, and game.name. Additionally, the user can specify an exact match or full C#-style regex for pattern matching. Multiple instances of this flag are allowed.");
internal const string OutputTypeListValue = "output-type";
- internal static Feature OutputTypeListInput => new(
+ internal static ListFeature OutputTypeListInput => new(
OutputTypeListValue,
["-ot", "--output-type"],
"Output DATs to a specified format",
- ParameterType.List,
longDescription: @"Add outputting the created DAT to known format. Multiple instances of this flag are allowed.
Possible values are:
@@ -810,19 +732,17 @@ Possible values are:
xml, logiqx - Logiqx XML");
internal const string RegionListValue = "region";
- internal static Feature RegionListInput => new(
+ internal static ListFeature RegionListInput => new(
RegionListValue,
["-reg", "--region"],
"Add a region for 1G1R",
- ParameterType.List,
longDescription: "Add a region (in order) for use with 1G1R filtering. If this is not supplied, then by default, only parent sets will be included in the output. Multiple instances of this flag are allowed.");
internal const string ReportTypeListValue = "report-type";
- internal static Feature ReportTypeListInput => new(
+ internal static ListFeature ReportTypeListInput => new(
ReportTypeListValue,
["-srt", "--report-type"],
"Output statistics to a specified format",
- ParameterType.List,
longDescription: @"Add outputting the created DAT to known format. Multiple instances of this flag are allowed.
Possible values are:
@@ -834,11 +754,10 @@ Possible values are:
tsv - Standardized Tab-Separated Value");
internal const string UpdateFieldListValue = "update-field";
- internal static Feature UpdateFieldListInput => new(
+ internal static ListFeature UpdateFieldListInput => new(
UpdateFieldListValue,
["-uf", "--update-field"],
"Update a game/rom field from base DATs",
- ParameterType.List,
longDescription: "Update any valid item or machine field from base DAT(s). Examples include: romof, publisher, and offset.");
#endregion
@@ -846,143 +765,126 @@ Possible values are:
#region String features
internal const string AddExtensionStringValue = "add-extension";
- internal static Feature AddExtensionStringInput => new(
+ internal static StringFeature AddExtensionStringInput => new(
AddExtensionStringValue,
["-ae", "--add-extension"],
"Add an extension to each item",
- ParameterType.String,
longDescription: "Add a postfix extension to each full item name.");
internal const string AuthorStringValue = "author";
- internal static Feature AuthorStringInput => new(
+ internal static StringFeature AuthorStringInput => new(
AuthorStringValue,
["-au", "--author"],
"Set the author of the DAT",
- ParameterType.String,
longDescription: "Set the author header field for the output DAT(s)");
internal const string CategoryStringValue = "category";
- internal static Feature CategoryStringInput => new(
+ internal static StringFeature CategoryStringInput => new(
CategoryStringValue,
["-c", "--category"],
"Set the category of the DAT",
- ParameterType.String,
longDescription: "Set the category header field for the output DAT(s)");
internal const string CommentStringValue = "comment";
- internal static Feature CommentStringInput => new(
+ internal static StringFeature CommentStringInput => new(
CommentStringValue,
["-co", "--comment"],
"Set a new comment of the DAT",
- ParameterType.String,
longDescription: "Set the comment header field for the output DAT(s)");
internal const string DateStringValue = "date";
- internal static Feature DateStringInput => new(
+ internal static StringFeature DateStringInput => new(
DateStringValue,
["-da", "--date"],
"Set a new date",
- ParameterType.String,
longDescription: "Set the date header field for the output DAT(s)");
internal const string DescriptionStringValue = "description";
- internal static Feature DescriptionStringInput => new(
+ internal static StringFeature DescriptionStringInput => new(
DescriptionStringValue,
["-de", "--description"],
"Set the description of the DAT",
- ParameterType.String,
longDescription: "Set the description header field for the output DAT(s)");
internal const string EmailStringValue = "email";
- internal static Feature EmailStringInput => new(
+ internal static StringFeature EmailStringInput => new(
EmailStringValue,
["-em", "--email"],
"Set a new email of the DAT",
- ParameterType.String,
longDescription: "Set the email header field for the output DAT(s)");
internal const string FilenameStringValue = "filename";
- internal static Feature FilenameStringInput => new(
+ internal static StringFeature FilenameStringInput => new(
FilenameStringValue,
["-f", "--filename"],
"Set the external name of the DAT",
- ParameterType.String,
longDescription: "Set the external filename for the output DAT(s)");
internal const string ForceMergingStringValue = "forcemerging";
- internal static Feature ForceMergingStringInput => new(
+ internal static StringFeature ForceMergingStringInput => new(
ForceMergingStringValue,
["-fm", "--forcemerging"],
"Set force merging",
- ParameterType.String,
longDescription: @"Set the forcemerging tag to the given value.
Possible values are: None, Split, Device, Merged, Nonmerged, Full");
internal const string ForceNodumpStringValue = "forcenodump";
- internal static Feature ForceNodumpStringInput => new(
+ internal static StringFeature ForceNodumpStringInput => new(
ForceNodumpStringValue,
["-fn", "--forcenodump"],
"Set force nodump",
- ParameterType.String,
longDescription: @"Set the forcenodump tag to the given value.
Possible values are: None, Obsolete, Required, Ignore");
internal const string ForcePackingStringValue = "forcepacking";
- internal static Feature ForcePackingStringInput => new(
+ internal static StringFeature ForcePackingStringInput => new(
ForcePackingStringValue,
["-fp", "--forcepacking"],
"Set force packing",
- ParameterType.String,
longDescription: @"Set the forcepacking tag to the given value.
Possible values are: None, Zip, Unzip, Partial, Flat");
internal const string HeaderStringValue = "header";
- internal static Feature HeaderStringInput => new(
+ internal static StringFeature HeaderStringInput => new(
HeaderStringValue,
["-h", "--header"],
"Set a header skipper to use, blank means all",
- ParameterType.String,
longDescription: "Set the header special field for the output DAT(s). In file rebuilding, this flag allows for either all copier headers (using \"\") or specific copier headers by name (such as \"fds.xml\") to determine if a file matches or not.");
internal const string HomepageStringValue = "homepage";
- internal static Feature HomepageStringInput => new(
+ internal static StringFeature HomepageStringInput => new(
HomepageStringValue,
["-hp", "--homepage"],
"Set a new homepage of the DAT",
- ParameterType.String,
longDescription: "Set the homepage header field for the output DAT(s)");
internal const string LogLevelStringValue = "log-level";
- internal static Feature LogLevelStringInput => new(
+ internal static StringFeature LogLevelStringInput => new(
LogLevelStringValue,
["-ll", "--log-level"],
"Set the lowest log level for output",
- ParameterType.String,
longDescription: @"Set the lowest log level for output.
Possible values are: Verbose, User, Warning, Error");
internal const string NameStringValue = "name";
- internal static Feature NameStringInput => new(
+ internal static StringFeature NameStringInput => new(
NameStringValue,
["-n", "--name"],
"Set the internal name of the DAT",
- ParameterType.String,
longDescription: "Set the name header field for the output DAT(s)");
internal const string OutputDirStringValue = "output-dir";
- internal static Feature OutputDirStringInput => new(
+ internal static StringFeature OutputDirStringInput => new(
OutputDirStringValue,
["-out", "--output-dir"],
"Set output directory",
- ParameterType.String,
longDescription: "This sets an output folder to be used when the files are created. If a path is not defined, the runtime directory is used instead.");
internal const string PostfixStringValue = "postfix";
- internal static Feature PostfixStringInput => new(
+ internal static StringFeature PostfixStringInput => new(
PostfixStringValue,
["-post", "--postfix"],
"Set postfix for all lines",
- ParameterType.String,
longDescription: @"Set a generic postfix to be appended to all outputted lines.
Some special strings that can be used:
@@ -1002,11 +904,10 @@ Some special strings that can be used:
- %size% - Replaced with the size");
internal const string PrefixStringValue = "prefix";
- internal static Feature PrefixStringInput => new(
+ internal static StringFeature PrefixStringInput => new(
PrefixStringValue,
["-pre", "--prefix"],
"Set prefix for all lines",
- ParameterType.String,
longDescription: @"Set a generic prefix to be prepended to all outputted lines.
Some special strings that can be used:
@@ -1026,43 +927,38 @@ Some special strings that can be used:
- %size% - Replaced with the size");
internal const string ReplaceExtensionStringValue = "replace-extension";
- internal static Feature ReplaceExtensionStringInput => new(
+ internal static StringFeature ReplaceExtensionStringInput => new(
ReplaceExtensionStringValue,
["-rep", "--replace-extension"],
"Replace all extensions with specified",
- ParameterType.String,
longDescription: "When an extension exists, replace it with the provided instead.");
internal const string RootStringValue = "root";
- internal static Feature RootStringInput => new(
+ internal static StringFeature RootStringInput => new(
RootStringValue,
["-r", "--root"],
"Set a new rootdir",
- ParameterType.String,
longDescription: "Set the rootdir (as used by SuperDAT mode) for the output DAT(s).");
internal const string RootDirStringValue = "root-dir";
- internal static Feature RootDirStringInput => new(
+ internal static StringFeature RootDirStringInput => new(
RootDirStringValue,
["-rd", "--root-dir"],
"Set the root directory for calc",
- ParameterType.String,
longDescription: "In the case that the files will not be stored from the root directory, a new root can be set for path length calculations.");
internal const string UrlStringValue = "url";
- internal static Feature UrlStringInput => new(
+ internal static StringFeature UrlStringInput => new(
UrlStringValue,
["-u", "--url"],
"Set a new URL of the DAT",
- ParameterType.String,
longDescription: "Set the URL header field for the output DAT(s)");
internal const string VersionStringValue = "version";
- internal static Feature VersionStringInput => new(
+ internal static StringFeature VersionStringInput => new(
VersionStringValue,
["-v", "--version"],
"Set the version of the DAT",
- ParameterType.String,
longDescription: "Set the version header field for the output DAT(s)");
#endregion
diff --git a/SabreTools/Features/Batch.cs b/SabreTools/Features/Batch.cs
index 203d667b..b0c16e7d 100644
--- a/SabreTools/Features/Batch.cs
+++ b/SabreTools/Features/Batch.cs
@@ -16,15 +16,13 @@ namespace SabreTools.Features
// TODO: Should the private classes here be split into a new namespace?
internal class Batch : BaseFeature
{
- public const string Value = "Batch";
+ public const string DisplayName = "Batch";
- public Batch()
- {
- Name = Value;
- Flags.AddRange(["bt", "batch"]);
- Description = "Enable batch mode";
- _featureType = ParameterType.Flag;
- LongDescription = @"Run a special mode that takes input files as lists of batch commands to run sequentially. Each command has to be its own line and must be followed by a semicolon (`;`). Commented lines may start with either `REM` or `#`. Multiple batch files are allowed but they will be run independently from each other.
+ private static readonly string[] _flags = ["bt", "batch"];
+
+ private const string _description = "Enable batch mode";
+
+ private const string _longDescription = @"Run a special mode that takes input files as lists of batch commands to run sequentially. Each command has to be its own line and must be followed by a semicolon (`;`). Commented lines may start with either `REM` or `#`. Multiple batch files are allowed but they will be run independently from each other.
The following commands are currently implemented:
@@ -44,6 +42,9 @@ Set the output directory: output(outdir);
Write the internal items: write([overwrite = true]);
Reset the internal state: reset();";
+ public Batch()
+ : base(DisplayName, _flags, _description, _longDescription)
+ {
// Common Features
AddCommonFeatures();
}
diff --git a/SabreTools/Features/DatFromDir.cs b/SabreTools/Features/DatFromDir.cs
index 63958e64..71929d25 100644
--- a/SabreTools/Features/DatFromDir.cs
+++ b/SabreTools/Features/DatFromDir.cs
@@ -9,16 +9,17 @@ namespace SabreTools.Features
{
internal class DatFromDir : BaseFeature
{
- public const string Value = "DATFromDir";
+ public const string DisplayName = "DATFromDir";
+
+ private static readonly string[] _flags = ["d", "d2d", "dfd"];
+
+ private const string _description = "Create DAT(s) from an input directory";
+
+ private const string _longDescription = "Create a DAT file from an input directory or set of files. By default, this will output a DAT named based on the input directory and the current date. It will also treat all archives as possible games and add all three hashes (CRC, MD5, SHA-1) for each file.";
public DatFromDir()
+ : base(DisplayName, _flags, _description, _longDescription)
{
- Name = Value;
- Flags.AddRange(["d", "d2d", "dfd"]);
- Description = "Create DAT(s) from an input directory";
- _featureType = ParameterType.Flag;
- LongDescription = "Create a DAT file from an input directory or set of files. By default, this will output a DAT named based on the input directory and the current date. It will also treat all archives as possible games and add all three hashes (CRC, MD5, SHA-1) for each file.";
-
// Common Features
AddCommonFeatures();
diff --git a/SabreTools/Features/DisplayHelp.cs b/SabreTools/Features/DisplayHelp.cs
index ad8de686..a00bf882 100644
--- a/SabreTools/Features/DisplayHelp.cs
+++ b/SabreTools/Features/DisplayHelp.cs
@@ -4,15 +4,17 @@ namespace SabreTools.Features
{
internal class DisplayHelp : BaseFeature
{
- public const string Value = "Help";
+ public const string DisplayName = "Help";
+
+ private static readonly string[] _flags = ["?", "h", "help"];
+
+ private const string _description = "Show this help";
+
+ private const string _longDescription = "Built-in to most of the programs is a basic help text.";
public DisplayHelp()
+ : base(DisplayName, _flags, _description, _longDescription)
{
- Name = Value;
- Flags.AddRange(["?", "h", "help"]);
- Description = "Show this help";
- _featureType = ParameterType.Flag;
- LongDescription = "Built-in to most of the programs is a basic help text.";
}
public override bool ProcessArgs(string[] args, FeatureSet help)
diff --git a/SabreTools/Features/DisplayHelpDetailed.cs b/SabreTools/Features/DisplayHelpDetailed.cs
index c52e0070..015c789d 100644
--- a/SabreTools/Features/DisplayHelpDetailed.cs
+++ b/SabreTools/Features/DisplayHelpDetailed.cs
@@ -4,15 +4,17 @@ namespace SabreTools.Features
{
internal class DisplayHelpDetailed : BaseFeature
{
- public const string Value = "Help (Detailed)";
+ public const string DisplayName = "Help (Detailed)";
+
+ private static readonly string[] _flags = ["??", "hd", "help-detailed"];
+
+ private const string _description = "Show this detailed help";
+
+ private const string _longDescription = "Display a detailed help text to the screen.";
public DisplayHelpDetailed()
+ : base(DisplayName, _flags, _description, _longDescription)
{
- Name = Value;
- Flags.AddRange(["??", "hd", "help-detailed"]);
- Description = "Show this detailed help";
- _featureType = ParameterType.Flag;
- LongDescription = "Display a detailed help text to the screen.";
}
public override bool ProcessArgs(string[] args, FeatureSet help)
diff --git a/SabreTools/Features/Sort.cs b/SabreTools/Features/Sort.cs
index 0187c01f..de9c5896 100644
--- a/SabreTools/Features/Sort.cs
+++ b/SabreTools/Features/Sort.cs
@@ -11,16 +11,17 @@ namespace SabreTools.Features
{
internal class Sort : BaseFeature
{
- public const string Value = "Sort";
+ public const string DisplayName = "Sort";
+
+ private static readonly string[] _flags = ["ss", "sort"];
+
+ private const string _description = "Sort inputs by a set of DATs";
+
+ private const string _longDescription = "This feature allows the user to quickly rebuild based on a supplied DAT file(s). By default all files will be rebuilt to uncompressed folders in the output directory.";
public Sort()
+ : base(DisplayName, _flags, _description, _longDescription)
{
- Name = Value;
- Flags.AddRange(["ss", "sort"]);
- Description = "Sort inputs by a set of DATs";
- _featureType = ParameterType.Flag;
- LongDescription = "This feature allows the user to quickly rebuild based on a supplied DAT file(s). By default all files will be rebuilt to uncompressed folders in the output directory.";
-
// Common Features
AddCommonFeatures();
diff --git a/SabreTools/Features/Split.cs b/SabreTools/Features/Split.cs
index d96d588e..9d9be53d 100644
--- a/SabreTools/Features/Split.cs
+++ b/SabreTools/Features/Split.cs
@@ -12,16 +12,17 @@ namespace SabreTools.Features
{
internal class Split : BaseFeature
{
- public const string Value = "Split";
+ public const string DisplayName = "Split";
+
+ private static readonly string[] _flags = ["sp", "split"];
+
+ private const string _description = "Split input DATs by a given criteria";
+
+ private const string _longDescription = "This feature allows the user to split input DATs by a number of different possible criteria. See the individual input information for details. More than one split type is allowed at a time.";
public Split()
+ : base(DisplayName, _flags, _description, _longDescription)
{
- Name = Value;
- Flags.AddRange(["sp", "split"]);
- Description = "Split input DATs by a given criteria";
- _featureType = ParameterType.Flag;
- LongDescription = "This feature allows the user to split input DATs by a number of different possible criteria. See the individual input information for details. More than one split type is allowed at a time.";
-
// Common Features
AddCommonFeatures();
diff --git a/SabreTools/Features/Stats.cs b/SabreTools/Features/Stats.cs
index 7813a1b1..49dcfe92 100644
--- a/SabreTools/Features/Stats.cs
+++ b/SabreTools/Features/Stats.cs
@@ -7,15 +7,13 @@ namespace SabreTools.Features
{
internal class Stats : BaseFeature
{
- public const string Value = "Stats";
+ public const string DisplayName = "Stats";
- public Stats()
- {
- Name = Value;
- Flags.AddRange(["st", "stats"]);
- Description = "Get statistics on all input DATs";
- _featureType = ParameterType.Flag;
- LongDescription = @"This will output by default the combined statistics for all input DAT files.
+ private static readonly string[] _flags = ["st", "stats"];
+
+ private const string _description = "Get statistics on all input DATs";
+
+ private const string _longDescription = @"This will output by default the combined statistics for all input DAT files.
The stats that are outputted are as follows:
- Total uncompressed size
@@ -30,6 +28,9 @@ The stats that are outputted are as follows:
- Items that include a SHA-512
- Items with Nodump status";
+ public Stats()
+ : base(DisplayName, _flags, _description, _longDescription)
+ {
// Common Features
AddCommonFeatures();
diff --git a/SabreTools/Features/Update.cs b/SabreTools/Features/Update.cs
index 5201c065..0c04c169 100644
--- a/SabreTools/Features/Update.cs
+++ b/SabreTools/Features/Update.cs
@@ -14,16 +14,17 @@ namespace SabreTools.Features
{
internal class Update : BaseFeature
{
- public const string Value = "Update";
+ public const string DisplayName = "Update";
+
+ private static readonly string[] _flags = ["ud", "update"];
+
+ private const string _description = "Update and manipulate DAT(s)";
+
+ private const string _longDescription = "This is the multitool part of the program, allowing for almost every manipulation to a DAT, or set of DATs. This is also a combination of many different programs that performed DAT manipulation that work better together.";
public Update()
+ : base(DisplayName, _flags, _description, _longDescription)
{
- Name = Value;
- Flags.AddRange(["ud", "update"]);
- Description = "Update and manipulate DAT(s)";
- _featureType = ParameterType.Flag;
- LongDescription = "This is the multitool part of the program, allowing for almost every manipulation to a DAT, or set of DATs. This is also a combination of many different programs that performed DAT manipulation that work better together.";
-
// Common Features
AddCommonFeatures();
diff --git a/SabreTools/Features/Verify.cs b/SabreTools/Features/Verify.cs
index be710226..4cc36aad 100644
--- a/SabreTools/Features/Verify.cs
+++ b/SabreTools/Features/Verify.cs
@@ -10,16 +10,17 @@ namespace SabreTools.Features
{
internal class Verify : BaseFeature
{
- public const string Value = "Verify";
+ public const string DisplayName = "Verify";
+
+ private static readonly string[] _flags = ["ve", "verify"];
+
+ private const string _description = "Verify a folder against DATs";
+
+ private const string _longDescription = "When used, this will use an input DAT or set of DATs to blindly check against an input folder. The base of the folder is considered the base for the combined DATs and games are either the directories or archives within. This will only do a direct verification of the items within and will create a fixdat afterwards for missing files.";
public Verify()
+ : base(DisplayName, _flags, _description, _longDescription)
{
- Name = Value;
- Flags.AddRange(["ve", "verify"]);
- Description = "Verify a folder against DATs";
- _featureType = ParameterType.Flag;
- LongDescription = "When used, this will use an input DAT or set of DATs to blindly check against an input folder. The base of the folder is considered the base for the combined DATs and games are either the directories or archives within. This will only do a direct verification of the items within and will create a fixdat afterwards for missing files.";
-
// Common Features
AddCommonFeatures();
diff --git a/SabreTools/Features/Version.cs b/SabreTools/Features/Version.cs
index 6b7c7a29..4d04be73 100644
--- a/SabreTools/Features/Version.cs
+++ b/SabreTools/Features/Version.cs
@@ -6,16 +6,17 @@ namespace SabreTools.Features
{
internal class Version : BaseFeature
{
- public const string Value = "Version";
+ public const string DisplayName = "Version";
+
+ private static readonly string[] _flags = ["v", "version"];
+
+ private const string _description = "Prints version";
+
+ private const string _longDescription = "Prints current program version.";
public Version()
+ : base(DisplayName, _flags, _description, _longDescription)
{
- Name = Value;
- Flags.AddRange(["v", "version"]);
- Description = "Prints version";
- _featureType = ParameterType.Flag;
- LongDescription = "Prints current program version.";
-
// Common Features
AddCommonFeatures();
}
diff --git a/SabreTools/Program.cs b/SabreTools/Program.cs
index 05d86a74..baa4ca0b 100644
--- a/SabreTools/Program.cs
+++ b/SabreTools/Program.cs
@@ -92,7 +92,7 @@ namespace SabreTools
BaseFeature feature = (_help[featureName] as BaseFeature)!;
// If we had the help feature first
- if (featureName == DisplayHelp.Value || featureName == DisplayHelpDetailed.Value)
+ if (featureName == DisplayHelp.DisplayName || featureName == DisplayHelpDetailed.DisplayName)
{
feature.ProcessArgs(args, _help);
LoggerImpl.Close();
@@ -124,24 +124,24 @@ namespace SabreTools
switch (featureName)
{
// No-op as these should be caught
- case DisplayHelp.Value:
- case DisplayHelpDetailed.Value:
+ case DisplayHelp.DisplayName:
+ case DisplayHelpDetailed.DisplayName:
break;
// Require input verification
- case Batch.Value:
- case DatFromDir.Value:
- case Split.Value:
- case Stats.Value:
- case Update.Value:
- case Verify.Value:
+ case Batch.DisplayName:
+ case DatFromDir.DisplayName:
+ case Split.DisplayName:
+ case Stats.DisplayName:
+ case Update.DisplayName:
+ case Verify.DisplayName:
VerifyInputs(feature.Inputs, feature);
success = feature.ProcessFeatures(features);
break;
// Requires no input verification
- case Sort.Value:
- case Features.Version.Value:
+ case Sort.DisplayName:
+ case Features.Version.DisplayName:
success = feature.ProcessFeatures(features);
break;