Fix edge-case issues with Help functionality; modernize features

This commit is contained in:
Matt Nadareski
2025-04-18 11:54:59 -04:00
parent a59de70a4f
commit 9f42399165
21 changed files with 742 additions and 653 deletions

View File

@@ -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)}|";

View File

@@ -1,14 +0,0 @@
namespace SabreTools.Help
{
/// <summary>
/// Determines the parameter type to check for
/// </summary>
public enum ParameterType
{
Flag = 0,
String,
Int32,
Int64,
List,
}
}

View File

@@ -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;
// <summary>
/// Indicates if the feature has been seen already
/// </summary>
protected bool _foundOnce = false;
protected object? _value = null;
#endregion
#region Publicly facing variables
#region Properties
public string? Name { get; protected set; }
/// <summary>
/// Display name for the feature
/// </summary>
public string Name { get; protected set; }
/// <summary>
/// Set of flags associated with the feature
/// </summary>
public readonly List<string> Flags = [];
public string? Description { get; protected set; }
/// <summary>
/// Short description of the feature
/// </summary>
public string Description { get; protected set; }
/// <summary>
/// Optional long description of the feature
/// </summary>
public string? LongDescription { get; protected set; }
/// <summary>
/// Set of subfeatures associated with this feature
/// </summary>
public readonly Dictionary<string, Feature?> Features = [];
#endregion
#region Constructors
/// <summary>
/// Only used by inheriting classes
/// </summary>
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<string> 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
/// <summary>
/// Directly address a given subfeature
/// </summary>
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
}
}
/// <summary>
/// Add a new flag for this feature
/// </summary>
/// <param name="flag">Flag to add for this feature</param>
public void AddFlag(string flag)
{
lock (Flags)
{
Flags.Add(flag);
}
}
/// <summary>
/// Add a set of new flags for this feature
/// </summary>
/// <param name="flags">List of flags to add to this feature</param>
public void AddFlags(List<string> flags)
{
lock (Flags)
{
Flags.AddRange(flags);
}
}
/// <summary>
/// Returns if a flag exists for the current feature
/// </summary>
@@ -144,54 +132,39 @@ namespace SabreTools.Help
List<string> 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;
}
/// <summary>
/// Create a padding space based on the given length
/// </summary>
/// <param name="spaces">Number of padding spaces to add</param>
/// <returns>String with requested number of blank spaces</returns>
private static string CreatePadding(int spaces)
{
return string.Empty.PadRight(spaces);
}
/// <summary>
/// Output this feature and all subfeatures
/// </summary>
@@ -272,7 +245,7 @@ namespace SabreTools.Help
List<string> 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
/// <param name="exact">True if just this feature should be checked, false if all subfeatures are checked as well</param>
/// <param name="ignore">True if the existing flag should be ignored, false otherwise</param>
/// <returns>True if the flag was valid, false otherwise</returns>
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<string>();
(_value as List<string>)?.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;
}
/// <summary>
/// Get the boolean value associated with this feature
/// </summary>
public bool GetBoolValue()
{
if (_featureType != ParameterType.Flag)
throw new ArgumentException("Feature is not a flag");
return (_value as bool?) ?? false;
}
/// <summary>
/// Get the string value associated with this feature
/// </summary>
public string? GetStringFieldValue()
{
if (_featureType != ParameterType.String)
throw new ArgumentException("Feature is not a string");
return _value as string;
}
/// <summary>
/// Get the Int32 value associated with this feature
/// </summary>
public int GetInt32Value()
{
if (_featureType != ParameterType.Int32)
throw new ArgumentException("Feature is not an int");
return (_value as int?) ?? int.MinValue;
}
/// <summary>
/// Get the Int64 value associated with this feature
/// </summary>
public long GetInt64Value()
{
if (_featureType != ParameterType.Int64)
throw new ArgumentException("Feature is not a long");
return (_value as long?) ?? long.MinValue;
}
/// <summary>
/// Get the List\<string\> value associated with this feature
/// </summary>
public List<string> GetListValue()
{
if (_featureType != ParameterType.List)
throw new ArgumentException("Feature is not a list");
return (_value as List<string>) ?? [];
}
public abstract bool ValidateInput(string input, bool exact = false, bool ignore = false);
/// <summary>
/// Returns if this feature has a valid value or not
/// </summary>
/// <returns>True if the feature is enabled, false otherwise</returns>
public bool IsEnabled()
public abstract bool IsEnabled();
/// <summary>
/// Pre-format the flags for output
/// </summary>
protected abstract string FormatFlags();
/// <summary>
/// Create a padding space based on the given length
/// </summary>
/// <param name="spaces">Number of padding spaces to add</param>
/// <returns>String with requested number of blank spaces</returns>
private static string CreatePadding(int spaces) => string.Empty.PadRight(spaces);
#endregion
}
public abstract class Feature<T> : 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<string>) != null,
_ => false,
};
}
internal Feature(string name, string[] flags, string description, string? longDescription = null)
: base(name, flags, description, longDescription)
{
}
#endregion
#region Instance Methods
/// <inheritdoc/>
public override abstract bool ValidateInput(string input, bool exact = false, bool ignore = false);
/// <inheritdoc/>
public override abstract bool IsEnabled();
/// <inheritdoc/>
protected override abstract string FormatFlags();
#endregion
}
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Text;
namespace SabreTools.Help
{
public class FlagFeature : Feature<bool>
{
#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
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override bool IsEnabled() => Value;
/// <inheritdoc/>
protected override string FormatFlags()
{
var sb = new StringBuilder();
Flags.ForEach(flag => sb.Append($"{flag}, "));
return sb.ToString().TrimEnd(' ', ',');
}
#endregion
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Text;
namespace SabreTools.Help
{
public class Int32Feature : Feature<int>
{
#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
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override bool IsEnabled() => Value != int.MinValue;
/// <inheritdoc/>
protected override string FormatFlags()
{
var sb = new StringBuilder();
Flags.ForEach(flag => sb.Append($"{flag}=, "));
return sb.ToString().TrimEnd(' ', ',');
}
#endregion
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Text;
namespace SabreTools.Help
{
public class Int64Feature : Feature<long>
{
#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
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override bool IsEnabled() => Value != long.MinValue;
/// <inheritdoc/>
protected override string FormatFlags()
{
var sb = new StringBuilder();
Flags.ForEach(flag => sb.Append($"{flag}=, "));
return sb.ToString().TrimEnd(' ', ',');
}
#endregion
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SabreTools.Help
{
public class ListFeature : Feature<List<string>>
{
#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
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override bool IsEnabled() => Value != null;
/// <inheritdoc/>
protected override string FormatFlags()
{
var sb = new StringBuilder();
Flags.ForEach(flag => sb.Append($"{flag}=, "));
return sb.ToString().TrimEnd(' ', ',');
}
#endregion
}
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Text;
namespace SabreTools.Help
{
public class StringFeature : Feature<string>
{
#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
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override bool IsEnabled() => Value != null;
/// <inheritdoc/>
protected override string FormatFlags()
{
var sb = new StringBuilder();
Flags.ForEach(flag => sb.Append($"{flag}=, "));
return sb.ToString().TrimEnd(' ', ',');
}
#endregion
}
}

View File

@@ -8,7 +8,7 @@ namespace SabreTools.Help
/// <summary>
/// Represents an actionable top-level feature
/// </summary>
public abstract class TopLevel : Feature
public abstract class TopLevel : FlagFeature
{
#region Fields
@@ -30,10 +30,14 @@ namespace SabreTools.Help
#region Constructors
/// <summary>
/// Constructor
/// </summary>
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<string, Feature?> 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;
}
/// <summary>
@@ -120,9 +127,12 @@ namespace SabreTools.Help
protected static long GetInt64(Dictionary<string, Feature?> 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;
}
/// <summary>
@@ -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 ?? [];
}
/// <summary>
@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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)

View File

@@ -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)

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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;