Files
Libation/Source/LibationCli/Options/_OptionsBase.cs

244 lines
7.5 KiB
C#

using CommandLine;
using CsvHelper.TypeConversion;
using Dinah.Core;
using FileManager;
using LibationFileManager;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
#nullable enable
namespace LibationCli
{
public abstract class OptionsBase
{
[Option(longName: "libationFiles", HelpText = "Path to Libation Files directory")]
public DirectoryInfo? LibationFiles { get; set; }
[Option('o', "override", HelpText = "Configuration setting override [SettingName]=\"Setting_Value\"")]
public IEnumerable<OptionOverride>? SettingOverrides { get; set; }
public async Task Run()
{
if (LibationFiles?.Exists is true)
{
Environment.SetEnvironmentVariable(LibationFileManager.LibationFiles.LIBATION_FILES_DIR, LibationFiles.FullName);
}
//***********************************************//
// //
// do not use Configuration before this line //
// //
//***********************************************//
Setup.Initialize();
if (SettingOverrides is not null)
ProcessSettingsOverrides();
try
{
await ProcessAsync();
}
catch (Exception ex)
{
Environment.ExitCode = (int)ExitCode.RunTimeError;
PrintVerbUsage(
"ERROR",
"=====",
ex.Message,
"",
ex.StackTrace);
}
}
private static bool TryParseEnum(Type enumType, string? value, out object? result)
{
var values = Enum.GetNames(enumType);
if (values.Select(n => n.ToLowerInvariant()).Distinct().Count() != values.Length)
{
//Enum names must be case sensitive.
return Enum.TryParse(enumType, value, out result);
}
for (int i = 0; i < values.Length; i++)
{
if (values[i].Equals(value, StringComparison.OrdinalIgnoreCase))
{
return Enum.TryParse(enumType, values[i], out result);
}
}
result = null;
return false;
}
protected void PrintVerbUsage(params string?[] linesBeforeUsage)
{
var verb = GetType().GetCustomAttribute<VerbAttribute>()?.Name;
var helpText = new HelpVerb { HelpType = verb }.GetHelpText();
helpText.AddPreOptionsLines(linesBeforeUsage);
helpText.AddPreOptionsLine("");
helpText.AddPreOptionsLine($"{verb} Usage:");
Console.Error.WriteLine(helpText);
}
protected static void ReplaceConsoleText(TextWriter writer, int previousLength, string newText)
{
writer.Write(new string('\b', previousLength));
writer.Write(newText);
writer.Write(new string(' ', int.Max(0, previousLength - newText.Length)));
}
protected abstract Task ProcessAsync();
protected IOrderedEnumerable<PropertyInfo> GetConfigurationProperties()
=> typeof(Configuration).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(DescriptionAttribute)))
.Where(p => !p.Name.In(ExcludedSettings))
.OrderBy(p => p.PropertyType.IsEnum)
.ThenBy(p => p.PropertyType.Name)
.ThenBy(p => p.Name);
private readonly string[] ExcludedSettings = [
nameof(Configuration.LibationFiles),
nameof(Configuration.GridScaleFactor),
nameof(Configuration.GridFontScaleFactor),
nameof(Configuration.GridColumnsVisibilities),
nameof(Configuration.GridColumnsDisplayIndices),
nameof(Configuration.GridColumnsWidths)];
private void ProcessSettingsOverrides()
{
var configProperties = GetConfigurationProperties().ToArray();
foreach (var option in SettingOverrides?.Where(p => p.Property is not null && p.Value is not null) ?? [])
{
if (option.Property?.StartsWithInsensitive(ReplacePrefix) is true)
{
OverrideReplacement(option);
}
else if (configProperties.FirstOrDefault(p => p.Name.EqualsInsensitive(option.Property)) is not PropertyInfo property)
{
Console.Error.WriteLine($"Unknown configuration property '{option.Property}'");
}
else if (property.PropertyType == typeof(string))
{
property.SetValue(Configuration.Instance, option.Value?.Trim());
}
else if (property.PropertyType == typeof(bool) && bool.TryParse(option.Value?.Trim(), out var bVal))
{
property.SetValue(Configuration.Instance, bVal);
}
else if (property.PropertyType == typeof(int) && int.TryParse(option.Value?.Trim(), out var intVal))
{
property.SetValue(Configuration.Instance, intVal);
}
else if (property.PropertyType == typeof(long) && long.TryParse(option.Value?.Trim(), out var longVal))
{
property.SetValue(Configuration.Instance, longVal);
}
else if (property.PropertyType == typeof(LongPath))
{
var value = option.Value is null ? null : (LongPath)option.Value.Trim();
property.SetValue(Configuration.Instance, value);
}
else if (property.PropertyType.IsEnum && TryParseEnum(property.PropertyType, option.Value?.Trim(), out var enumVal))
{
property.SetValue(Configuration.Instance, enumVal);
}
else
{
Console.Error.WriteLine($"Cannot set configuration property '{property.Name}' of type '{property.PropertyType}' with value '{option.Value}'");
}
}
}
private static void OverrideReplacement(OptionOverride option)
{
List<Replacement> newReplacements = [];
bool addedToList = false;
foreach (var r in Configuration.Instance.ReplacementCharacters.Replacements)
{
if (GetReplacementName(r).EqualsInsensitive(option.Property))
{
var newReplacement = new Replacement(r.CharacterToReplace, option.Value ?? string.Empty, r.Description)
{
Mandatory = r.Mandatory
};
newReplacements.Add(newReplacement);
addedToList = true;
}
else
{
newReplacements.Add(r);
}
}
if (!addedToList)
{
var charToReplace = option.Property!.Substring(ReplacePrefix.Length);
if (charToReplace.Length != 1)
{
Console.Error.WriteLine($"Invalid character to replace: '{charToReplace}'");
}
else
{
newReplacements.Add(new(charToReplace[0], option.Value ?? string.Empty, ""));
}
}
Configuration.Instance.ReplacementCharacters = new ReplacementCharacters { Replacements = newReplacements };
}
const string ReplacePrefix = "Replace_";
protected static string GetReplacementName(Replacement r)
=> !r.Mandatory ? ReplacePrefix + r.CharacterToReplace
: r.CharacterToReplace == '\0' ? ReplacePrefix + "OtherInvalid"
: r.CharacterToReplace == '/' ? ReplacePrefix + "Slash"
: r.CharacterToReplace == '\\' ? ReplacePrefix + "BackSlash"
: r.Description == "Open Quote" ? ReplacePrefix + "OpenQuote"
: r.Description == "Close Quote" ? ReplacePrefix + "CloseQuote"
: r.Description == "Other Quote" ? ReplacePrefix + "OtherQuote"
: ReplacePrefix + r.Description.Replace(" ", "");
public class OptionOverride
{
public string? Property { get; }
public string? Value { get; }
public OptionOverride(string value)
{
if (value is null)
return;
//Special case of Replace_= settings
var start
= value.StartsWithInsensitive(ReplacePrefix + "=")
? value.IndexOf('=', ReplacePrefix.Length + 1)
: value.IndexOf('=');
if (start < 1)
return;
Property = value[..start];
//Don't trim here. Trim before parsing the value if needed, otherwise
//preserve for settings which utilize white space (e.g. Replacements)
Value = value[(start + 1)..];
if (Value.StartsWith('"') && Value.EndsWith('"'))
{
Value = Value[1..];
}
if (Value.EndsWith('"'))
{
Value = Value[..^1];
}
}
}
}
}