diff --git a/Cmdline/Action/AuthToken.cs b/Cmdline/Action/AuthToken.cs index adb586ab1..3850a2923 100644 --- a/Cmdline/Action/AuthToken.cs +++ b/Cmdline/Action/AuthToken.cs @@ -135,31 +135,39 @@ internal class AuthTokenSubOptions : VerbCommandOptions [HelpVerbOption] public string GetUsage(string verb) { - HelpText ht = HelpText.AutoBuild(this, verb); + var ht = HelpText.AutoBuild(this, verb); + foreach (var h in GetHelp(verb)) + { + ht.AddPreOptionsLine(h); + } + return ht; + } + + public static IEnumerable GetHelp(string verb) + { // Add a usage prefix line - ht.AddPreOptionsLine(" "); + yield return " "; if (string.IsNullOrEmpty(verb)) { - ht.AddPreOptionsLine($"ckan authtoken - {Properties.Resources.AuthTokenHelpSummary}"); - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan authtoken <{Properties.Resources.Command}> [{Properties.Resources.Options}]"); + yield return $"ckan authtoken - {Properties.Resources.AuthTokenHelpSummary}"; + yield return $"{Properties.Resources.Usage}: ckan authtoken <{Properties.Resources.Command}> [{Properties.Resources.Options}]"; } else { - ht.AddPreOptionsLine("authtoken " + verb + " - " + GetDescription(verb)); + yield return "authtoken " + verb + " - " + GetDescription(typeof(AuthTokenSubOptions), verb); switch (verb) { case "add": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan authtoken {verb} [{Properties.Resources.Options}] host token"); + yield return $"{Properties.Resources.Usage}: ckan authtoken {verb} [{Properties.Resources.Options}] host token"; break; case "remove": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan authtoken {verb} [{Properties.Resources.Options}] host"); + yield return $"{Properties.Resources.Usage}: ckan authtoken {verb} [{Properties.Resources.Options}] host"; break; case "list": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan authtoken {verb} [{Properties.Resources.Options}]"); + yield return $"{Properties.Resources.Usage}: ckan authtoken {verb} [{Properties.Resources.Options}]"; break; } } - return ht; } } diff --git a/Cmdline/Action/Available.cs b/Cmdline/Action/Available.cs index 03eeac981..85a365998 100644 --- a/Cmdline/Action/Available.cs +++ b/Cmdline/Action/Available.cs @@ -1,5 +1,7 @@ using System.Linq; +using CommandLine; + namespace CKAN.CmdLine { public class Available : ICommand @@ -45,4 +47,11 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) private readonly IUser user; private readonly RepositoryDataManager repoData; } + + internal class AvailableOptions : InstanceSpecificOptions + { + [Option("detail", HelpText = "Show short description of each module")] + public bool detail { get; set; } + } + } diff --git a/Cmdline/Action/Cache.cs b/Cmdline/Action/Cache.cs index 50332e5bf..a8c4faaed 100644 --- a/Cmdline/Action/Cache.cs +++ b/Cmdline/Action/Cache.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + using CommandLine; using CommandLine.Text; using Autofac; @@ -29,25 +31,34 @@ public class CacheSubOptions : VerbCommandOptions [HelpVerbOption] public string GetUsage(string verb) { - HelpText ht = HelpText.AutoBuild(this, verb); + var ht = HelpText.AutoBuild(this, verb); + foreach (var h in GetHelp(verb)) + { + ht.AddPreOptionsLine(h); + } + return ht; + } + + public static IEnumerable GetHelp(string verb) + { // Add a usage prefix line - ht.AddPreOptionsLine(" "); + yield return " "; if (string.IsNullOrEmpty(verb)) { - ht.AddPreOptionsLine($"ckan cache - {Properties.Resources.CacheHelpSummary}"); - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan cache <{Properties.Resources.Command}> [{Properties.Resources.Options}]"); + yield return $"ckan cache - {Properties.Resources.CacheHelpSummary}"; + yield return $"{Properties.Resources.Usage}: ckan cache <{Properties.Resources.Command}> [{Properties.Resources.Options}]"; } else { - ht.AddPreOptionsLine("cache " + verb + " - " + GetDescription(verb)); + yield return "cache " + verb + " - " + GetDescription(typeof(CacheSubOptions), verb); switch (verb) { // First the commands with one string argument case "set": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan cache {verb} [{Properties.Resources.Options}] path"); + yield return $"{Properties.Resources.Usage}: ckan cache {verb} [{Properties.Resources.Options}] path"; break; case "setlimit": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan cache {verb} [{Properties.Resources.Options}] megabytes"); + yield return $"{Properties.Resources.Usage}: ckan cache {verb} [{Properties.Resources.Options}] megabytes"; break; // Now the commands with only --flag type options @@ -56,11 +67,10 @@ public string GetUsage(string verb) case "reset": case "showlimit": default: - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan cache {verb} [{Properties.Resources.Options}]"); + yield return $"{Properties.Resources.Usage}: ckan cache {verb} [{Properties.Resources.Options}]"; break; } } - return ht; } } @@ -158,7 +168,8 @@ private int SetCacheDirectory(SetOptions options) { if (string.IsNullOrEmpty(options.Path)) { - user.RaiseError("set <{0}> - {1}", Properties.Resources.Path, Properties.Resources.ArgumentMissing); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage("set"); return Exit.BADOPT; } @@ -231,6 +242,14 @@ private void printCacheInfo() CkanModule.FmtSize(bytesFree)); } + private void PrintUsage(string verb) + { + foreach (var h in CacheSubOptions.GetHelp(verb)) + { + user.RaiseError(h); + } + } + private IUser user; private GameInstanceManager manager; } diff --git a/Cmdline/Action/Compare.cs b/Cmdline/Action/Compare.cs index b66d4d50e..85d6c028b 100644 --- a/Cmdline/Action/Compare.cs +++ b/Cmdline/Action/Compare.cs @@ -1,3 +1,5 @@ +using CommandLine; + using CKAN.Versioning; namespace CKAN.CmdLine @@ -46,11 +48,25 @@ public int RunCommand(object rawOptions) } else { - user.RaiseMessage("{0}: ckan compare version1 version2", Properties.Resources.Usage); + user.RaiseError(Properties.Resources.ArgumentMissing); + foreach (var h in Actions.GetHelp("compare")) + { + user.RaiseError(h); + } return Exit.BADOPT; } return Exit.OK; } } + + internal class CompareOptions : CommonOptions + { + [Option("machine-readable", HelpText = "Output in a machine readable format: -1, 0 or 1")] + public bool machine_readable { get; set;} + + [ValueOption(0)] public string Left { get; set; } + [ValueOption(1)] public string Right { get; set; } + } + } diff --git a/Cmdline/Action/Compat.cs b/Cmdline/Action/Compat.cs index 734c3310f..c6380e97e 100644 --- a/Cmdline/Action/Compat.cs +++ b/Cmdline/Action/Compat.cs @@ -1,4 +1,6 @@ +using System; using System.Linq; +using System.Collections.Generic; using CommandLine; using CommandLine.Text; @@ -7,80 +9,106 @@ namespace CKAN.CmdLine { - public class CompatOptions : VerbCommandOptions + public class CompatSubOptions : VerbCommandOptions { - [VerbOption("list", HelpText = "List compatible KSP versions")] + [VerbOption("list", HelpText = "List compatible game versions")] public CompatListOptions List { get; set; } - [VerbOption("add", HelpText = "Add version to KSP compatibility list")] + [VerbOption("clear", HelpText = "Forget all compatible game versions")] + public CompatClearOptions Clear { get; set; } + + [VerbOption("add", HelpText = "Add versions to compatible game versions list")] public CompatAddOptions Add { get; set; } - [VerbOption("forget", HelpText = "Forget version on KSP compatibility list")] + [VerbOption("forget", HelpText = "Forget compatible game versions")] public CompatForgetOptions Forget { get; set; } + [VerbOption("set", HelpText = "Set the compatible game versions list")] + public CompatSetOptions Set { get; set; } + [HelpVerbOption] public string GetUsage(string verb) { - HelpText ht = HelpText.AutoBuild(this, verb); + var ht = HelpText.AutoBuild(this, verb); + foreach (var h in GetHelp(verb)) + { + ht.AddPreOptionsLine(h); + } + return ht; + } + + public static IEnumerable GetHelp(string verb) + { // Add a usage prefix line - ht.AddPreOptionsLine(" "); + yield return " "; if (string.IsNullOrEmpty(verb)) { - ht.AddPreOptionsLine($"ckan compat - {Properties.Resources.CompatHelpSummary}"); - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan compat <{Properties.Resources.Command}> [{Properties.Resources.Options}]"); + yield return $"ckan compat - {Properties.Resources.CompatHelpSummary}"; + yield return $"{Properties.Resources.Usage}: ckan compat <{Properties.Resources.Command}> [{Properties.Resources.Options}]"; } else { - ht.AddPreOptionsLine("compat " + verb + " - " + GetDescription(verb)); + yield return "compat " + verb + " - " + GetDescription(typeof(CompatSubOptions), verb); switch (verb) { // First the commands with one string argument case "add": + case "set": case "forget": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan compat {verb} [{Properties.Resources.Options}] version"); + yield return $"{Properties.Resources.Usage}: ckan compat {verb} [{Properties.Resources.Options}] version [version2 ...]"; break; // Now the commands with only --flag type options case "list": + case "clear": default: - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan compat {verb} [{Properties.Resources.Options}]"); + yield return $"{Properties.Resources.Usage}: ckan compat {verb} [{Properties.Resources.Options}]"; break; } } - return ht; } } public class CompatListOptions : InstanceSpecificOptions { } + public class CompatClearOptions : InstanceSpecificOptions { } + public class CompatAddOptions : InstanceSpecificOptions { - [ValueOption(0)] public string Version { get; set; } + [ValueList(typeof(List))] + public List Versions { get; set; } } public class CompatForgetOptions : InstanceSpecificOptions { - [ValueOption(0)] public string Version { get; set; } + [ValueList(typeof(List))] + public List Versions { get; set; } } - public class Compat : ISubCommand + public class CompatSetOptions : InstanceSpecificOptions { - public Compat() { } + [ValueList(typeof(List))] + public List Versions { get; set; } + } - public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCommandOptions options) + public class Compat : ISubCommand + { + public int RunSubCommand(GameInstanceManager mgr, + CommonOptions opts, + SubCommandOptions options) { var exitCode = Exit.OK; - Parser.Default.ParseArgumentsStrict(options.options.ToArray(), new CompatOptions(), (string option, object suboptions) => + Parser.Default.ParseArgumentsStrict(options.options.ToArray(), new CompatSubOptions(), (string option, object suboptions) => { // ParseArgumentsStrict calls us unconditionally, even with bad arguments if (!string.IsNullOrEmpty(option) && suboptions != null) { CommonOptions comOpts = (CommonOptions)suboptions; comOpts.Merge(opts); - _user = new ConsoleUser(comOpts.Headless); - _kspManager = manager ?? new GameInstanceManager(_user); - exitCode = comOpts.Handle(_kspManager, _user); + user = new ConsoleUser(comOpts.Headless); + manager = mgr ?? new GameInstanceManager(user); + exitCode = comOpts.Handle(manager, user); if (exitCode != Exit.OK) { return; @@ -89,108 +117,36 @@ public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCom switch (option) { case "list": - { - var instance = MainClass.GetGameInstance(_kspManager); - - string versionHeader = Properties.Resources.CompatVersionHeader; - string actualHeader = Properties.Resources.CompatActualHeader; - - var output = instance - .GetCompatibleVersions() - .Select(i => new - { - Version = i, - Actual = false - }) - .ToList(); - - output.Add(new - { - Version = instance.Version(), - Actual = true - }); - - output = output - .OrderByDescending(i => i.Actual) - .ThenByDescending(i => i.Version) - .ToList(); - - var versionWidth = Enumerable - .Repeat(versionHeader, 1) - .Concat(output.Select(i => i.Version.ToString())) - .Max(i => i.Length); - - var actualWidth = Enumerable - .Repeat(actualHeader, 1) - .Concat(output.Select(i => i.Actual.ToString())) - .Max(i => i.Length); - - const string columnFormat = "{0} {1}"; - - _user.RaiseMessage(string.Format(columnFormat, - versionHeader.PadRight(versionWidth), - actualHeader.PadRight(actualWidth) - )); - - _user.RaiseMessage(string.Format(columnFormat, - new string('-', versionWidth), - new string('-', actualWidth) - )); - - foreach (var line in output) - { - _user.RaiseMessage(columnFormat, - line.Version.ToString().PadRight(versionWidth), - line.Actual.ToString().PadRight(actualWidth) - ); - } - } + exitCode = List(MainClass.GetGameInstance(manager)) + ? Exit.OK + : Exit.ERROR; + break; + + case "clear": + exitCode = Clear(MainClass.GetGameInstance(manager)) + ? Exit.OK + : Exit.ERROR; break; case "add": - { - var instance = MainClass.GetGameInstance(_kspManager); - var addOptions = (CompatAddOptions)suboptions; - - if (GameVersion.TryParse(addOptions.Version, out GameVersion gv)) - { - var newCompatibleVersion = instance.GetCompatibleVersions(); - newCompatibleVersion.Add(gv); - instance.SetCompatibleVersions(newCompatibleVersion); - } - else - { - _user.RaiseError(Properties.Resources.CompatInvalid); - exitCode = Exit.ERROR; - } - } + exitCode = Add(suboptions as CompatAddOptions, + MainClass.GetGameInstance(manager)) + ? Exit.OK + : Exit.ERROR; break; case "forget": - { - var instance = MainClass.GetGameInstance(_kspManager); - var addOptions = (CompatForgetOptions)suboptions; - - if (GameVersion.TryParse(addOptions.Version, out GameVersion gv)) - { - if (gv != instance.Version()) - { - var newCompatibleVersion = instance.GetCompatibleVersions(); - newCompatibleVersion.RemoveAll(i => i == gv); - instance.SetCompatibleVersions(newCompatibleVersion); - } - else - { - _user.RaiseError(Properties.Resources.CompatCantForget); - exitCode = Exit.ERROR; - } - } - else - { - _user.RaiseError(Properties.Resources.CompatInvalid); - exitCode = Exit.ERROR; - } - } + exitCode = Forget(suboptions as CompatForgetOptions, + MainClass.GetGameInstance(manager)) + ? Exit.OK + : Exit.ERROR; + break; + + case "set": + exitCode = Set(suboptions as CompatSetOptions, + MainClass.GetGameInstance(manager)) + ? Exit.OK + : Exit.ERROR; break; default: @@ -202,7 +158,169 @@ public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCom return exitCode; } - private IUser _user; - private GameInstanceManager _kspManager; + private bool List(CKAN.GameInstance instance) + { + var versionHeader = Properties.Resources.CompatVersionHeader; + var actualHeader = Properties.Resources.CompatActualHeader; + var output = Enumerable.Repeat(new + { + Version = instance.Version(), + Actual = true, + }, + 1) + .Concat(instance.GetCompatibleVersions() + .OrderByDescending(v => v) + .Select(v => new + { + Version = v, + Actual = false, + })) + .ToList(); + + var versionWidth = Enumerable.Repeat(versionHeader, 1) + .Concat(output.Select(i => i.Version.ToString())) + .Max(i => i.Length); + + var actualWidth = Enumerable.Repeat(actualHeader, 1) + .Concat(output.Select(i => i.Actual.ToString())) + .Max(i => i.Length); + + const string columnFormat = "{0} {1}"; + + user.RaiseMessage(columnFormat, + versionHeader.PadRight(versionWidth), + actualHeader.PadRight(actualWidth)); + + user.RaiseMessage(columnFormat, + new string('-', versionWidth), + new string('-', actualWidth)); + + foreach (var line in output) + { + user.RaiseMessage(columnFormat, + line.Version.ToString() + .PadRight(versionWidth), + line.Actual.ToString() + .PadRight(actualWidth)); + } + return true; + } + + private bool Clear(CKAN.GameInstance instance) + { + instance.SetCompatibleVersions(new List()); + List(instance); + return true; + } + + private bool Add(CompatAddOptions addOptions, + CKAN.GameInstance instance) + { + if (addOptions.Versions.Count < 1) + { + user.RaiseError(Properties.Resources.CompatMissing); + PrintUsage("add"); + return false; + } + if (!TryParseVersions(addOptions.Versions, + out GameVersion[] goodVers, + out string[] badVers)) + { + user.RaiseError(Properties.Resources.CompatInvalid, + string.Join(", ", badVers)); + return false; + } + instance.SetCompatibleVersions(instance.GetCompatibleVersions() + .Concat(goodVers) + .Distinct() + .ToList()); + List(instance); + return true; + } + + private bool Forget(CompatForgetOptions forgetOptions, + CKAN.GameInstance instance) + { + if (forgetOptions.Versions.Count < 1) + { + user.RaiseError(Properties.Resources.CompatMissing); + PrintUsage("forget"); + return false; + } + if (!TryParseVersions(forgetOptions.Versions, + out GameVersion[] goodVers, + out string[] badVers)) + { + user.RaiseError(Properties.Resources.CompatInvalid, + string.Join(", ", badVers)); + return false; + } + var rmActualVers = goodVers.Intersect(new GameVersion[] { instance.Version(), + instance.Version().WithoutBuild }) + .Select(gv => gv.ToString()) + .ToArray(); + if (rmActualVers.Length > 0) + { + user.RaiseError(Properties.Resources.CompatCantForget, + string.Join(", ", rmActualVers)); + return false; + } + instance.SetCompatibleVersions(instance.GetCompatibleVersions() + .Except(goodVers) + .ToList()); + List(instance); + return true; + } + + private bool Set(CompatSetOptions setOptions, + CKAN.GameInstance instance) + { + if (setOptions.Versions.Count < 1) + { + user.RaiseError(Properties.Resources.CompatMissing); + PrintUsage("set"); + return false; + } + if (!TryParseVersions(setOptions.Versions, + out GameVersion[] goodVers, + out string[] badVers)) + { + user.RaiseError(Properties.Resources.CompatInvalid, + string.Join(", ", badVers)); + return false; + } + instance.SetCompatibleVersions(goodVers.Distinct().ToList()); + List(instance); + return true; + } + + private void PrintUsage(string verb) + { + foreach (var h in CompatSubOptions.GetHelp(verb)) + { + user.RaiseError(h); + } + } + + private static bool TryParseVersions(IEnumerable versions, + out GameVersion[] goodVers, + out string[] badVers) + { + var gameVersions = versions + .Select(v => GameVersion.TryParse(v, out GameVersion gv) + ? new Tuple(v, gv) + : new Tuple(v, null)) + .ToArray(); + goodVers = gameVersions.Select(tuple => tuple.Item2) + .OfType() + .ToArray(); + badVers = gameVersions.Where(tuple => tuple.Item2 == null) + .Select(tuple => tuple.Item1) + .ToArray(); + return badVers.Length < 1; + } + + private IUser user; + private GameInstanceManager manager; } } diff --git a/Cmdline/Action/Filter.cs b/Cmdline/Action/Filter.cs index 0c21b97e0..253edd115 100644 --- a/Cmdline/Action/Filter.cs +++ b/Cmdline/Action/Filter.cs @@ -101,7 +101,8 @@ private int AddFilters(FilterAddOptions opts, string verb) { if (opts.filters.Count < 1) { - user.RaiseMessage("{0}: ckan filter {1} filter1 [filter2 ...]", Properties.Resources.Usage, verb); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage(verb); return Exit.BADOPT; } @@ -162,7 +163,8 @@ private int RemoveFilters(FilterRemoveOptions opts, string verb) { if (opts.filters.Count < 1) { - user.RaiseMessage("{0}: ckan filter {1} filter1 [filter2 ...]", Properties.Resources.Usage, verb); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage(verb); return Exit.BADOPT; } @@ -217,6 +219,14 @@ private int RemoveFilters(FilterRemoveOptions opts, string verb) return Exit.OK; } + private void PrintUsage(string verb) + { + foreach (var h in FilterSubOptions.GetHelp(verb)) + { + user.RaiseError(h); + } + } + private GameInstanceManager manager; private IUser user; } @@ -235,33 +245,41 @@ internal class FilterSubOptions : VerbCommandOptions [HelpVerbOption] public string GetUsage(string verb) { - HelpText ht = HelpText.AutoBuild(this, verb); + var ht = HelpText.AutoBuild(this, verb); + foreach (var h in GetHelp(verb)) + { + ht.AddPreOptionsLine(h); + } + return ht; + } + + public static IEnumerable GetHelp(string verb) + { // Add a usage prefix line - ht.AddPreOptionsLine(" "); + yield return " "; if (string.IsNullOrEmpty(verb)) { - ht.AddPreOptionsLine($"ckan filter - {Properties.Resources.FilterHelpSummary}"); - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan filter <{Properties.Resources.Command}> [{Properties.Resources.Options}]"); + yield return $"ckan filter - {Properties.Resources.FilterHelpSummary}"; + yield return $"{Properties.Resources.Usage}: ckan filter <{Properties.Resources.Command}> [{Properties.Resources.Options}]"; } else { - ht.AddPreOptionsLine("filter " + verb + " - " + GetDescription(verb)); + yield return "filter " + verb + " - " + GetDescription(typeof(FilterSubOptions), verb); switch (verb) { case "list": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan filter {verb}"); + yield return $"{Properties.Resources.Usage}: ckan filter {verb}"; break; case "add": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan filter {verb} [{Properties.Resources.Options}] filter1 [filter2 ...]"); + yield return $"{Properties.Resources.Usage}: ckan filter {verb} [{Properties.Resources.Options}] filter1 [filter2 ...]"; break; case "remove": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan filter {verb} [{Properties.Resources.Options}] filter1 [filter2 ...]"); + yield return $"{Properties.Resources.Usage}: ckan filter {verb} [{Properties.Resources.Options}] filter1 [filter2 ...]"; break; } } - return ht; } } diff --git a/Cmdline/Action/GameInstance.cs b/Cmdline/Action/GameInstance.cs index afcdabe4f..335d909a9 100644 --- a/Cmdline/Action/GameInstance.cs +++ b/Cmdline/Action/GameInstance.cs @@ -39,34 +39,43 @@ internal class InstanceSubOptions : VerbCommandOptions [HelpVerbOption] public string GetUsage(string verb) { - HelpText ht = HelpText.AutoBuild(this, verb); + var ht = HelpText.AutoBuild(this, verb); + foreach (var h in GetHelp(verb)) + { + ht.AddPreOptionsLine(h); + } + return ht; + } + + public static IEnumerable GetHelp(string verb) + { // Add a usage prefix line - ht.AddPreOptionsLine(" "); + yield return " "; if (string.IsNullOrEmpty(verb)) { - ht.AddPreOptionsLine($"ckan instance - {Properties.Resources.InstanceHelpSummary}"); - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan instance <{Properties.Resources.Command}> [{Properties.Resources.Options}]"); + yield return $"ckan instance - {Properties.Resources.InstanceHelpSummary}"; + yield return $"{Properties.Resources.Usage}: ckan instance <{Properties.Resources.Command}> [{Properties.Resources.Options}]"; } else { - ht.AddPreOptionsLine("instance " + verb + " - " + GetDescription(verb)); + yield return "instance " + verb + " - " + GetDescription(typeof(InstanceSubOptions), verb); switch (verb) { // First the commands with three string arguments case "fake": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] name path version [--game KSP|KSP2] [--MakingHistory ] [--BreakingGround ]"); + yield return $"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] name path version [--game KSP|KSP2] [--MakingHistory ] [--BreakingGround ]"; break; case "clone": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] instanceNameOrPath newname newpath"); + yield return $"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] instanceNameOrPath newname newpath"; break; // Second the commands with two string arguments case "add": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] name url"); + yield return $"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] name url"; break; case "rename": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] oldname newname"); + yield return $"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] oldname newname"; break; // Now the commands with one string argument @@ -74,18 +83,17 @@ public string GetUsage(string verb) case "forget": case "use": case "default": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] name"); + yield return $"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}] name"; break; // Now the commands with only --flag type options case "list": default: - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}]"); + yield return $"{Properties.Resources.Usage}: ckan instance {verb} [{Properties.Resources.Options}]"; break; } } - return ht; } } @@ -178,9 +186,9 @@ public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCom { CommonOptions options = (CommonOptions)suboptions; options.Merge(opts); - User = new ConsoleUser(options.Headless); - Manager = manager ?? new GameInstanceManager(User); - exitCode = options.Handle(Manager, User); + user = new ConsoleUser(options.Headless); + Manager = manager ?? new GameInstanceManager(user); + exitCode = options.Handle(Manager, user); if (exitCode != Exit.OK) { return; @@ -218,7 +226,7 @@ public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCom break; default: - User.RaiseMessage("{0}: instance {1}", Properties.Resources.UnknownCommand, option); + user.RaiseMessage("{0}: instance {1}", Properties.Resources.UnknownCommand, option); exitCode = Exit.BADOPT; break; } @@ -227,7 +235,7 @@ public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCom return exitCode; } - private IUser User { get; set; } + private IUser user { get; set; } private GameInstanceManager Manager { get; set; } #region option functions @@ -261,14 +269,14 @@ private int ListInstalls() const string columnFormat = "{0} {1} {2} {3}"; - User.RaiseMessage(columnFormat, + user.RaiseMessage(columnFormat, nameHeader.PadRight(nameWidth), versionHeader.PadRight(versionWidth), defaultHeader.PadRight(defaultWidth), pathHeader.PadRight(pathWidth) ); - User.RaiseMessage(columnFormat, + user.RaiseMessage(columnFormat, new string('-', nameWidth), new string('-', versionWidth), new string('-', defaultWidth), @@ -277,7 +285,7 @@ private int ListInstalls() foreach (var line in output) { - User.RaiseMessage(columnFormat, + user.RaiseMessage(columnFormat, line.Name.PadRight(nameWidth), line.Version.PadRight(versionWidth), line.Default.PadRight(defaultWidth), @@ -292,26 +300,27 @@ private int AddInstall(AddOptions options) { if (options.name == null || options.path == null) { - User.RaiseMessage("add <{0}> - {1}", Properties.Resources.Path, Properties.Resources.ArgumentMissing); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage("add"); return Exit.BADOPT; } if (Manager.HasInstance(options.name)) { - User.RaiseMessage(Properties.Resources.InstanceAddDuplicate, options.name); + user.RaiseMessage(Properties.Resources.InstanceAddDuplicate, options.name); return Exit.BADOPT; } try { string path = options.path; - Manager.AddInstance(path, options.name, User); - User.RaiseMessage(Properties.Resources.InstanceAdded, options.name, options.path); + Manager.AddInstance(path, options.name, user); + user.RaiseMessage(Properties.Resources.InstanceAdded, options.name, options.path); return Exit.OK; } catch (NotKSPDirKraken ex) { - User.RaiseMessage(Properties.Resources.InstanceNotInstance, ex.path); + user.RaiseMessage(Properties.Resources.InstanceNotInstance, ex.path); return Exit.BADOPT; } } @@ -320,8 +329,8 @@ private int CloneInstall(CloneOptions options) { if (options.nameOrPath == null || options.new_name == null || options.new_path == null) { - User.RaiseMessage("instance clone - {0}", - Properties.Resources.ArgumentMissing); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage("clone"); return Exit.BADOPT; } @@ -389,13 +398,13 @@ private int CloneInstall(CloneOptions options) } catch (NoGameInstanceKraken) { - User.RaiseError(Properties.Resources.InstanceCloneNotFound, instanceNameOrPath); + user.RaiseError(Properties.Resources.InstanceCloneNotFound, instanceNameOrPath); ListInstalls(); return Exit.ERROR; } catch (InstanceNameTakenKraken kraken) { - User.RaiseError(Properties.Resources.InstanceDuplicate, kraken.instName); + user.RaiseError(Properties.Resources.InstanceDuplicate, kraken.instName); return Exit.BADOPT; } @@ -408,7 +417,7 @@ private int CloneInstall(CloneOptions options) } else { - User.RaiseMessage(Properties.Resources.InstanceCloneFailed); + user.RaiseMessage(Properties.Resources.InstanceCloneFailed); return Exit.ERROR; } } @@ -417,19 +426,20 @@ private int RenameInstall(RenameOptions options) { if (options.old_name == null || options.new_name == null) { - User.RaiseMessage("rename - {0}", Properties.Resources.ArgumentMissing); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage("rename"); return Exit.BADOPT; } if (!Manager.HasInstance(options.old_name)) { - User.RaiseMessage(Properties.Resources.InstanceNotFound, options.old_name); + user.RaiseMessage(Properties.Resources.InstanceNotFound, options.old_name); return Exit.BADOPT; } Manager.RenameInstance(options.old_name, options.new_name); - User.RaiseMessage(Properties.Resources.InstanceRenamed, options.old_name, options.new_name); + user.RaiseMessage(Properties.Resources.InstanceRenamed, options.old_name, options.new_name); return Exit.OK; } @@ -437,19 +447,20 @@ private int ForgetInstall(ForgetOptions options) { if (options.name == null) { - User.RaiseMessage("forget - {0}", Properties.Resources.ArgumentMissing); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage("forget"); return Exit.BADOPT; } if (!Manager.HasInstance(options.name)) { - User.RaiseMessage(Properties.Resources.InstanceNotFound, options.name); + user.RaiseMessage(Properties.Resources.InstanceNotFound, options.name); return Exit.BADOPT; } Manager.RemoveInstance(options.name); - User.RaiseMessage(Properties.Resources.InstanceForgot, options.name); + user.RaiseMessage(Properties.Resources.InstanceForgot, options.name); return Exit.OK; } @@ -491,7 +502,7 @@ private int SetDefaultInstall(DefaultOptions options) try { - result = User.RaiseSelectionDialog(message, keys); + result = user.RaiseSelectionDialog(message, keys); } catch (Kraken) { @@ -508,7 +519,7 @@ private int SetDefaultInstall(DefaultOptions options) if (!Manager.Instances.ContainsKey(name)) { - User.RaiseMessage(Properties.Resources.InstanceNotFound, name); + user.RaiseMessage(Properties.Resources.InstanceNotFound, name); return Exit.BADOPT; } @@ -518,11 +529,11 @@ private int SetDefaultInstall(DefaultOptions options) } catch (NotKSPDirKraken k) { - User.RaiseMessage(Properties.Resources.InstanceNotInstance, k.path); + user.RaiseMessage(Properties.Resources.InstanceNotInstance, k.path); return Exit.BADOPT; } - User.RaiseMessage(Properties.Resources.InstanceDefaultSet, name); + user.RaiseMessage(Properties.Resources.InstanceDefaultSet, name); return Exit.OK; } @@ -541,18 +552,16 @@ int error() int badArgument() { log.Debug("Instance faking failed: bad argument(s). See console output for details."); - User.RaiseMessage(Properties.Resources.InstanceFakeBadArguments); + user.RaiseMessage(Properties.Resources.InstanceFakeBadArguments); return Exit.BADOPT; } if (options.name == null || options.path == null || options.version == null) { - User.RaiseMessage("instance fake <{0}> " + - "[--game KSP|KSP2] " + - "[--MakingHistory ] [--BreakingGround ] - {1}", - Properties.Resources.Path, Properties.Resources.ArgumentMissing); - return badArgument(); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage("fake"); + return Exit.BADOPT; } log.Debug("Parsing arguments..."); @@ -564,7 +573,7 @@ int badArgument() IGame game = KnownGames.GameByShortName(options.gameId); if (game == null) { - User.RaiseMessage(Properties.Resources.InstanceFakeBadGame, options.gameId); + user.RaiseMessage(Properties.Resources.InstanceFakeBadGame, options.gameId); return badArgument(); } @@ -578,7 +587,7 @@ int badArgument() } else { - User.RaiseError(Properties.Resources.InstanceFakeMakingHistory); + user.RaiseError(Properties.Resources.InstanceFakeMakingHistory); return badArgument(); } } @@ -590,7 +599,7 @@ int badArgument() } else { - User.RaiseError(Properties.Resources.InstanceFakeBreakingGround); + user.RaiseError(Properties.Resources.InstanceFakeBreakingGround); return badArgument(); } } @@ -603,27 +612,27 @@ int badArgument() catch (FormatException) { // Thrown if there is anything besides numbers and points in the version string or a different syntactic error. - User.RaiseError(Properties.Resources.InstanceFakeVersion); + user.RaiseError(Properties.Resources.InstanceFakeVersion); return badArgument(); } // Get the full version including build number. try { - version = version.RaiseVersionSelectionDialog(game, User); + version = version.RaiseVersionSelectionDialog(game, user); } catch (BadGameVersionKraken) { - User.RaiseError(Properties.Resources.InstanceFakeBadGameVersion); + user.RaiseError(Properties.Resources.InstanceFakeBadGameVersion); return badArgument(); } catch (CancelledActionKraken) { - User.RaiseError(Properties.Resources.InstanceFakeCancelled); + user.RaiseError(Properties.Resources.InstanceFakeCancelled); return error(); } - User.RaiseMessage(Properties.Resources.InstanceFakeCreating, + user.RaiseMessage(Properties.Resources.InstanceFakeCreating, installName, path, version.ToString()); log.Debug("Faking instance..."); @@ -633,25 +642,25 @@ int badArgument() Manager.FakeInstance(game, installName, path, version, dlcs); if (setDefault) { - User.RaiseMessage(Properties.Resources.InstanceFakeDefault); + user.RaiseMessage(Properties.Resources.InstanceFakeDefault); Manager.SetAutoStart(installName); } } catch (InstanceNameTakenKraken kraken) { - User.RaiseError(Properties.Resources.InstanceDuplicate, kraken.instName); + user.RaiseError(Properties.Resources.InstanceDuplicate, kraken.instName); return badArgument(); } catch (BadInstallLocationKraken kraken) { // The folder exists and is not empty. - User.RaiseError("{0}", kraken.Message); + user.RaiseError("{0}", kraken.Message); return badArgument(); } catch (WrongGameVersionKraken kraken) { // Thrown because the specified game instance is too old for one of the selected DLCs. - User.RaiseError("{0}", kraken.Message); + user.RaiseError("{0}", kraken.Message); return badArgument(); } catch (NotKSPDirKraken kraken) @@ -659,7 +668,7 @@ int badArgument() // Something went wrong adding the new instance to the registry, // most likely because the newly created directory is somehow not valid. log.Error(kraken); - User.RaiseError("{0}", kraken.Message); + user.RaiseError("{0}", kraken.Message); return error(); } catch (InvalidKSPInstanceKraken) @@ -673,15 +682,24 @@ int badArgument() // No need to test if valid, because this is done in AddInstance(). if (Manager.HasInstance(installName)) { - User.RaiseMessage(Properties.Resources.InstanceFakeDone); + user.RaiseMessage(Properties.Resources.InstanceFakeDone); return Exit.OK; } else { - User.RaiseError(Properties.Resources.InstanceFakeFailed); + user.RaiseError(Properties.Resources.InstanceFakeFailed); return error(); } } #endregion + + private void PrintUsage(string verb) + { + foreach (var h in InstanceSubOptions.GetHelp(verb)) + { + user.RaiseError(h); + } + } + } } diff --git a/Cmdline/Action/Import.cs b/Cmdline/Action/Import.cs index 7d23af6b4..87de99e65 100644 --- a/Cmdline/Action/Import.cs +++ b/Cmdline/Action/Import.cs @@ -2,6 +2,7 @@ using System.IO; using System.Collections.Generic; +using CommandLine; using log4net; namespace CKAN.CmdLine @@ -39,7 +40,11 @@ public int RunCommand(CKAN.GameInstance instance, object options) HashSet toImport = GetFiles(opts); if (toImport.Count < 1) { - user.RaiseMessage($"{Properties.Resources.Usage}: ckan import {Properties.Resources.Path} [path2, ...]"); + user.RaiseError(Properties.Resources.ArgumentMissing); + foreach (var h in Actions.GetHelp("import")) + { + user.RaiseError(h); + } return Exit.ERROR; } else @@ -109,4 +114,10 @@ private void AddFile(HashSet files, string filename) private static readonly ILog log = LogManager.GetLogger(typeof(Import)); } + internal class ImportOptions : InstanceSpecificOptions + { + [ValueList(typeof(List))] + public List paths { get; set; } + } + } diff --git a/Cmdline/Action/Install.cs b/Cmdline/Action/Install.cs index d9ac81f41..0f6df7bec 100644 --- a/Cmdline/Action/Install.cs +++ b/Cmdline/Action/Install.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using CommandLine; using log4net; namespace CKAN.CmdLine @@ -34,12 +35,11 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) var options = raw_options as InstallOptions; if (options.modules.Count == 0 && options.ckan_files == null) { - // What? No mods specified? - user.RaiseMessage("{0}:", Properties.Resources.Usage); - user.RaiseMessage( - " ckan install Mod [Mod2, ...] [--with-suggests] [--with-all-suggests] [--no-recommends]"); - user.RaiseMessage( - " ckan install -c file_or_url.ckan [file_or_url2.ckan, ...] [--with-suggests] [--with-all-suggests] [--no-recommends]"); + user.RaiseError(Properties.Resources.ArgumentMissing); + foreach (var h in Actions.GetHelp("install")) + { + user.RaiseError(h); + } return Exit.BADOPT; } @@ -257,4 +257,27 @@ private Uri getUri(string arg) private static readonly ILog log = LogManager.GetLogger(typeof(Install)); } + + internal class InstallOptions : InstanceSpecificOptions + { + [OptionArray('c', "ckanfiles", HelpText = "Local CKAN files or URLs to process")] + public string[] ckan_files { get; set; } + + [Option("no-recommends", DefaultValue = false, HelpText = "Do not install recommended modules")] + public bool no_recommends { get; set; } + + [Option("with-suggests", DefaultValue = false, HelpText = "Install suggested modules")] + public bool with_suggests { get; set; } + + [Option("with-all-suggests", DefaultValue = false, HelpText = "Install suggested modules all the way down")] + public bool with_all_suggests { get; set; } + + [Option("allow-incompatible", DefaultValue = false, HelpText = "Install modules that are not compatible with the current game version")] + public bool allow_incompatible { get; set; } + + [ValueList(typeof(List))] + [AvailableIdentifiers] + public List modules { get; set; } + } + } diff --git a/Cmdline/Action/List.cs b/Cmdline/Action/List.cs index 051d33d5c..c3fc8cb8f 100644 --- a/Cmdline/Action/List.cs +++ b/Cmdline/Action/List.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; +using CommandLine; using log4net; using CKAN.Exporters; @@ -177,4 +178,14 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) private static readonly ILog log = LogManager.GetLogger(typeof(List)); } + + internal class ListOptions : InstanceSpecificOptions + { + [Option("porcelain", HelpText = "Dump raw list of modules, good for shell scripting")] + public bool porcelain { get; set; } + + [Option("export", HelpText = "Export list of modules in specified format to stdout")] + public string export { get; set; } + } + } diff --git a/Cmdline/Action/Mark.cs b/Cmdline/Action/Mark.cs index ead13ffc3..d0e7d36ee 100644 --- a/Cmdline/Action/Mark.cs +++ b/Cmdline/Action/Mark.cs @@ -72,7 +72,8 @@ private int MarkAuto(MarkAutoOptions opts, bool value, string verb, string descr { if (opts.modules.Count < 1) { - user.RaiseMessage("{0}: ckan mark {1} Mod [Mod2 ...]", Properties.Resources.Usage, verb); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage(verb); return Exit.BADOPT; } @@ -120,6 +121,14 @@ private int MarkAuto(MarkAutoOptions opts, bool value, string verb, string descr return Exit.OK; } + private void PrintUsage(string verb) + { + foreach (var h in MarkSubOptions.GetHelp(verb)) + { + user.RaiseError(h); + } + } + private GameInstanceManager manager; private readonly RepositoryDataManager repoData; private IUser user; @@ -136,29 +145,37 @@ internal class MarkSubOptions : VerbCommandOptions [HelpVerbOption] public string GetUsage(string verb) { - HelpText ht = HelpText.AutoBuild(this, verb); + var ht = HelpText.AutoBuild(this, verb); + foreach (var h in GetHelp(verb)) + { + ht.AddPreOptionsLine(h); + } + return ht; + } + + public static IEnumerable GetHelp(string verb) + { // Add a usage prefix line - ht.AddPreOptionsLine(" "); + yield return " "; if (string.IsNullOrEmpty(verb)) { - ht.AddPreOptionsLine($"ckan mark - {Properties.Resources.MarkHelpSummary}"); - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan mark <{Properties.Resources.Command}> [{Properties.Resources.Options}]"); + yield return $"ckan mark - {Properties.Resources.MarkHelpSummary}"; + yield return $"{Properties.Resources.Usage}: ckan mark <{Properties.Resources.Command}> [{Properties.Resources.Options}]"; } else { - ht.AddPreOptionsLine("mark " + verb + " - " + GetDescription(verb)); + yield return "mark " + verb + " - " + GetDescription(typeof(MarkSubOptions), verb); switch (verb) { case "auto": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan mark {verb} [{Properties.Resources.Options}] Mod [Mod2 ...]"); + yield return $"{Properties.Resources.Usage}: ckan mark {verb} [{Properties.Resources.Options}] Mod [Mod2 ...]"; break; case "user": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan mark {verb} [{Properties.Resources.Options}] Mod [Mod2 ...]"); + yield return $"{Properties.Resources.Usage}: ckan mark {verb} [{Properties.Resources.Options}] Mod [Mod2 ...]"; break; } } - return ht; } } diff --git a/Cmdline/Action/Remove.cs b/Cmdline/Action/Remove.cs index e0634f313..4a3ce31d8 100644 --- a/Cmdline/Action/Remove.cs +++ b/Cmdline/Action/Remove.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text.RegularExpressions; +using CommandLine; using log4net; namespace CKAN.CmdLine @@ -107,7 +108,11 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) } else { - user.RaiseMessage(Properties.Resources.RemoveNothing); + user.RaiseError(Properties.Resources.ArgumentMissing); + foreach (var h in Actions.GetHelp("remove")) + { + user.RaiseError(h); + } return Exit.BADOPT; } @@ -120,4 +125,18 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) private static readonly ILog log = LogManager.GetLogger(typeof(Remove)); } + + internal class RemoveOptions : InstanceSpecificOptions + { + [Option("re", HelpText = "Parse arguments as regular expressions")] + public bool regex { get; set; } + + [ValueList(typeof(List))] + [InstalledIdentifiers] + public List modules { get; set; } + + [Option("all", DefaultValue = false, HelpText = "Remove all installed mods.")] + public bool rmall { get; set; } + } + } diff --git a/Cmdline/Action/Repair.cs b/Cmdline/Action/Repair.cs index 198f4dec6..cd89cccbc 100644 --- a/Cmdline/Action/Repair.cs +++ b/Cmdline/Action/Repair.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + using CommandLine; using CommandLine.Text; @@ -11,27 +13,35 @@ internal class RepairSubOptions : VerbCommandOptions [HelpVerbOption] public string GetUsage(string verb) { - HelpText ht = HelpText.AutoBuild(this, verb); + var ht = HelpText.AutoBuild(this, verb); + foreach (var h in GetHelp(verb)) + { + ht.AddPreOptionsLine(h); + } + return ht; + } + + public static IEnumerable GetHelp(string verb) + { // Add a usage prefix line - ht.AddPreOptionsLine(" "); + yield return " "; if (string.IsNullOrEmpty(verb)) { - ht.AddPreOptionsLine($"ckan repair - {Properties.Resources.RepairHelpSummary}"); - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan repair <{Properties.Resources.Command}> [{Properties.Resources.Options}]"); + yield return $"ckan repair - {Properties.Resources.RepairHelpSummary}"; + yield return $"{Properties.Resources.Usage}: ckan repair <{Properties.Resources.Command}> [{Properties.Resources.Options}]"; } else { - ht.AddPreOptionsLine("repair " + verb + " - " + GetDescription(verb)); + yield return "repair " + verb + " - " + GetDescription(typeof(RepairSubOptions), verb); switch (verb) { // Commands with only --flag type options case "registry": default: - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan repair {verb} [{Properties.Resources.Options}]"); + yield return $"{Properties.Resources.Usage}: ckan repair {verb} [{Properties.Resources.Options}]"; break; } } - return ht; } } diff --git a/Cmdline/Action/Replace.cs b/Cmdline/Action/Replace.cs index 7032baa75..071a69aae 100644 --- a/Cmdline/Action/Replace.cs +++ b/Cmdline/Action/Replace.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Linq; +using CommandLine; using log4net; + using CKAN.Versioning; namespace CKAN.CmdLine @@ -26,9 +28,11 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) if (options.modules.Count == 0 && ! options.replace_all) { - // What? No mods specified? - user.RaiseMessage("{0}: ckan replace Mod [Mod2, ...]", Properties.Resources.Usage); - user.RaiseMessage(" or ckan replace --all"); + user.RaiseError(Properties.Resources.ArgumentMissing); + foreach (var h in Actions.GetHelp("replace")) + { + user.RaiseError(h); + } return Exit.BADOPT; } @@ -178,4 +182,31 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) private static readonly ILog log = LogManager.GetLogger(typeof(Replace)); } + + internal class ReplaceOptions : InstanceSpecificOptions + { + [Option('c', "ckanfile", HelpText = "Local CKAN file to process")] + public string ckan_file { get; set; } + + [Option("no-recommends", HelpText = "Do not install recommended modules")] + public bool no_recommends { get; set; } + + [Option("with-suggests", HelpText = "Install suggested modules")] + public bool with_suggests { get; set; } + + [Option("with-all-suggests", HelpText = "Install suggested modules all the way down")] + public bool with_all_suggests { get; set; } + + [Option("allow-incompatible", DefaultValue = false, HelpText = "Install modules that are not compatible with the current game version")] + public bool allow_incompatible { get; set; } + + [Option("all", HelpText = "Replace all available replaced modules")] + public bool replace_all { get; set; } + + // TODO: How do we provide helptext on this? + [ValueList(typeof (List))] + [InstalledIdentifiers] + public List modules { get; set; } + } + } diff --git a/Cmdline/Action/Repo.cs b/Cmdline/Action/Repo.cs index 48dc18d07..627232d2f 100644 --- a/Cmdline/Action/Repo.cs +++ b/Cmdline/Action/Repo.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Collections.Generic; using Newtonsoft.Json; using CommandLine; @@ -31,44 +32,52 @@ public class RepoSubOptions : VerbCommandOptions [HelpVerbOption] public string GetUsage(string verb) { - HelpText ht = HelpText.AutoBuild(this, verb); + var ht = HelpText.AutoBuild(this, verb); + foreach (var h in GetHelp(verb)) + { + ht.AddPreOptionsLine(h); + } + return ht; + } + + public static IEnumerable GetHelp(string verb) + { // Add a usage prefix line - ht.AddPreOptionsLine(" "); + yield return " "; if (string.IsNullOrEmpty(verb)) { - ht.AddPreOptionsLine($"ckan repo - {Properties.Resources.RepoHelpSummary}"); - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan repo <{Properties.Resources.Command}> [{Properties.Resources.Options}]"); + yield return $"ckan repo - {Properties.Resources.RepoHelpSummary}"; + yield return $"{Properties.Resources.Usage}: ckan repo <{Properties.Resources.Command}> [{Properties.Resources.Options}]"; } else { - ht.AddPreOptionsLine("repo " + verb + " - " + GetDescription(verb)); + yield return "repo " + verb + " - " + GetDescription(typeof(RepoSubOptions), verb); switch (verb) { // First the commands with two arguments case "add": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan repo {verb} [{Properties.Resources.Options}] name url"); + yield return $"{Properties.Resources.Usage}: ckan repo {verb} [{Properties.Resources.Options}] name url"; break; case "priority": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan repo {verb} [{Properties.Resources.Options}] name priority"); + yield return $"{Properties.Resources.Usage}: ckan repo {verb} [{Properties.Resources.Options}] name priority"; break; // Then the commands with one argument case "remove": case "forget": case "default": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan repo {verb} [{Properties.Resources.Options}] name"); + yield return $"{Properties.Resources.Usage}: ckan repo {verb} [{Properties.Resources.Options}] name"; break; // Now the commands with only --flag type options case "available": case "list": default: - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan repo {verb} [{Properties.Resources.Options}]"); + yield return $"{Properties.Resources.Usage}: ckan repo {verb} [{Properties.Resources.Options}]"; break; } } - return ht; } } @@ -133,9 +142,9 @@ public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCom { CommonOptions options = (CommonOptions)suboptions; options.Merge(opts); - User = new ConsoleUser(options.Headless); - Manager = manager ?? new GameInstanceManager(User); - exitCode = options.Handle(Manager, User); + user = new ConsoleUser(options.Headless); + Manager = manager ?? new GameInstanceManager(user); + exitCode = options.Handle(Manager, user); if (exitCode != Exit.OK) { return; @@ -169,7 +178,7 @@ public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCom break; default: - User.RaiseMessage(Properties.Resources.RepoUnknownCommand, option); + user.RaiseMessage(Properties.Resources.RepoUnknownCommand, option); exitCode = Exit.BADOPT; break; } @@ -191,7 +200,7 @@ private RepositoryList FetchMasterRepositoryList(Uri master_uri = null) private int AvailableRepositories() { - User.RaiseMessage(Properties.Resources.RepoAvailableHeader); + user.RaiseMessage(Properties.Resources.RepoAvailableHeader); RepositoryList repositories; try @@ -200,7 +209,7 @@ private int AvailableRepositories() } catch { - User.RaiseError(Properties.Resources.RepoAvailableFailed, MainClass.GetGameInstance(Manager).game.RepositoryListURL.ToString()); + user.RaiseError(Properties.Resources.RepoAvailableFailed, MainClass.GetGameInstance(Manager).game.RepositoryListURL.ToString()); return Exit.ERROR; } @@ -212,7 +221,7 @@ private int AvailableRepositories() foreach (Repository repository in repositories.repositories) { - User.RaiseMessage(" {0}: {1}", repository.name.PadRight(maxNameLen), repository.uri); + user.RaiseMessage(" {0}: {1}", repository.name.PadRight(maxNameLen), repository.uri); } return Exit.OK; @@ -238,17 +247,17 @@ private int ListRepositories() const string columnFormat = "{0} {1} {2}"; - User.RaiseMessage(columnFormat, + user.RaiseMessage(columnFormat, priorityHeader.PadRight(priorityWidth), nameHeader.PadRight(nameWidth), urlHeader.PadRight(urlWidth)); - User.RaiseMessage(columnFormat, + user.RaiseMessage(columnFormat, new string('-', priorityWidth), new string('-', nameWidth), new string('-', urlWidth)); foreach (Repository repository in repositories.Values.OrderBy(r => r.priority)) { - User.RaiseMessage(columnFormat, + user.RaiseMessage(columnFormat, repository.priority.ToString().PadRight(priorityWidth), repository.name.PadRight(nameWidth), repository.uri); @@ -262,7 +271,8 @@ private int AddRepository(RepoAddOptions options) if (options.name == null) { - User.RaiseMessage("add [ ] - {0}", Properties.Resources.ArgumentMissing); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage("add"); return Exit.BADOPT; } @@ -276,7 +286,7 @@ private int AddRepository(RepoAddOptions options) } catch { - User.RaiseError(Properties.Resources.RepoAvailableFailed, Manager.CurrentInstance.game.RepositoryListURL.ToString()); + user.RaiseError(Properties.Resources.RepoAvailableFailed, Manager.CurrentInstance.game.RepositoryListURL.ToString()); return Exit.ERROR; } @@ -292,7 +302,7 @@ private int AddRepository(RepoAddOptions options) // Nothing found in the master list? if (options.uri == null) { - User.RaiseMessage(Properties.Resources.RepoAddNotFound, options.name); + user.RaiseMessage(Properties.Resources.RepoAddNotFound, options.name); return Exit.BADOPT; } } @@ -302,19 +312,19 @@ private int AddRepository(RepoAddOptions options) if (repositories.ContainsKey(options.name)) { - User.RaiseMessage(Properties.Resources.RepoAddDuplicate, options.name); + user.RaiseMessage(Properties.Resources.RepoAddDuplicate, options.name); return Exit.BADOPT; } if (repositories.Values.Any(r => r.uri.ToString() == options.uri)) { - User.RaiseMessage(Properties.Resources.RepoAddDuplicateURL, options.uri); + user.RaiseMessage(Properties.Resources.RepoAddDuplicateURL, options.uri); return Exit.BADOPT; } manager.registry.RepositoriesAdd(new Repository(options.name, options.uri, manager.registry.Repositories.Count)); - User.RaiseMessage(Properties.Resources.RepoAdded, options.name, options.uri); + user.RaiseMessage(Properties.Resources.RepoAdded, options.name, options.uri); manager.Save(); return Exit.OK; @@ -324,13 +334,14 @@ private int SetRepositoryPriority(RepoPriorityOptions options) { if (options.name == null) { - User.RaiseMessage("priority - {0}", Properties.Resources.ArgumentMissing); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage("priority"); return Exit.BADOPT; } var manager = RegistryManager.Instance(MainClass.GetGameInstance(Manager), repoData); if (options.priority < 0 || options.priority >= manager.registry.Repositories.Count) { - User.RaiseMessage(Properties.Resources.RepoPriorityInvalid, + user.RaiseMessage(Properties.Resources.RepoPriorityInvalid, options.priority, manager.registry.Repositories.Count - 1); return Exit.BADOPT; } @@ -365,7 +376,7 @@ private int SetRepositoryPriority(RepoPriorityOptions options) } else { - User.RaiseMessage(Properties.Resources.RepoPriorityNotFound, options.name); + user.RaiseMessage(Properties.Resources.RepoPriorityNotFound, options.name); return Exit.BADOPT; } } @@ -374,7 +385,8 @@ private int ForgetRepository(RepoForgetOptions options) { if (options.name == null) { - User.RaiseError("forget - {0}", Properties.Resources.ArgumentMissing); + user.RaiseError(Properties.Resources.ArgumentMissing); + PrintUsage("forget"); return Exit.BADOPT; } @@ -389,10 +401,10 @@ private int ForgetRepository(RepoForgetOptions options) name = repos.Keys.FirstOrDefault(repo => repo.Equals(options.name, StringComparison.OrdinalIgnoreCase)); if (name == null) { - User.RaiseMessage(Properties.Resources.RepoForgetNotFound, options.name); + user.RaiseMessage(Properties.Resources.RepoForgetNotFound, options.name); return Exit.BADOPT; } - User.RaiseMessage(Properties.Resources.RepoForgetRemoving, name); + user.RaiseMessage(Properties.Resources.RepoForgetRemoving, name); } manager.registry.RepositoriesRemove(name); @@ -401,7 +413,7 @@ private int ForgetRepository(RepoForgetOptions options) { remaining[i].priority = i; } - User.RaiseMessage(Properties.Resources.RepoForgetRemoved, options.name); + user.RaiseMessage(Properties.Resources.RepoForgetRemoved, options.name); manager.Save(); return Exit.OK; @@ -424,15 +436,23 @@ private int DefaultRepository(RepoDefaultOptions options) manager.registry.RepositoriesAdd( new Repository(Repository.default_ckan_repo_name, uri, repositories.Count)); - User.RaiseMessage(Properties.Resources.RepoSet, Repository.default_ckan_repo_name, uri); + user.RaiseMessage(Properties.Resources.RepoSet, Repository.default_ckan_repo_name, uri); manager.Save(); return Exit.OK; } + private void PrintUsage(string verb) + { + foreach (var h in RepoSubOptions.GetHelp(verb)) + { + user.RaiseError(h); + } + } + private GameInstanceManager Manager; private readonly RepositoryDataManager repoData; - private IUser User; + private IUser user; private static readonly ILog log = LogManager.GetLogger(typeof (Repo)); } diff --git a/Cmdline/Action/Search.cs b/Cmdline/Action/Search.cs index 59edd9912..6083f67c6 100644 --- a/Cmdline/Action/Search.cs +++ b/Cmdline/Action/Search.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Text.RegularExpressions; +using CommandLine; + namespace CKAN.CmdLine { public class Search : ICommand @@ -20,7 +22,11 @@ public int RunCommand(CKAN.GameInstance ksp, object raw_options) // Check the input. if (string.IsNullOrWhiteSpace(options.search_term) && string.IsNullOrWhiteSpace(options.author_term)) { - user.RaiseError(Properties.Resources.SearchNoTerm); + user.RaiseError(Properties.Resources.ArgumentMissing); + foreach (var h in Actions.GetHelp("search")) + { + user.RaiseError(h); + } return Exit.BADOPT; } @@ -198,4 +204,20 @@ public static void AdjustModulesCase(CKAN.GameInstance instance, Registry regist private readonly RepositoryDataManager repoData; private readonly IUser user; } + + internal class SearchOptions : InstanceSpecificOptions + { + [Option("detail", HelpText = "Show full name, latest compatible version and short description of each module")] + public bool detail { get; set; } + + [Option("all", HelpText = "Show incompatible mods too")] + public bool all { get; set; } + + [Option("author", HelpText = "Limit search results to mods by matching authors")] + public string author_term { get; set; } + + [ValueOption(0)] + public string search_term { get; set; } + } + } diff --git a/Cmdline/Action/Show.cs b/Cmdline/Action/Show.cs index bfbd12c05..f9550cc96 100644 --- a/Cmdline/Action/Show.cs +++ b/Cmdline/Action/Show.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Text; +using CommandLine; + using CKAN.Versioning; namespace CKAN.CmdLine @@ -20,8 +22,11 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) ShowOptions options = (ShowOptions) raw_options; if (options.modules == null || options.modules.Count < 1) { - // empty argument - user.RaiseMessage("show - {0}", Properties.Resources.ArgumentMissing); + user.RaiseError(Properties.Resources.ArgumentMissing); + foreach (var h in Actions.GetHelp("show")) + { + user.RaiseError(h); + } return Exit.BADOPT; } @@ -381,4 +386,30 @@ private static string RelationshipToPrintableString(RelationshipDescriptor dep) private IUser user { get; set; } private readonly RepositoryDataManager repoData; } + + internal class ShowOptions : InstanceSpecificOptions + { + [Option("without-description", HelpText = "Don't show the name, abstract, or description")] + public bool without_description { get; set; } + + [Option("without-module-info", HelpText = "Don't show the version, authors, status, license, tags, languages")] + public bool without_module_info { get; set; } + + [Option("without-relationships", HelpText = "Don't show dependencies or conflicts")] + public bool without_relationships { get; set; } + + [Option("without-resources", HelpText = "Don't show home page, etc.")] + public bool without_resources { get; set; } + + [Option("without-files", HelpText = "Don't show contained files")] + public bool without_files { get; set; } + + [Option("with-versions", HelpText = "Print table of all versions of the mod and their compatible game versions")] + public bool with_versions { get; set; } + + [ValueList(typeof(List))] + [AvailableIdentifiers] + public List modules { get; set; } + } + } diff --git a/Cmdline/Action/Update.cs b/Cmdline/Action/Update.cs index 182dbb08c..dbb71cfdd 100644 --- a/Cmdline/Action/Update.cs +++ b/Cmdline/Action/Update.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Linq; +using CommandLine; + namespace CKAN.CmdLine { public class Update : ICommand @@ -141,4 +143,11 @@ private void UpdateRepository(CKAN.GameInstance instance) private readonly RepositoryDataManager repoData; private readonly IUser user; } + + internal class UpdateOptions : InstanceSpecificOptions + { + [Option("list-changes", DefaultValue = false, HelpText = "List new and removed modules")] + public bool list_changes { get; set; } + } + } diff --git a/Cmdline/Action/Upgrade.cs b/Cmdline/Action/Upgrade.cs index 7c730fdf0..55855c342 100644 --- a/Cmdline/Action/Upgrade.cs +++ b/Cmdline/Action/Upgrade.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Transactions; +using CommandLine; using Autofac; using CKAN.Versioning; @@ -44,12 +45,10 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) if (options.modules.Count == 0 && !options.upgrade_all) { - // What? No files specified? - user.RaiseMessage("{0}: ckan upgrade Mod [Mod2, ...]", Properties.Resources.Usage); - user.RaiseMessage(" or ckan upgrade --all"); - if (AutoUpdate.CanUpdate) + user.RaiseError(Properties.Resources.ArgumentMissing); + foreach (var h in Actions.GetHelp("upgrade")) { - user.RaiseMessage(" or ckan upgrade ckan [--stable-release|--dev-build]"); + user.RaiseError(h); } return Exit.BADOPT; } @@ -318,4 +317,35 @@ private void UpgradeModules(GameInstanceManager manager, private readonly GameInstanceManager manager; private readonly RepositoryDataManager repoData; } + + internal class UpgradeOptions : InstanceSpecificOptions + { + [Option('c', "ckanfile", HelpText = "Local CKAN file to process")] + public string ckan_file { get; set; } + + [Option("no-recommends", DefaultValue = false, HelpText = "Do not install recommended modules")] + public bool no_recommends { get; set; } + + [Option("with-suggests", DefaultValue = false, HelpText = "Install suggested modules")] + public bool with_suggests { get; set; } + + [Option("with-all-suggests", DefaultValue = false, HelpText = "Install suggested modules all the way down")] + public bool with_all_suggests { get; set; } + + [Option("all", DefaultValue = false, HelpText = "Upgrade all available updated modules")] + public bool upgrade_all { get; set; } + + [Option("dev-build", DefaultValue = false, + HelpText = "For `ckan` option only, use dev builds")] + public bool dev_build { get; set; } + + [Option("stable-release", DefaultValue = false, + HelpText = "For `ckan` option only, use stable releases")] + public bool stable_release { get; set; } + + [ValueList(typeof (List))] + [InstalledIdentifiers] + public List modules { get; set; } + } + } diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs index 13499591e..86e459357 100644 --- a/Cmdline/Options.cs +++ b/Cmdline/Options.cs @@ -108,7 +108,7 @@ internal class Actions : VerbCommandOptions public CacheSubOptions Cache { get; set; } [VerbOption("compat", HelpText = "Manage game version compatibility")] - public CompatOptions Compat { get; set; } + public CompatSubOptions Compat { get; set; } [VerbOption("compare", HelpText = "Compare version strings")] public CompareOptions Compare { get; set; } @@ -122,21 +122,28 @@ internal class Actions : VerbCommandOptions [HelpVerbOption] public string GetUsage(string verb) { - HelpText ht = HelpText.AutoBuild(this, verb); + var ht = HelpText.AutoBuild(this, verb); + foreach (var h in GetHelp(verb)) + { + ht.AddPreOptionsLine(h); + } + return ht; + } + public static IEnumerable GetHelp(string verb) + { // Add a usage prefix line + yield return " "; if (string.IsNullOrEmpty(verb)) { - ht.AddPreOptionsLine(" "); - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan <{Properties.Resources.Command}> [{Properties.Resources.Options}]"); + yield return $"{Properties.Resources.Usage}: ckan <{Properties.Resources.Command}> [{Properties.Resources.Options}]"; } else { - string descr = GetDescription(verb); + string descr = GetDescription(typeof(Action), verb); if (!string.IsNullOrEmpty(descr)) { - ht.AddPreOptionsLine(" "); - ht.AddPreOptionsLine($"ckan {verb} - {descr}"); + yield return $"ckan {verb} - {descr}"; } switch (verb) { @@ -150,21 +157,19 @@ public string GetUsage(string verb) case "remove": case "uninstall": case "upgrade": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] modules"); - break; case "show": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] module"); + yield return $"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] module [module2 ...]"; break; // Commands with other string arguments case "search": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] substring"); + yield return $"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] substring"; break; case "compare": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] version1 version2"); + yield return $"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] version1 version2"; break; case "import": - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] paths"); + yield return $"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}] path [path2 ...]"; break; // Commands with only --flag type options @@ -176,11 +181,10 @@ public string GetUsage(string verb) case "clean": case "version": default: - ht.AddPreOptionsLine($"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}]"); + yield return $"{Properties.Resources.Usage}: ckan {verb} [{Properties.Resources.Options}]"; break; } } - return ht; } } @@ -188,13 +192,14 @@ public string GetUsage(string verb) public abstract class VerbCommandOptions { protected string GetDescription(string verb) - { - return GetType().GetProperties() + => GetDescription(GetType(), verb); + + protected static string GetDescription(Type t, string verb) + => t.GetProperties() .Select(property => (BaseOptionAttribute)Attribute.GetCustomAttribute( property, typeof(BaseOptionAttribute), false)) .FirstOrDefault(attrib => attrib?.LongName == verb) ?.HelpText; - } } // Options common to all classes. @@ -366,106 +371,11 @@ public SubCommandOptions(string[] args) // Each action defines its own options that it supports. // Don't forget to cast to this type when you're processing them later on. - internal class InstallOptions : InstanceSpecificOptions - { - [OptionArray('c', "ckanfiles", HelpText = "Local CKAN files or URLs to process")] - public string[] ckan_files { get; set; } - - [Option("no-recommends", DefaultValue = false, HelpText = "Do not install recommended modules")] - public bool no_recommends { get; set; } - - [Option("with-suggests", DefaultValue = false, HelpText = "Install suggested modules")] - public bool with_suggests { get; set; } - - [Option("with-all-suggests", DefaultValue = false, HelpText = "Install suggested modules all the way down")] - public bool with_all_suggests { get; set; } - - [Option("allow-incompatible", DefaultValue = false, HelpText = "Install modules that are not compatible with the current game version")] - public bool allow_incompatible { get; set; } - - [ValueList(typeof(List))] - [AvailableIdentifiers] - public List modules { get; set; } - } - - internal class UpgradeOptions : InstanceSpecificOptions - { - [Option('c', "ckanfile", HelpText = "Local CKAN file to process")] - public string ckan_file { get; set; } - - [Option("no-recommends", DefaultValue = false, HelpText = "Do not install recommended modules")] - public bool no_recommends { get; set; } - - [Option("with-suggests", DefaultValue = false, HelpText = "Install suggested modules")] - public bool with_suggests { get; set; } - - [Option("with-all-suggests", DefaultValue = false, HelpText = "Install suggested modules all the way down")] - public bool with_all_suggests { get; set; } - - [Option("all", DefaultValue = false, HelpText = "Upgrade all available updated modules")] - public bool upgrade_all { get; set; } - - [Option("dev-build", DefaultValue = false, - HelpText = "For `ckan` option only, use dev builds")] - public bool dev_build { get; set; } - - [Option("stable-release", DefaultValue = false, - HelpText = "For `ckan` option only, use stable releases")] - public bool stable_release { get; set; } - - [ValueList(typeof (List))] - [InstalledIdentifiers] - public List modules { get; set; } - } - - internal class ReplaceOptions : InstanceSpecificOptions - { - [Option('c', "ckanfile", HelpText = "Local CKAN file to process")] - public string ckan_file { get; set; } - - [Option("no-recommends", HelpText = "Do not install recommended modules")] - public bool no_recommends { get; set; } - - [Option("with-suggests", HelpText = "Install suggested modules")] - public bool with_suggests { get; set; } - - [Option("with-all-suggests", HelpText = "Install suggested modules all the way down")] - public bool with_all_suggests { get; set; } - - [Option("allow-incompatible", DefaultValue = false, HelpText = "Install modules that are not compatible with the current game version")] - public bool allow_incompatible { get; set; } - - [Option("all", HelpText = "Replace all available replaced modules")] - public bool replace_all { get; set; } - - // TODO: How do we provide helptext on this? - [ValueList(typeof (List))] - [InstalledIdentifiers] - public List modules { get; set; } - } - - internal class ScanOptions : InstanceSpecificOptions - { - } - - internal class ListOptions : InstanceSpecificOptions - { - [Option("porcelain", HelpText = "Dump raw list of modules, good for shell scripting")] - public bool porcelain { get; set; } - - [Option("export", HelpText = "Export list of modules in specified format to stdout")] - public string export { get; set; } - } + internal class ScanOptions : InstanceSpecificOptions { } internal class VersionOptions : CommonOptions { } internal class CleanOptions : InstanceSpecificOptions { } - internal class AvailableOptions : InstanceSpecificOptions - { - [Option("detail", HelpText = "Show short description of each module")] - public bool detail { get; set; } - } - #if NETFRAMEWORK || WINDOWS internal class GuiOptions : InstanceSpecificOptions { @@ -480,80 +390,6 @@ internal class ConsoleUIOptions : InstanceSpecificOptions public string Theme { get; set; } } - internal class UpdateOptions : InstanceSpecificOptions - { - [Option("list-changes", DefaultValue = false, HelpText = "List new and removed modules")] - public bool list_changes { get; set; } - } - - internal class RemoveOptions : InstanceSpecificOptions - { - [Option("re", HelpText = "Parse arguments as regular expressions")] - public bool regex { get; set; } - - [ValueList(typeof(List))] - [InstalledIdentifiers] - public List modules { get; set; } - - [Option("all", DefaultValue = false, HelpText = "Remove all installed mods.")] - public bool rmall { get; set; } - } - - internal class ImportOptions : InstanceSpecificOptions - { - [ValueList(typeof(List))] - public List paths { get; set; } - } - - internal class ShowOptions : InstanceSpecificOptions - { - [Option("without-description", HelpText = "Don't show the name, abstract, or description")] - public bool without_description { get; set; } - - [Option("without-module-info", HelpText = "Don't show the version, authors, status, license, tags, languages")] - public bool without_module_info { get; set; } - - [Option("without-relationships", HelpText = "Don't show dependencies or conflicts")] - public bool without_relationships { get; set; } - - [Option("without-resources", HelpText = "Don't show home page, etc.")] - public bool without_resources { get; set; } - - [Option("without-files", HelpText = "Don't show contained files")] - public bool without_files { get; set; } - - [Option("with-versions", HelpText = "Print table of all versions of the mod and their compatible game versions")] - public bool with_versions { get; set; } - - [ValueList(typeof(List))] - [AvailableIdentifiers] - public List modules { get; set; } - } - - internal class SearchOptions : InstanceSpecificOptions - { - [Option("detail", HelpText = "Show full name, latest compatible version and short description of each module")] - public bool detail { get; set; } - - [Option("all", HelpText = "Show incompatible mods too")] - public bool all { get; set; } - - [Option("author", HelpText = "Limit search results to mods by matching authors")] - public string author_term { get; set; } - - [ValueOption(0)] - public string search_term { get; set; } - } - - internal class CompareOptions : CommonOptions - { - [Option("machine-readable", HelpText = "Output in a machine readable format: -1, 0 or 1")] - public bool machine_readable { get; set;} - - [ValueOption(0)] public string Left { get; set; } - [ValueOption(1)] public string Right { get; set; } - } - [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] public class AvailableIdentifiersAttribute : Attribute { } diff --git a/Cmdline/Properties/Resources.resx b/Cmdline/Properties/Resources.resx index 92be68fcb..0abd3627c 100644 --- a/Cmdline/Properties/Resources.resx +++ b/Cmdline/Properties/Resources.resx @@ -178,8 +178,9 @@ If you must run as an administrator, the `--asroot` parameter will bypass this c Manage game version compatibility Version Actual - ERROR: Invalid game version - ERROR: Cannot forget actual game version + No game versions specified! + Invalid game versions: {0} + Cannot forget actual game version: {0} Manage game instances <NONE> Yes diff --git a/Core/GameInstance.cs b/Core/GameInstance.cs index a99e3b67e..13717b8d1 100644 --- a/Core/GameInstance.cs +++ b/Core/GameInstance.cs @@ -126,7 +126,9 @@ private void SetupCkanDirectories() public void SetCompatibleVersions(List compatibleVersions) { - _compatibleVersions = compatibleVersions.Distinct().ToList(); + _compatibleVersions = compatibleVersions.Distinct() + .OrderByDescending(v => v) + .ToList(); SaveCompatibleVersions(); }