Overhaul LibationCli

Add version verb with option to check for upgrade
Add Search verb to search the library
Add export file type inference
Add more set-status options
Add console progress bar and ETA
Add processable option to liberate specific book IDs
Scan accounts by nickname or account ID
Improve startup performance for halp and on parsing error
More useful error messages
This commit is contained in:
Mbucari 2023-07-02 14:32:54 -06:00
parent bb05847b25
commit 29803c6ba0
14 changed files with 477 additions and 146 deletions

View File

@ -0,0 +1,77 @@
using System;
using System.IO;
namespace LibationCli;
internal class ConsoleProgressBar
{
public TextWriter Output { get; }
public int MaxWidth { get; }
public char ProgressChar { get; }
public char NoProgressChar { get; }
public double? Progress
{
get => m_Progress;
set
{
m_Progress = value ?? 0;
WriteProgress();
}
}
public TimeSpan RemainingTime
{
get => m_RemainingTime;
set
{
m_RemainingTime = value;
WriteProgress();
}
}
private double m_Progress;
private TimeSpan m_RemainingTime;
private int m_LastWriteLength = 0;
private const int MAX_ETA_LEN = 10;
private readonly int m_NumProgressPieces;
public ConsoleProgressBar(
TextWriter output,
int maxWidth = 80,
char progressCharr = '#',
char noProgressChar = '.')
{
Output = output;
MaxWidth = maxWidth;
ProgressChar = progressCharr;
NoProgressChar = noProgressChar;
m_NumProgressPieces = MaxWidth - MAX_ETA_LEN - 4;
}
private void WriteProgress()
{
var numCompleted = (int)Math.Round(double.Min(100, m_Progress) * m_NumProgressPieces / 100);
var numRemaining = m_NumProgressPieces - numCompleted;
var progressBar = $"[{new string(ProgressChar, numCompleted)}{new string(NoProgressChar, numRemaining)}] ";
progressBar += RemainingTime.TotalMinutes > 1000
? "ETA ∞"
: $"ETA {(int)RemainingTime.TotalMinutes}:{RemainingTime.Seconds:D2}";
Output.Write(new string('\b', m_LastWriteLength) + progressBar);
if (progressBar.Length < m_LastWriteLength)
{
var extra = m_LastWriteLength - progressBar.Length;
Output.Write(new string(' ', extra) + new string('\b', extra));
}
m_LastWriteLength = progressBar.Length;
}
public void Clear()
=> Output.Write(
new string('\b', m_LastWriteLength)
+ new string(' ', m_LastWriteLength)
+ new string('\b', m_LastWriteLength));
}

View File

@ -0,0 +1,52 @@
using AppScaffolding;
using CommandLine;
using CommandLine.Text;
namespace LibationCli;
[Verb("help", HelpText = "Display more information on a specific command.")]
internal class HelpVerb
{
/// <summary>
/// Name of the verb to get help about
/// </summary>
[Value(0, Default = "")]
public string HelpType { get; set; }
/// <summary>
/// Create a base <see cref="HelpText"/> for <see cref="LibationCli"/>
/// </summary>
public static HelpText CreateHelpText()
{
var auto = new HelpText
{
AutoVersion = false,
AutoHelp = false,
Heading = $"LibationCli v{LibationScaffolding.BuildVersion.ToString(3)}",
AdditionalNewLineAfterOption = true,
MaximumDisplayWidth = 80
};
return auto;
}
/// <summary>
/// Get the <see cref="HelpType"/>'s <see cref="HelpText"/>
/// </summary>
public HelpText GetHelpText()
{
var helpText = CreateHelpText();
var result = new Parser().ParseArguments(new string[] { HelpType }, Program.VerbTypes);
if (result.TypeInfo.Current == typeof(NullInstance))
{
//HelpType is not a defined verb so get LibationCli usage
helpText.AddVerbs(Program.VerbTypes);
}
else
{
helpText.AddDashesToOption = true;
helpText.AutoHelp = true;
helpText.AddOptions(result);
}
return helpText;
}
}

View File

@ -3,7 +3,8 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRun>true</PublishReadyToRun>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>

View File

