Files
SabreTools/SabreTools.Help/Feature.cs

573 lines
21 KiB
C#
Raw Normal View History

2020-12-07 12:33:24 -08:00
using System;
using System.Collections.Generic;
using System.Linq;
2020-06-11 11:44:46 -07:00
2020-12-07 13:57:26 -08:00
namespace SabreTools.Help
{
2019-02-08 13:47:44 -08:00
public class Feature
{
#region Protected instance variables
2019-02-08 13:47:44 -08:00
2020-12-07 13:57:26 -08:00
protected ParameterType _featureType;
protected bool _foundOnce = false;
2024-02-28 19:19:50 -05:00
protected object? _value = null;
2019-02-08 13:47:44 -08:00
#endregion
#region Publicly facing variables
2024-02-28 19:19:50 -05:00
public string? Name { get; protected set; }
2024-07-18 00:32:41 -04:00
public readonly List<string> Flags = [];
2024-02-28 19:19:50 -05:00
public string? Description { get; protected set; }
public string? LongDescription { get; protected set; }
2024-07-18 00:32:41 -04:00
public readonly Dictionary<string, Feature?> Features = [];
2019-02-08 13:47:44 -08:00
#endregion
#region Constructors
2024-07-18 01:15:06 -04:00
/// <summary>
/// Only used by inheriting classes
/// </summary>
protected Feature()
{
}
2024-02-28 19:19:50 -05:00
public Feature(string name, string flag, string description, ParameterType featureType, string? longDescription = null)
2019-02-08 13:47:44 -08:00
{
2024-07-18 00:32:41 -04:00
_featureType = featureType;
2024-07-18 00:23:16 -04:00
Name = name;
2024-07-18 00:32:41 -04:00
Flags.Add(flag);
2024-07-18 00:23:16 -04:00
Description = description;
LongDescription = longDescription;
2019-02-08 13:47:44 -08:00
}
2024-02-28 19:19:50 -05:00
public Feature(string name, List<string> flags, string description, ParameterType featureType, string? longDescription = null)
2019-02-08 13:47:44 -08:00
{
2024-07-18 00:32:41 -04:00
_featureType = featureType;
2024-07-18 00:23:16 -04:00
Name = name;
2024-07-18 00:32:41 -04:00
Flags.AddRange(flags);
2024-07-18 00:23:16 -04:00
Description = description;
LongDescription = longDescription;
2019-02-08 13:47:44 -08:00
}
#endregion
#region Accessors
/// <summary>
/// Directly address a given subfeature
/// </summary>
2024-02-28 19:19:50 -05:00
public Feature? this[string name]
2019-02-08 13:47:44 -08:00
{
2024-07-18 00:23:16 -04:00
get { return Features.ContainsKey(name) ? Features[name] : null; }
set { Features[name] = value; }
2019-02-08 13:47:44 -08:00
}
/// <summary>
/// Directly address a given subfeature
/// </summary>
2024-02-28 19:19:50 -05:00
public Feature? this[Feature? subfeature]
2019-02-08 13:47:44 -08:00
{
2024-07-18 00:23:16 -04:00
get { return Features.ContainsKey(subfeature?.Name ?? string.Empty) ? Features[subfeature?.Name ?? string.Empty] : null; }
set { Features[subfeature?.Name ?? string.Empty] = value; }
2019-02-08 13:47:44 -08:00
}
/// <summary>
/// Add a new feature for this feature
/// </summary>
/// <param name="feature"></param>
public void AddFeature(Feature feature)
{
2024-07-18 00:23:16 -04:00
lock (Features)
2019-02-08 13:47:44 -08:00
{
2024-07-18 00:23:16 -04:00
Features[feature.Name ?? string.Empty] = feature;
2019-02-08 13:47:44 -08:00
}
}
/// <summary>
/// Add a new flag for this feature
/// </summary>
/// <param name="flag">Flag to add for this feature</param>
public void AddFlag(string flag)
{
2024-07-18 00:23:16 -04:00
lock (Flags)
2019-02-08 13:47:44 -08:00
{
2024-07-18 00:23:16 -04:00
Flags.Add(flag);
2019-02-08 13:47:44 -08:00
}
}
/// <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)
{
2024-07-18 00:23:16 -04:00
lock (Flags)
2019-02-08 13:47:44 -08:00
{
2024-07-18 00:23:16 -04:00
Flags.AddRange(flags);
2019-02-08 13:47:44 -08:00
}
}
/// <summary>
/// Returns if a flag exists for the current feature
/// </summary>
/// <param name="name">Name of the flag to check</param>
/// <returns>True if the flag was found, false otherwise</returns>
public bool ContainsFlag(string name)
{
2024-07-18 00:23:16 -04:00
return Flags.Any(f => f == name || f.TrimStart('-') == name);
2019-02-08 13:47:44 -08:00
}
/// <summary>
/// Returns if the feature contains a flag that starts with the given character
/// </summary>
/// <param name="c">Character to check against</param>
/// <returns>True if the flag was found, false otherwise</returns>
public bool StartsWith(char c)
{
2024-07-18 00:23:16 -04:00
return Flags.Any(f => f.TrimStart('-').ToLowerInvariant()[0] == c);
2019-02-08 13:47:44 -08:00
}
#endregion
#region Instance Methods
/// <summary>
/// Output this feature only
/// </summary>
/// <param name="pre">Positive number representing number of spaces to put in front of the feature</param>
/// <param name="midpoint">Positive number representing the column where the description should start</param>
/// <param name="includeLongDescription">True if the long description should be formatted and output, false otherwise</param>
public List<string> Output(int pre = 0, int midpoint = 0, bool includeLongDescription = false)
{
// Create the output list
2024-02-28 19:19:50 -05:00
List<string> outputList = [];
2019-02-08 13:47:44 -08:00
// Build the output string first
string output = string.Empty;
2019-02-08 13:47:44 -08:00
// Add the pre-space first
output += CreatePadding(pre);
// Preprocess the flags, if necessary
2024-07-18 00:23:16 -04:00
string[] newflags = new string[Flags.Count];
Flags.CopyTo(newflags);
2019-02-08 13:47:44 -08:00
switch (_featureType)
{
2020-12-07 13:57:26 -08:00
case ParameterType.Int32:
case ParameterType.Int64:
case ParameterType.List:
case ParameterType.String:
2019-02-08 13:47:44 -08:00
for (int i = 0; i < newflags.Length; i++)
{
newflags[i] += "=";
}
break;
2020-12-07 13:57:26 -08:00
case ParameterType.Flag:
2019-02-08 13:47:44 -08:00
default:
// No-op
break;
}
// Now add all flags
output += string.Join(", ", newflags);
2019-02-08 13:47:44 -08:00
// 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);
else
output += " ";
// Append the description
2024-07-18 00:23:16 -04:00
output += Description;
2019-02-08 13:47:44 -08:00
// Now append it to the list
outputList.Add(output);
// 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;
// Prepare the output string
output = CreatePadding(pre + 4);
// Now split the input description and start processing
2024-07-18 00:23:16 -04:00
string[]? split = LongDescription?.Split(' ');
2024-02-28 19:19:50 -05:00
if (split == null)
return outputList;
2019-02-08 13:47:44 -08:00
for (int i = 0; i < split.Length; i++)
{
// If we have a newline character, reset the line and continue
if (split[i].Contains('\n'))
2019-02-08 13:47:44 -08:00
{
string[] subsplit = split[i].Replace("\r", string.Empty).Split('\n');
2019-02-08 13:47:44 -08:00
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)
{
output += (output.Length == pre + 4 ? string.Empty : " ") + subsplit[j];
2019-02-08 13:47:44 -08:00
}
// 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];
2019-02-08 13:47:44 -08:00
}
outputList.Add(output);
output = CreatePadding(pre + 4);
}
2024-02-28 23:14:21 -05:00
output += subsplit[subsplit.Length - 1];
2019-02-08 13:47:44 -08:00
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)
{
output += (output.Length == pre + 4 ? string.Empty : " ") + split[i];
2019-02-08 13:47:44 -08:00
}
// 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];
2019-02-08 13:47:44 -08:00
}
}
// Add the last created output and a blank line for clarity
outputList.Add(output);
outputList.Add(string.Empty);
2019-02-08 13:47:44 -08:00
}
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)
2019-02-08 13:47:44 -08:00
{
return string.Empty.PadRight(spaces);
2019-02-08 13:47:44 -08:00
}
/// <summary>
/// Output this feature and all subfeatures
/// </summary>
/// <param name="tabLevel">Level of indentation for this feature</param>
/// <param name="pre">Positive number representing number of spaces to put in front of the feature</param>
/// <param name="midpoint">Positive number representing the column where the description should start</param>
/// <param name="includeLongDescription">True if the long description should be formatted and output, false otherwise</param>
public List<string> OutputRecursive(int tabLevel, int pre = 0, int midpoint = 0, bool includeLongDescription = false)
{
// Create the output list
2024-07-18 00:32:41 -04:00
List<string> outputList = [];
2019-02-08 13:47:44 -08:00
// Build the output string first
string output = string.Empty;
2019-02-08 13:47:44 -08:00
// Normalize based on the tab level
int preAdjusted = pre;
int midpointAdjusted = midpoint;
if (tabLevel > 0)
{
preAdjusted += 4 * tabLevel;
midpointAdjusted += 4 * tabLevel;
}
// Add the pre-space first
output += CreatePadding(preAdjusted);
// Preprocess the flags, if necessary
2024-07-18 00:23:16 -04:00
string[] newflags = new string[Flags.Count];
Flags.CopyTo(newflags);
2019-02-08 13:47:44 -08:00
switch (_featureType)
{
2020-12-07 13:57:26 -08:00
case ParameterType.Int32:
case ParameterType.Int64:
case ParameterType.List:
case ParameterType.String:
2019-02-08 13:47:44 -08:00
for (int i = 0; i < newflags.Length; i++)
{
newflags[i] += "=";
}
break;
2020-12-07 13:57:26 -08:00
case ParameterType.Flag:
2019-02-08 13:47:44 -08:00
default:
// No-op
break;
}
// Now add all flags
output += string.Join(", ", newflags);
2019-02-08 13:47:44 -08:00
// 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);
else
output += " ";
// Append the description
2024-07-18 00:23:16 -04:00
output += Description;
2019-02-08 13:47:44 -08:00
// Now append it to the list
outputList.Add(output);
// 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;
// Prepare the output string
output = CreatePadding(preAdjusted + 4);
// Now split the input description and start processing
2024-07-18 00:23:16 -04:00
string[]? split = LongDescription?.Split(' ');
2024-02-28 19:19:50 -05:00
if (split == null)
return outputList;
2019-02-08 13:47:44 -08:00
for (int i = 0; i < split.Length; i++)
{
// If we have a newline character, reset the line and continue
if (split[i].Contains('\n'))
2019-02-08 13:47:44 -08:00
{
string[] subsplit = split[i].Replace("\r", string.Empty).Split('\n');
2019-02-08 13:47:44 -08:00
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)
{
output += (output.Length == preAdjusted + 4 ? string.Empty : " ") + subsplit[j];
2019-02-08 13:47:44 -08:00
}
// 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];
2019-02-08 13:47:44 -08:00
}
outputList.Add(output);
output = CreatePadding(preAdjusted + 4);
}
2024-02-28 23:14:21 -05:00
output += subsplit[subsplit.Length - 1];
2019-02-08 13:47:44 -08:00
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)
{
output += (output.Length == preAdjusted + 4 ? string.Empty : " ") + split[i];
2019-02-08 13:47:44 -08:00
}
// 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];
2019-02-08 13:47:44 -08:00
}
}
// Add the last created output and a blank line for clarity
outputList.Add(output);
outputList.Add(string.Empty);
2019-02-08 13:47:44 -08:00
}
// Now let's append all subfeatures
2024-07-18 00:23:16 -04:00
foreach (string feature in Features.Keys)
2019-02-08 13:47:44 -08:00
{
2024-07-18 00:23:16 -04:00
outputList.AddRange(Features[feature]!.OutputRecursive(tabLevel + 1, pre, midpoint, includeLongDescription));
2019-02-08 13:47:44 -08:00
}
return outputList;
}
/// <summary>
/// Validate whether a flag is valid for this feature or not
/// </summary>
/// <param name="input">Input to check against</param>
/// <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;
// 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
2020-12-07 13:57:26 -08:00
case ParameterType.Flag:
2024-07-18 00:23:16 -04:00
valid = !input.Contains('=') && Flags.Contains(input);
2019-02-08 13:47:44 -08:00
if (valid)
{
_value = true;
2019-02-08 13:47:44 -08:00
// If we've already found this feature before
if (_foundOnce && !ignore)
valid = false;
_foundOnce = true;
}
2019-02-08 13:47:44 -08:00
break;
2019-02-08 13:47:44 -08:00
// If we have an Int32, try to parse it if at all possible
2020-12-07 13:57:26 -08:00
case ParameterType.Int32:
2024-07-18 00:23:16 -04:00
valid = input.Contains('=') && Flags.Contains(input.Split('=')[0]);
2019-02-08 13:47:44 -08:00
if (valid)
{
if (!Int32.TryParse(input.Split('=')[1], out int value))
value = Int32.MinValue;
_value = value;
2019-02-08 13:47:44 -08:00
// If we've already found this feature before
if (_foundOnce && !ignore)
valid = false;
_foundOnce = true;
}
2019-02-08 13:47:44 -08:00
break;
2019-02-08 13:47:44 -08:00
// If we have an Int32, try to parse it if at all possible
2020-12-07 13:57:26 -08:00
case ParameterType.Int64:
2024-07-18 00:23:16 -04:00
valid = input.Contains('=') && Flags.Contains(input.Split('=')[0]);
2019-02-08 13:47:44 -08:00
if (valid)
{
if (!Int64.TryParse(input.Split('=')[1], out long value))
value = Int64.MinValue;
_value = value;
2019-02-08 13:47:44 -08:00
// If we've already found this feature before
if (_foundOnce && !ignore)
valid = false;
_foundOnce = true;
}
2019-02-08 13:47:44 -08:00
break;
2019-02-08 13:47:44 -08:00
// If we have an input, make sure it has an equals sign in it
2020-12-07 13:57:26 -08:00
case ParameterType.List:
2024-07-18 00:23:16 -04:00
valid = input.Contains('=') && Flags.Contains(input.Split('=')[0]);
2019-02-08 13:47:44 -08:00
if (valid)
{
_value ??= new List<string>();
2024-02-28 19:19:50 -05:00
(_value as List<string>)?.Add(input.Split('=')[1]);
2019-02-08 13:47:44 -08:00
}
2019-02-08 13:47:44 -08:00
break;
2020-12-07 13:57:26 -08:00
case ParameterType.String:
2024-07-18 00:23:16 -04:00
valid = input.Contains('=') && Flags.Contains(input.Split('=')[0]);
2019-02-08 13:47:44 -08:00
if (valid)
{
_value = input.Split('=')[1];
2019-02-08 13:47:44 -08:00
// If we've already found this feature before
if (_foundOnce && !ignore)
valid = false;
_foundOnce = true;
}
2019-02-08 13:47:44 -08:00
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)
2024-07-18 00:23:16 -04:00
valid = Features.Keys.Any(k => Features[k]!.ValidateInput(input));
2019-02-08 13:47:44 -08:00
return valid;
}
/// <summary>
/// Get the boolean value associated with this feature
2019-02-08 13:47:44 -08:00
/// </summary>
public bool GetBoolValue()
2019-02-08 13:47:44 -08:00
{
2020-12-07 13:57:26 -08:00
if (_featureType != ParameterType.Flag)
throw new ArgumentException("Feature is not a flag");
2019-02-08 13:47:44 -08:00
return (_value as bool?) ?? false;
}
/// <summary>
/// Get the string value associated with this feature
/// </summary>
2024-03-11 16:26:28 -04:00
public string? GetStringFieldValue()
{
2020-12-07 13:57:26 -08:00
if (_featureType != ParameterType.String)
throw new ArgumentException("Feature is not a string");
2024-02-28 19:19:50 -05:00
return _value as string;
}
/// <summary>
/// Get the Int32 value associated with this feature
/// </summary>
public int GetInt32Value()
{
2020-12-07 13:57:26 -08:00
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()
{
2020-12-07 13:57:26 -08:00
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()
{
2020-12-07 13:57:26 -08:00
if (_featureType != ParameterType.List)
throw new ArgumentException("Feature is not a list");
2024-02-28 19:19:50 -05:00
return (_value as List<string>) ?? [];
2019-02-08 13:47:44 -08:00
}
/// <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()
{
return _featureType switch
{
2020-12-07 13:57:26 -08:00
ParameterType.Flag => (_value as bool?) == true,
ParameterType.String => (_value as string) != null,
2024-02-28 19:19:50 -05:00
ParameterType.Int32 => (_value as int?).HasValue && (_value as int?)!.Value != int.MinValue,
ParameterType.Int64 => (_value as long?).HasValue && (_value as long?)!.Value != long.MinValue,
2020-12-07 13:57:26 -08:00
ParameterType.List => (_value as List<string>) != null,
_ => false,
};
2019-02-08 13:47:44 -08:00
}
#endregion
}
}