From 29803c6ba0e659a0016de64654c1a68b9cfdc90a Mon Sep 17 00:00:00 2001 From: Mbucari <37587114+Mbucari@users.noreply.github.com> Date: Sun, 2 Jul 2023 14:32:54 -0600 Subject: [PATCH] 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 --- Source/LibationCli/ConsoleProgressBar.cs | 77 ++++++++++++++ Source/LibationCli/HelpVerb.cs | 52 +++++++++ Source/LibationCli/LibationCli.csproj | 3 +- Source/LibationCli/Options/ConvertOptions.cs | 5 +- Source/LibationCli/Options/ExportOptions.cs | 44 +++++--- Source/LibationCli/Options/LiberateOptions.cs | 7 +- Source/LibationCli/Options/ScanOptions.cs | 28 +++-- Source/LibationCli/Options/SearchOptions.cs | 50 +++++++++ .../Options/SetDownloadStatusOptions.cs | 82 +++++++++----- Source/LibationCli/Options/VersionOptions.cs | 59 +++++++++++ Source/LibationCli/Options/_OptionsBase.cs | 39 +++++-- .../Options/_ProcessableOptionsBase.cs | 38 +++++-- Source/LibationCli/Program.cs | 100 ++++++++++++------ Source/LibationCli/Setup.cs | 39 ++----- 14 files changed, 477 insertions(+), 146 deletions(-) create mode 100644 Source/LibationCli/ConsoleProgressBar.cs create mode 100644 Source/LibationCli/HelpVerb.cs create mode 100644 Source/LibationCli/Options/SearchOptions.cs create mode 100644 Source/LibationCli/Options/VersionOptions.cs diff --git a/Source/LibationCli/ConsoleProgressBar.cs b/Source/LibationCli/ConsoleProgressBar.cs new file mode 100644 index 00000000..7369f427 --- /dev/null +++ b/Source/LibationCli/ConsoleProgressBar.cs @@ -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)); +} diff --git a/Source/LibationCli/HelpVerb.cs b/Source/LibationCli/HelpVerb.cs new file mode 100644 index 00000000..b9e329cf --- /dev/null +++ b/Source/LibationCli/HelpVerb.cs @@ -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 +{ + /// + /// Name of the verb to get help about + /// + [Value(0, Default = "")] + public string HelpType { get; set; } + + /// + /// Create a base for + /// + 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; + } + + /// + /// Get the 's + /// + 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; + } +} diff --git a/Source/LibationCli/LibationCli.csproj b/Source/LibationCli/LibationCli.csproj index 04926587..864e4ec6 100644 --- a/Source/LibationCli/LibationCli.csproj +++ b/Source/LibationCli/LibationCli.csproj @@ -3,7 +3,8 @@ Exe - net7.0 + net7.0-windows + win-x64 true false false diff --git a/Source/LibationCli/Options/ConvertOptions.cs b/Source/LibationCli/Options/ConvertOptions.cs index fa3b744b..b20f0152 100644 --- a/Source/LibationCli/Options/ConvertOptions.cs +++ b/Source/LibationCli/Options/ConvertOptions.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using CommandLine; using System.Threading.Tasks; -using CommandLine; namespace LibationCli { diff --git a/Source/LibationCli/Options/ExportOptions.cs b/Source/LibationCli/Options/ExportOptions.cs index 6d43a029..6e994718 100644 --- a/Source/LibationCli/Options/ExportOptions.cs +++ b/Source/LibationCli/Options/ExportOptions.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using ApplicationServices; -using AudibleUtilities; +using ApplicationServices; using CommandLine; +using System; +using System.IO; +using System.Threading.Tasks; namespace LibationCli { @@ -29,26 +27,38 @@ namespace LibationCli } */ #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; } - [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; } - [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; } protected override Task ProcessAsync() { - if (xlsx) - LibraryExporter.ToXlsx(FilePath); - if (csv) - LibraryExporter.ToCsv(FilePath); - if (json) - LibraryExporter.ToJson(FilePath); - - Console.WriteLine($"Library exported to: {FilePath}"); + Action exporter + = csv ? LibraryExporter.ToCsv + : json ? LibraryExporter.ToJson + : xlsx ? LibraryExporter.ToXlsx + : Path.GetExtension(FilePath)?.ToLower() switch + { + ".xlsx" => LibraryExporter.ToXlsx, + ".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; } } diff --git a/Source/LibationCli/Options/LiberateOptions.cs b/Source/LibationCli/Options/LiberateOptions.cs index 5c904771..ab1a8e78 100644 --- a/Source/LibationCli/Options/LiberateOptions.cs +++ b/Source/LibationCli/Options/LiberateOptions.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CommandLine; +using CommandLine; using DataLayer; using FileLiberator; +using System.Threading.Tasks; namespace LibationCli { diff --git a/Source/LibationCli/Options/ScanOptions.cs b/Source/LibationCli/Options/ScanOptions.cs index 5f1be8e3..43c888dd 100644 --- a/Source/LibationCli/Options/ScanOptions.cs +++ b/Source/LibationCli/Options/ScanOptions.cs @@ -1,18 +1,18 @@ -using System; +using ApplicationServices; +using AudibleUtilities; +using CommandLine; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using ApplicationServices; -using AudibleUtilities; -using CommandLine; namespace LibationCli { [Verb("scan", HelpText = "Scan library. Default: scan all accounts. Optional: use 'account' flag to specify a single account.")] public class ScanOptions : OptionsBase { - [Value(0, MetaName = "Accounts", HelpText = "Optional: nicknames of accounts to scan.", Required = false)] - public IEnumerable AccountNicknames { get; set; } + [Value(0, MetaName = "Accounts", HelpText = "Optional: user ID or nicknames of accounts to scan.", Required = false)] + public IEnumerable AccountNames { get; set; } protected override async Task ProcessAsync() { @@ -42,13 +42,19 @@ namespace LibationCli private Account[] getAccounts() { using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var accounts = persister.AccountsSettings.GetAll().ToArray(); + var allAccounts = persister.AccountsSettings.GetAll().ToArray(); - if (!AccountNicknames.Any()) - return accounts; + if (!AccountNames.Any()) + return allAccounts; - var found = accounts.Where(acct => AccountNicknames.Contains(acct.AccountName)).ToArray(); - var notFound = AccountNicknames.Except(found.Select(f => f.AccountName)).ToArray(); + var accountNames = AccountNames.Select(n => n.ToLower()).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 if (!found.Any()) diff --git a/Source/LibationCli/Options/SearchOptions.cs b/Source/LibationCli/Options/SearchOptions.cs new file mode 100644 index 00000000..5ed00d6a --- /dev/null +++ b/Source/LibationCli/Options/SearchOptions.cs @@ -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 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}"; + } + } +} diff --git a/Source/LibationCli/Options/SetDownloadStatusOptions.cs b/Source/LibationCli/Options/SetDownloadStatusOptions.cs index 0bde6a84..1c5f43ac 100644 --- a/Source/LibationCli/Options/SetDownloadStatusOptions.cs +++ b/Source/LibationCli/Options/SetDownloadStatusOptions.cs @@ -1,37 +1,69 @@ -using System; +using ApplicationServices; +using CommandLine; +using DataLayer; +using Dinah.Core; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using ApplicationServices; -using AudibleUtilities; -using CommandLine; namespace LibationCli { - [Verb("set-status", HelpText = """ - 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' + [Verb("set-status", HelpText = """ + Set download statuses throughout library based on whether each book's audio file can be found. """)] - public class SetDownloadStatusOptions : OptionsBase - { - [Option(shortName: 'd', longName: "downloaded", Required = true)] - public bool SetDownloaded { get; set; } + public class SetDownloadStatusOptions : OptionsBase + { + [Option(shortName: 'd', longName: "downloaded", Group = "Download Status", HelpText = "set download status to 'Downloaded'")] + public bool SetDownloaded { get; set; } - [Option(shortName: 'n', longName: "not-downloaded", Required = true)] - public bool SetNotDownloaded { get; set; } + [Option(shortName: 'n', longName: "not-downloaded", Group = "Download Status", HelpText = "set download status to 'Downloaded'")] + public bool SetNotDownloaded { get; set; } - protected override async Task ProcessAsync() - { - var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking(); + [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 bulkSetStatus = new BulkSetDownloadStatus(libraryBooks, SetDownloaded, SetNotDownloaded); - await Task.Run(() => bulkSetStatus.Discover()); - bulkSetStatus.Execute(); + [Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books on which to set download status.")] + public IEnumerable Asins { get; set; } - foreach (var msg in bulkSetStatus.Messages) - Console.WriteLine(msg); - } - } + protected override async Task ProcessAsync() + { + 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); + } + } + } } diff --git a/Source/LibationCli/Options/VersionOptions.cs b/Source/LibationCli/Options/VersionOptions.cs new file mode 100644 index 00000000..4ebced9e --- /dev/null +++ b/Source/LibationCli/Options/VersionOptions.cs @@ -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; + } +} diff --git a/Source/LibationCli/Options/_OptionsBase.cs b/Source/LibationCli/Options/_OptionsBase.cs index 40578b75..cce3f0ee 100644 --- a/Source/LibationCli/Options/_OptionsBase.cs +++ b/Source/LibationCli/Options/_OptionsBase.cs @@ -1,8 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using CommandLine; +using System; +using System.IO; +using System.Reflection; using System.Threading.Tasks; -using CommandLine; namespace LibationCli { @@ -17,15 +17,34 @@ namespace LibationCli catch (Exception ex) { Environment.ExitCode = (int)ExitCode.RunTimeError; - - Console.Error.WriteLine("ERROR"); - Console.Error.WriteLine("====="); - Console.Error.WriteLine(ex.Message); - Console.Error.WriteLine(); - Console.Error.WriteLine(ex.StackTrace); + PrintVerbUsage(new string[] + { + "ERROR", + "=====", + ex.Message, + "", + ex.StackTrace + }); } } + protected void PrintVerbUsage(params string[] linesBeforeUsage) + { + var verb = GetType().GetCustomAttribute().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(); } } diff --git a/Source/LibationCli/Options/_ProcessableOptionsBase.cs b/Source/LibationCli/Options/_ProcessableOptionsBase.cs index 664499c1..bb4cd735 100644 --- a/Source/LibationCli/Options/_ProcessableOptionsBase.cs +++ b/Source/LibationCli/Options/_ProcessableOptionsBase.cs @@ -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.Linq; using System.Threading.Tasks; -using ApplicationServices; -using CommandLine; -using DataLayer; -using FileLiberator; namespace LibationCli { public abstract class ProcessableOptionsBase : OptionsBase { + + [Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books to process.")] + public IEnumerable Asins { get; set; } + protected static TProcessable CreateProcessable(EventHandler completedAction = null) where TProcessable : Processable, new() { + var progressBar = new ConsoleProgressBar(Console.Out); var strProc = new TProcessable(); 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) => { @@ -32,12 +43,23 @@ namespace LibationCli } }; + strProc.StreamingTimeRemaining += (_, e) => progressBar.RemainingTime = e; + strProc.StreamingProgressChanged += (_, e) => progressBar.Progress = e.ProgressPercentage; + 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); var done = "Done. All books have been processed"; diff --git a/Source/LibationCli/Program.cs b/Source/LibationCli/Program.cs index 3bdce3da..19538dd5 100644 --- a/Source/LibationCli/Program.cs +++ b/Source/LibationCli/Program.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CommandLine; +using CommandLine; using CommandLine.Text; using Dinah.Core; -using Dinah.Core.Collections; -using Dinah.Core.Collections.Generic; +using System; +using System.Linq; +using System.Threading.Tasks; namespace LibationCli { @@ -19,47 +16,63 @@ namespace LibationCli } class Program { - static async Task 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 string input = null; + //input = " set-status -n --force B017V4IM1G"; + //input = " liberate B017V4IM1G"; + //input = " convert B017V4IM1G"; + //input = " search \"-liberated\""; //input = " export --help"; + //input = " version --check"; //input = " scan rmcrackan"; + //input = " help set-status"; //input = " liberate "; - // note: this hack will fail for quoted file paths with spaces because it will break on those spaces if (!string.IsNullOrWhiteSpace(input)) args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); var setBreakPointHere = args; #endif - var result = Parser.Default.ParseArguments(args, types); + var result = new Parser(ConfigureParser).ParseArguments(args, VerbTypes); - // if successfully parsed - // async: run parsed options - await result.WithParsedAsync(opt => opt.Run()); + if (result.Value is HelpVerb helper) + Console.Error.WriteLine(helper.GetHelpText()); + 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(opt => opt.Run()); + } } - private static void HandleErrors(ParserResult result, IEnumerable errors) + private static void HandleErrors(ParserResult result) { - var errorsList = errors.ToList(); + var errorsList = result.Errors.ToList(); if (errorsList.Any(e => e.Tag.In(ErrorType.HelpRequestedError, ErrorType.VersionRequestedError, ErrorType.HelpVerbRequestedError))) { Environment.ExitCode = (int)ExitCode.NonRunNonError; @@ -67,17 +80,36 @@ namespace LibationCli } Environment.ExitCode = (int)ExitCode.ParseError; + var helpText = HelpVerb.CreateHelpText(); - if (errorsList.Any(e => e.Tag.In(ErrorType.NoVerbSelectedError))) + if (errorsList.OfType().Any()) { - Console.Error.WriteLine("No verb selected"); - return; + //Print LibationCli usage + 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, - h => HelpText.DefaultParsingErrorsHandler(result, h), - e => e); - Console.WriteLine(helpText); + if (!errorsList.OfType().Any(o => o.Token.ToLower() == "help")) + { + //verb was not executed with the "--help" option, + //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; } } } diff --git a/Source/LibationCli/Setup.cs b/Source/LibationCli/Setup.cs index fc9e35ea..8c4f242e 100644 --- a/Source/LibationCli/Setup.cs +++ b/Source/LibationCli/Setup.cs @@ -1,14 +1,8 @@ -using System; -using System.Collections.Generic; +using AppScaffolding; +using CommandLine; +using System; using System.Linq; 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 { @@ -16,6 +10,11 @@ namespace LibationCli { 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 // @@ -26,28 +25,6 @@ namespace LibationCli LibationScaffolding.RunPostConfigMigrations(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()