@ -1,8 +1,5 @@
using System; using CommandLine;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommandLine;
namespace LibationCli namespace LibationCli
{ {

View File

@ -1,10 +1,8 @@
using System; using ApplicationServices;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ApplicationServices;
using AudibleUtilities;
using CommandLine; using CommandLine;
using System;
using System.IO;
using System.Threading.Tasks;
namespace LibationCli namespace LibationCli
{ {
@ -29,26 +27,38 @@ namespace LibationCli
} }
*/ */
#endregion #endregion
[Option(shortName: 'x', longName: "xlsx", SetName = "xlsx", Required = true)] [Option(shortName: 'x', longName: "xlsx", HelpText = "Microsoft Excel Spreadsheet", SetName = "Export Format")]
public bool xlsx { get; set; } public bool xlsx { get; set; }
[Option(shortName: 'c', longName: "csv", SetName = "csv", Required = true)] [Option(shortName: 'c', longName: "csv", HelpText = "Comma-separated values", SetName = "Export Format")]
public bool csv { get; set; } public bool csv { get; set; }
[Option(shortName: 'j', longName: "json", SetName = "json", Required = true)] [Option(shortName: 'j', longName: "json", HelpText = "JavaScript Object Notation", SetName = "Export Format")]
public bool json { get; set; } public bool json { get; set; }
protected override Task ProcessAsync() protected override Task ProcessAsync()
{ {
if (xlsx) Action<string> exporter
LibraryExporter.ToXlsx(FilePath); = csv ? LibraryExporter.ToCsv
if (csv) : json ? LibraryExporter.ToJson
LibraryExporter.ToCsv(FilePath); : xlsx ? LibraryExporter.ToXlsx
if (json) : Path.GetExtension(FilePath)?.ToLower() switch
LibraryExporter.ToJson(FilePath); {
".xlsx" => LibraryExporter.ToXlsx,
Console.WriteLine($"Library exported to: {FilePath}"); ".csv" => LibraryExporter.ToCsv,
".json" => LibraryExporter.ToJson,
_ => null
};
if (exporter is null)
{
PrintVerbUsage($"Undefined export format for file type \"{Path.GetExtension(FilePath)}\"");
}
else
{
exporter(FilePath);
Console.WriteLine($"Library exported to: {FilePath}");
}
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View File

@ -1,10 +1,7 @@
using System; using CommandLine;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommandLine;
using DataLayer; using DataLayer;
using FileLiberator; using FileLiberator;
using System.Threading.Tasks;
namespace LibationCli namespace LibationCli
{ {

View File

@ -1,18 +1,18 @@
using System; using ApplicationServices;
using AudibleUtilities;
using CommandLine;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using ApplicationServices;
using AudibleUtilities;
using CommandLine;
namespace LibationCli namespace LibationCli
{ {
[Verb("scan", HelpText = "Scan library. Default: scan all accounts. Optional: use 'account' flag to specify a single account.")] [Verb("scan", HelpText = "Scan library. Default: scan all accounts. Optional: use 'account' flag to specify a single account.")]
public class ScanOptions : OptionsBase public class ScanOptions : OptionsBase
{ {
[Value(0, MetaName = "Accounts", HelpText = "Optional: nicknames of accounts to scan.", Required = false)] [Value(0, MetaName = "Accounts", HelpText = "Optional: user ID or nicknames of accounts to scan.", Required = false)]
public IEnumerable<string> AccountNicknames { get; set; } public IEnumerable<string> AccountNames { get; set; }
protected override async Task ProcessAsync() protected override async Task ProcessAsync()
{ {
@ -42,13 +42,19 @@ namespace LibationCli
private Account[] getAccounts() private Account[] getAccounts()
{ {
using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var accounts = persister.AccountsSettings.GetAll().ToArray(); var allAccounts = persister.AccountsSettings.GetAll().ToArray();
if (!AccountNicknames.Any()) if (!AccountNames.Any())
return accounts; return allAccounts;
var found = accounts.Where(acct => AccountNicknames.Contains(acct.AccountName)).ToArray(); var accountNames = AccountNames.Select(n => n.ToLower()).ToArray();
var notFound = AccountNicknames.Except(found.Select(f => f.AccountName)).ToArray();
var found
= allAccounts
.Where(acct => accountNames.Contains(acct.AccountName.ToLower()) || accountNames.Contains(acct.AccountId.ToLower()))
.ToArray();
var notFound = allAccounts.Except(found).ToArray();
// no accounts found. do not continue // no accounts found. do not continue
if (!found.Any()) if (!found.Any())

View File

@ -0,0 +1,50 @@
using ApplicationServices;
using CommandLine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace LibationCli.Options
{
[Verb("search", HelpText = "Search for books in your library")]
internal class SearchOptions : OptionsBase
{
[Value(0, MetaName = "query", Required = true, HelpText = "Lucene query test to search")]
public IEnumerable<string> Query { get; set; }
protected override Task ProcessAsync()
{
var query = string.Join(" ", Query).Trim('\"');
var results = SearchEngineCommands.Search(query).Docs.ToList();
Console.WriteLine($"Found {results.Count} matching results.");
const string nextPrompt = "Press any key for the next 10 results or Esc for all results";
bool waitForNextBatch = true;
for (int i = 0; i < results.Count; i += 10)
{
foreach (var doc in results.Skip(i).Take(10))
Console.WriteLine(getDocDisplay(doc.Doc));
if (waitForNextBatch)
{
Console.Write(nextPrompt);
waitForNextBatch = Console.ReadKey().Key != ConsoleKey.Escape;
ReplaceConsoleText(Console.Out, nextPrompt.Length, "");
Console.SetCursorPosition(0, Console.CursorTop);
}
}
return Task.CompletedTask;
}
private static string getDocDisplay(Lucene.Net.Documents.Document doc)
{
var title = doc.GetField("title");
var id = doc.GetField("_ID_");
return $"[{id.StringValue}] - {title.StringValue}";
}
}
}

View File

@ -1,37 +1,69 @@
using System; using ApplicationServices;
using CommandLine;
using DataLayer;
using Dinah.Core;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using ApplicationServices;
using AudibleUtilities;
using CommandLine;
namespace LibationCli namespace LibationCli
{ {
[Verb("set-status", HelpText = """ [Verb("set-status", HelpText = """
Set download statuses throughout library based on whether each book's audio file can be found. Set download statuses throughout library based on whether each book's audio file can be found.
Must include at least one flag: --downloaded , --not-downloaded.
Downloaded: If the audio file can be found, set download status to 'Downloaded'.
Not Downloaded: If the audio file cannot be found, set download status to 'Not Downloaded'
""")] """)]
public class SetDownloadStatusOptions : OptionsBase public class SetDownloadStatusOptions : OptionsBase
{ {
[Option(shortName: 'd', longName: "downloaded", Required = true)] [Option(shortName: 'd', longName: "downloaded", Group = "Download Status", HelpText = "set download status to 'Downloaded'")]
public bool SetDownloaded { get; set; } public bool SetDownloaded { get; set; }
[Option(shortName: 'n', longName: "not-downloaded", Required = true)] [Option(shortName: 'n', longName: "not-downloaded", Group = "Download Status", HelpText = "set download status to 'Downloaded'")]
public bool SetNotDownloaded { get; set; } public bool SetNotDownloaded { get; set; }
protected override async Task ProcessAsync() [Option("force", HelpText = "Set the download status regardless of whether the book's audio file can be found. Only one download status option may be used with this option.")]
{ public bool Force { get; set; }
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
var bulkSetStatus = new BulkSetDownloadStatus(libraryBooks, SetDownloaded, SetNotDownloaded); [Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books on which to set download status.")]
await Task.Run(() => bulkSetStatus.Discover()); public IEnumerable<string> Asins { get; set; }
bulkSetStatus.Execute();
foreach (var msg in bulkSetStatus.Messages) protected override async Task ProcessAsync()
Console.WriteLine(msg); {
} if (Force && SetDownloaded && SetNotDownloaded)
} {
PrintVerbUsage("ERROR:\nWhen run with --force option, only one download status option may be used.");
return;
}
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
if (Asins.Any())
{
var asins = Asins.Select(a => a.ToLower()).ToArray();
libraryBooks = libraryBooks.Where(lb => lb.Book.AudibleProductId.ToLower().In(asins)).ToList();
if (libraryBooks.Count == 0)
{
Console.Error.WriteLine("Could not find any books matching asins");
return;
}
}
if (Force)
{
var status = SetDownloaded ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
var num = libraryBooks.UpdateBookStatus(status);
Console.WriteLine($"Set LiberatedStatus to '{status}' on {"book".PluralizeWithCount(num)}");
}
else
{
var bulkSetStatus = new BulkSetDownloadStatus(libraryBooks, SetDownloaded, SetNotDownloaded);
await Task.Run(() => bulkSetStatus.Discover());
bulkSetStatus.Execute();
foreach (var msg in bulkSetStatus.Messages)
Console.WriteLine(msg);
}
}
}
} }

View File

@ -0,0 +1,59 @@
using AppScaffolding;
using CommandLine;
using System;
using System.Threading.Tasks;
namespace LibationCli.Options;
[Verb("version", HelpText = "Display version information.")]
internal class VersionOptions : OptionsBase
{
[Option('c', "check", Required = false, HelpText = "Check if an upgrade is available")]
public bool CheckForUpgrade { get; set; }
protected override Task ProcessAsync()
{
const string checkingForUpgrade = "Checking for upgrade...";
Console.WriteLine($"Libation {LibationScaffolding.Variety} v{LibationScaffolding.BuildVersion.ToString(3)}");
if (CheckForUpgrade)
{
Console.Write(checkingForUpgrade);
var origColor = Console.ForegroundColor;
try
{
var upgradeProperties = LibationScaffolding.GetLatestRelease();
if (upgradeProperties is null)
{
Console.ForegroundColor = ConsoleColor.Green;
ReplaceConsoleText(Console.Out, checkingForUpgrade.Length, "No available upgrade");
Console.WriteLine();
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
ReplaceConsoleText(Console.Out, checkingForUpgrade.Length, $"Upgrade Available: v{upgradeProperties.LatestRelease.ToString(3)}");
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(upgradeProperties.ZipUrl);
Console.WriteLine();
Console.WriteLine("Release Notes");
Console.WriteLine("=============");
Console.WriteLine(upgradeProperties.Notes);
}
}
catch
{
Console.Error.WriteLine("ERROR CHECKING FOR UPGRADE");
}
finally
{
Console.ForegroundColor = origColor;
}
}
return Task.CompletedTask;
}
}

View File

@ -1,8 +1,8 @@
using System; using CommandLine;
using System.Collections.Generic; using System;
using System.Linq; using System.IO;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommandLine;
namespace LibationCli namespace LibationCli
{ {
@ -17,15 +17,34 @@ namespace LibationCli
catch (Exception ex) catch (Exception ex)
{ {
Environment.ExitCode = (int)ExitCode.RunTimeError; Environment.ExitCode = (int)ExitCode.RunTimeError;
PrintVerbUsage(new string[]
Console.Error.WriteLine("ERROR"); {
Console.Error.WriteLine("====="); "ERROR",
Console.Error.WriteLine(ex.Message); "=====",
Console.Error.WriteLine(); ex.Message,
Console.Error.WriteLine(ex.StackTrace); "",
ex.StackTrace
});
} }
} }
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 abstract Task ProcessAsync();
} }
} }

View File

@ -1,23 +1,34 @@
using System; using ApplicationServices;
using CommandLine;
using DataLayer;
using Dinah.Core;
using FileLiberator;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using ApplicationServices;
using CommandLine;
using DataLayer;
using FileLiberator;
namespace LibationCli namespace LibationCli
{ {
public abstract class ProcessableOptionsBase : OptionsBase public abstract class ProcessableOptionsBase : OptionsBase
{ {
[Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books to process.")]
public IEnumerable<string> Asins { get; set; }
protected static TProcessable CreateProcessable<TProcessable>(EventHandler<LibraryBook> completedAction = null) protected static TProcessable CreateProcessable<TProcessable>(EventHandler<LibraryBook> completedAction = null)
where TProcessable : Processable, new() where TProcessable : Processable, new()
{ {
var progressBar = new ConsoleProgressBar(Console.Out);
var strProc = new TProcessable(); var strProc = new TProcessable();
strProc.Begin += (o, e) => Console.WriteLine($"{typeof(TProcessable).Name} Begin: {e}"); strProc.Begin += (o, e) => Console.WriteLine($"{typeof(TProcessable).Name} Begin: {e}");
strProc.Completed += (o, e) => Console.WriteLine($"{typeof(TProcessable).Name} Completed: {e}");
strProc.Completed += (o, e) =>
{
progressBar.Clear();
Console.WriteLine($"{typeof(TProcessable).Name} Completed: {e}");
};
strProc.Completed += (s, e) => strProc.Completed += (s, e) =>
{ {
@ -32,12 +43,23 @@ namespace LibationCli
} }
}; };
strProc.StreamingTimeRemaining += (_, e) => progressBar.RemainingTime = e;
strProc.StreamingProgressChanged += (_, e) => progressBar.Progress = e.ProgressPercentage;
return strProc; return strProc;
} }
protected static async Task RunAsync(Processable Processable) protected async Task RunAsync(Processable Processable)
{ {
foreach (var libraryBook in Processable.GetValidLibraryBooks(DbContexts.GetLibrary_Flat_NoTracking())) var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking().AsEnumerable();
if (Asins.Any())
{
var asinsLower = Asins.Select(a => a.ToLower()).ToArray();
libraryBooks = libraryBooks.Where(lb => lb.Book.AudibleProductId.ToLower().In(asinsLower));
}
foreach (var libraryBook in Processable.GetValidLibraryBooks(libraryBooks))
await ProcessOneAsync(Processable, libraryBook, false); await ProcessOneAsync(Processable, libraryBook, false);
var done = "Done. All books have been processed"; var done = "Done. All books have been processed";

View File

@ -1,12 +1,9 @@
using System; using CommandLine;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommandLine;
using CommandLine.Text; using CommandLine.Text;
using Dinah.Core; using Dinah.Core;
using Dinah.Core.Collections; using System;
using Dinah.Core.Collections.Generic; using System.Linq;
using System.Threading.Tasks;
namespace LibationCli namespace LibationCli
{ {
@ -19,47 +16,63 @@ namespace LibationCli
} }
class Program class Program
{ {
static async Task<int> Main(string[] args) public readonly static Type[] VerbTypes = Setup.LoadVerbs();
static async Task Main(string[] args)
{ {
//***********************************************//
// //
// do not use Configuration before this line //
// //
//***********************************************//
Setup.Initialize();
var types = Setup.LoadVerbs();
#if DEBUG #if DEBUG
string input = null; string input = null;
//input = " set-status -n --force B017V4IM1G";
//input = " liberate B017V4IM1G";
//input = " convert B017V4IM1G";
//input = " search \"-liberated\"";
//input = " export --help"; //input = " export --help";
//input = " version --check";
//input = " scan rmcrackan"; //input = " scan rmcrackan";
//input = " help set-status";
//input = " liberate "; //input = " liberate ";
// note: this hack will fail for quoted file paths with spaces because it will break on those spaces // note: this hack will fail for quoted file paths with spaces because it will break on those spaces
if (!string.IsNullOrWhiteSpace(input)) if (!string.IsNullOrWhiteSpace(input))
args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var setBreakPointHere = args; var setBreakPointHere = args;
#endif #endif
var result = Parser.Default.ParseArguments(args, types); var result = new Parser(ConfigureParser).ParseArguments(args, VerbTypes);
// if successfully parsed if (result.Value is HelpVerb helper)
// async: run parsed options Console.Error.WriteLine(helper.GetHelpText());
await result.WithParsedAsync<OptionsBase>(opt => opt.Run()); else if (result.TypeInfo.Current == typeof(HelpVerb))
{
//Error parsing the command, but the verb type was identified as HelpVerb
//Print LibationCli usage
var helpText = HelpVerb.CreateHelpText();
helpText.AddVerbs(VerbTypes);
Console.Error.WriteLine(helpText);
}
else if (result.Errors.Any())
HandleErrors(result);
else
{
//Everything parsed correctly, so execute the command
// if not successfully parsed //***********************************************//
// sync: handle parse errors // //
result.WithNotParsed(errors => HandleErrors(result, errors)); // do not use Configuration before this line //
// //
//***********************************************//
Setup.Initialize();
return Environment.ExitCode; // if successfully parsed
// async: run parsed options
await result.WithParsedAsync<OptionsBase>(opt => opt.Run());
}
} }
private static void HandleErrors(ParserResult<object> result, IEnumerable<Error> errors) private static void HandleErrors(ParserResult<object> result)
{ {
var errorsList = errors.ToList(); var errorsList = result.Errors.ToList();
if (errorsList.Any(e => e.Tag.In(ErrorType.HelpRequestedError, ErrorType.VersionRequestedError, ErrorType.HelpVerbRequestedError))) if (errorsList.Any(e => e.Tag.In(ErrorType.HelpRequestedError, ErrorType.VersionRequestedError, ErrorType.HelpVerbRequestedError)))
{ {
Environment.ExitCode = (int)ExitCode.NonRunNonError; Environment.ExitCode = (int)ExitCode.NonRunNonError;
@ -67,17 +80,36 @@ namespace LibationCli
} }
Environment.ExitCode = (int)ExitCode.ParseError; Environment.ExitCode = (int)ExitCode.ParseError;
var helpText = HelpVerb.CreateHelpText();
if (errorsList.Any(e => e.Tag.In(ErrorType.NoVerbSelectedError))) if (errorsList.OfType<NoVerbSelectedError>().Any())
{ {
Console.Error.WriteLine("No verb selected"); //Print LibationCli usage
return; helpText.AddPreOptionsLine("No verb selected");
helpText.AddVerbs(VerbTypes);
} }
else
{
//print the specified verb's usage
helpText.AddDashesToOption = true;
helpText.AutoHelp = true;
var helpText = HelpText.AutoBuild(result, if (!errorsList.OfType<UnknownOptionError>().Any(o => o.Token.ToLower() == "help"))
h => HelpText.DefaultParsingErrorsHandler(result, h), {
e => e); //verb was not executed with the "--help" option,
Console.WriteLine(helpText); //so print verb option parsing error info.
helpText = HelpText.DefaultParsingErrorsHandler(result, helpText);
}
helpText.AddOptions(result);
}
Console.Error.WriteLine(helpText);
}
private static void ConfigureParser(ParserSettings settings)
{
settings.AutoVersion = false;
settings.AutoHelp = false;
} }
} }
} }

View File

@ -1,14 +1,8 @@
using System; using AppScaffolding;
using System.Collections.Generic; using CommandLine;
using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using AppScaffolding;
using CommandLine;
using CommandLine.Text;
using Dinah.Core;
using Dinah.Core.Collections;
using Dinah.Core.Collections.Generic;
namespace LibationCli namespace LibationCli
{ {
@ -16,6 +10,11 @@ namespace LibationCli
{ {
public static void Initialize() public static void Initialize()
{ {
//Determine variety by the dlls present in the current directory.
//Necessary to be able to check for upgrades.
var variety = System.IO.File.Exists("System.Windows.Forms.dll") ? Variety.Classic : Variety.Chardonnay;
LibationScaffolding.SetReleaseIdentifier(variety);
//***********************************************// //***********************************************//
// // // //
// do not use Configuration before this line // // do not use Configuration before this line //
@ -26,28 +25,6 @@ namespace LibationCli
LibationScaffolding.RunPostConfigMigrations(config); LibationScaffolding.RunPostConfigMigrations(config);
LibationScaffolding.RunPostMigrationScaffolding(config); LibationScaffolding.RunPostMigrationScaffolding(config);
#if !DEBUG
checkForUpdate();
#endif
}
private static void checkForUpdate()
{
var upgradeProperties = LibationScaffolding.GetLatestRelease();
if (upgradeProperties is null)
return;
var origColor = Console.ForegroundColor;
try
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"UPDATE AVAILABLE @ {upgradeProperties.ZipUrl}");
}
finally
{
Console.ForegroundColor = origColor;
}
} }
public static Type[] LoadVerbs() => Assembly.GetExecutingAssembly() public static Type[] LoadVerbs() => Assembly.GetExecutingAssembly()