diff --git a/Cmdline/Action/AuthToken.cs b/Cmdline/Action/AuthToken.cs
index d5bd9ff60c..ba3eeb6aaf 100644
--- a/Cmdline/Action/AuthToken.cs
+++ b/Cmdline/Action/AuthToken.cs
@@ -3,180 +3,190 @@
using Autofac;
using CKAN.Configuration;
using CommandLine;
-using CommandLine.Text;
-using log4net;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
///
- /// Subcommand for managing authentication tokens
+ /// Class for managing authentication tokens.
///
public class AuthToken : ISubCommand
{
- ///
- /// Initialize the subcommand
- ///
- public AuthToken() { }
+ private GameInstanceManager _manager;
+ private IUser _user;
///
- /// Run the subcommand
+ /// Run the 'authtoken' command.
///
- /// Manager to provide game instances
- /// Command line parameters paritally handled by parser
- /// Command line parameters not yet handled by parser
- ///
- /// Exit code
- ///
- public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCommandOptions unparsed)
+ ///
+ public int RunCommand(GameInstanceManager manager, object args)
{
- string[] args = unparsed.options.ToArray();
- int exitCode = Exit.OK;
+ var s = args.ToString();
+ var opts = s.Replace(s.Substring(0, s.LastIndexOf('.') + 1), "").Split('+');
+
+ CommonOptions options = new CommonOptions();
+ _user = new ConsoleUser(options.Headless);
+ _manager = manager ?? new GameInstanceManager(_user);
+ var exitCode = options.Handle(_manager, _user);
+
+ if (exitCode != Exit.Ok)
+ return exitCode;
- Parser.Default.ParseArgumentsStrict(args, new AuthTokenSubOptions(), (string option, object suboptions) =>
+ switch (opts[1])
{
- if (!string.IsNullOrEmpty(option) && suboptions != null)
- {
- CommonOptions options = (CommonOptions)suboptions;
- options.Merge(opts);
- user = new ConsoleUser(options.Headless);
- if (manager == null)
- {
- manager = new GameInstanceManager(user);
- }
- exitCode = options.Handle(manager, user);
- if (exitCode == Exit.OK)
- {
- switch (option)
- {
- case "list":
- exitCode = listAuthTokens(options);
- break;
- case "add":
- exitCode = addAuthToken((AddAuthTokenOptions)options);
- break;
- case "remove":
- exitCode = removeAuthToken((RemoveAuthTokenOptions)options);
- break;
- }
- }
- }
- }, () => { exitCode = MainClass.AfterHelp(); });
+ case "AddAuthToken":
+ exitCode = AddAuthToken(args);
+ break;
+ case "ListAuthToken":
+ exitCode = ListAuthTokens();
+ break;
+ case "RemoveAuthToken":
+ exitCode = RemoveAuthToken(args);
+ break;
+ default:
+ exitCode = Exit.BadOpt;
+ break;
+ }
+
return exitCode;
}
- private int listAuthTokens(CommonOptions opts)
+ ///
+ public string GetUsage(string prefix, string[] args)
{
- List hosts = new List(ServiceLocator.Container.Resolve().GetAuthTokenHosts());
- if (hosts.Count > 0)
+ if (args.Length == 1)
+ return $"{prefix} {args[0]} [options]";
+
+ switch (args[1])
{
- int longestHostLen = hostHeader.Length;
- int longestTokenLen = tokenHeader.Length;
- foreach (string host in hosts)
- {
- longestHostLen = Math.Max(longestHostLen, host.Length);
- string token;
- if (ServiceLocator.Container.Resolve().TryGetAuthToken(host, out token))
- {
- longestTokenLen = Math.Max(longestTokenLen, token.Length);
- }
- }
- // Create format string: {0,-longestHostLen} {1,-longestTokenLen}
- string fmt = string.Format("{0}0,-{2}{1} {0}1,-{3}{1}",
- "{", "}", longestHostLen, longestTokenLen);
- user.RaiseMessage(fmt, hostHeader, tokenHeader);
- user.RaiseMessage(fmt,
- new string('-', longestHostLen),
- new string('-', longestTokenLen)
- );
- foreach (string host in hosts)
- {
- string token;
- if (ServiceLocator.Container.Resolve().TryGetAuthToken(host, out token))
- {
- user.RaiseMessage(fmt, host, token);
- }
- }
+ case "add":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "list":
+ return $"{prefix} {args[0]} {args[1]} [options]";
+ case "remove":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ default:
+ return $"{prefix} {args[0]} [options]";
}
- return Exit.OK;
}
- private int addAuthToken(AddAuthTokenOptions opts)
+ private int AddAuthToken(object args)
{
- if (Uri.CheckHostName(opts.host) != UriHostNameType.Unknown)
+ var opts = (AuthTokenOptions.AddAuthToken)args;
+ if (opts.Host == null || opts.Token == null)
{
- ServiceLocator.Container.Resolve().SetAuthToken(opts.host, opts.token);
+ _user.RaiseMessage("add - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
+
+ if (Uri.CheckHostName(opts.Host) != UriHostNameType.Unknown)
+ {
+ ServiceLocator.Container.Resolve().SetAuthToken(opts.Host, opts.Token);
+ _user.RaiseMessage("Successfully added \"{0}\".", opts.Host);
}
else
{
- user.RaiseError("Invalid host name: {0}", opts.host);
+ _user.RaiseMessage("Invalid host name.");
+ return Exit.BadOpt;
}
- return Exit.OK;
+
+ return Exit.Ok;
}
- private int removeAuthToken(RemoveAuthTokenOptions opts)
+ private int ListAuthTokens()
{
- ServiceLocator.Container.Resolve().SetAuthToken(opts.host, null);
- return Exit.OK;
- }
+ const string hostHeader = "Host";
+ const string tokenHeader = "Token";
- private const string hostHeader = "Host";
- private const string tokenHeader = "Token";
+ var hosts = new List(ServiceLocator.Container.Resolve().GetAuthTokenHosts());
+ if (hosts.Count > 0)
+ {
+ var hostWidth = hostHeader.Length;
+ var tokenWidth = tokenHeader.Length;
+ foreach (var host in hosts)
+ {
+ hostWidth = Math.Max(hostWidth, host.Length);
+ if (ServiceLocator.Container.Resolve().TryGetAuthToken(host, out string token) && token != null)
+ {
+ tokenWidth = Math.Max(tokenWidth, token.Length);
+ }
+ }
- private IUser user;
- private static readonly ILog log = LogManager.GetLogger(typeof(AuthToken));
- }
+ _user.RaiseMessage("{0} {1}",
+ hostHeader.PadRight(hostWidth),
+ tokenHeader.PadRight(tokenWidth)
+ );
- internal class AuthTokenSubOptions : VerbCommandOptions
- {
- [VerbOption("list", HelpText = "List auth tokens")]
- public CommonOptions ListOptions { get; set; }
+ _user.RaiseMessage("{0} {1}",
+ new string('-', hostWidth),
+ new string('-', tokenWidth)
+ );
- [VerbOption("add", HelpText = "Add an auth token")]
- public AddAuthTokenOptions AddOptions { get; set; }
+ foreach (var host in hosts)
+ {
+ if (ServiceLocator.Container.Resolve().TryGetAuthToken(host, out string token))
+ {
+ _user.RaiseMessage("{0} {1}",
+ host.PadRight(hostWidth),
+ token.PadRight(tokenWidth)
+ );
+ }
+ }
+ }
- [VerbOption("remove", HelpText = "Delete an auth token")]
- public RemoveAuthTokenOptions RemoveOptions { get; set; }
+ return Exit.Ok;
+ }
- [HelpVerbOption]
- public string GetUsage(string verb)
+ private int RemoveAuthToken(object args)
{
- HelpText ht = HelpText.AutoBuild(this, verb);
- // Add a usage prefix line
- ht.AddPreOptionsLine(" ");
- if (string.IsNullOrEmpty(verb))
+ var opts = (AuthTokenOptions.RemoveAuthToken)args;
+ if (opts.Host == null)
{
- ht.AddPreOptionsLine("ckan authtoken - Manage authentication tokens");
- ht.AddPreOptionsLine($"Usage: ckan authtoken [options]");
+ _user.RaiseMessage("remove - argument missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
+
+ var hosts = new List(ServiceLocator.Container.Resolve().GetAuthTokenHosts());
+ if (hosts.Contains(opts.Host))
+ {
+ ServiceLocator.Container.Resolve().SetAuthToken(opts.Host, null);
+ _user.RaiseMessage("Successfully removed \"{0}\".", opts.Host);
}
else
{
- ht.AddPreOptionsLine("authtoken " + verb + " - " + GetDescription(verb));
- switch (verb)
- {
- case "add":
- ht.AddPreOptionsLine($"Usage: ckan authtoken {verb} [options] host token");
- break;
- case "remove":
- ht.AddPreOptionsLine($"Usage: ckan authtoken {verb} [options] host");
- break;
- case "list":
- ht.AddPreOptionsLine($"Usage: ckan authtoken {verb} [options]");
- break;
- }
+ _user.RaiseMessage("There is no host with the name \"{0}\".", opts.Host);
+ _user.RaiseMessage("Use 'ckan authtoken list' to view a list of hosts.");
+ return Exit.BadOpt;
}
- return ht;
+
+ return Exit.Ok;
}
}
- internal class AddAuthTokenOptions : CommonOptions
+ [Verb("authtoken", HelpText = "Manage authentication tokens")]
+ [ChildVerbs(typeof(AddAuthToken), typeof(ListAuthToken), typeof(RemoveAuthToken))]
+ internal class AuthTokenOptions
{
- [ValueOption(0)] public string host { get; set; }
- [ValueOption(1)] public string token { get; set; }
- }
+ [VerbExclude]
+ [Verb("add", HelpText = "Add an authentication token")]
+ internal class AddAuthToken : CommonOptions
+ {
+ [Value(0, MetaName = "Host", HelpText = "The host (DNS / IP) to authenticate with")]
+ public string Host { get; set; }
- internal class RemoveAuthTokenOptions : CommonOptions
- {
- [ValueOption(0)] public string host { get; set; }
- }
+ [Value(1, MetaName = "Token", HelpText = "The token to authenticate with")]
+ public string Token { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("list", HelpText = "List authentication tokens")]
+ internal class ListAuthToken : CommonOptions { }
+ [VerbExclude]
+ [Verb("remove", HelpText = "Remove an authentication token")]
+ internal class RemoveAuthToken : CommonOptions
+ {
+ [Value(0, MetaName = "Host", HelpText = "The host (DNS / IP) to remove")]
+ public string Host { get; set; }
+ }
+ }
}
diff --git a/Cmdline/Action/Available.cs b/Cmdline/Action/Available.cs
index ae445fe6d9..3fba5d2bb2 100644
--- a/Cmdline/Action/Available.cs
+++ b/Cmdline/Action/Available.cs
@@ -1,45 +1,62 @@
using System.Linq;
-using System.Collections.Generic;
+using CommandLine;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for listing the available mods.
+ ///
public class Available : ICommand
{
- public IUser user { get; set; }
+ private readonly IUser _user;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current to raise messages to the user.
public Available(IUser user)
{
- this.user = user;
+ _user = user;
}
- public int RunCommand(CKAN.GameInstance ksp, object raw_options)
+ ///
+ /// Run the 'available' command.
+ ///
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- AvailableOptions opts = (AvailableOptions)raw_options;
- IRegistryQuerier registry = RegistryManager.Instance(ksp).registry;
-
+ var opts = (AvailableOptions)args;
+ IRegistryQuerier registry = RegistryManager.Instance(inst).registry;
+
var compatible = registry
- .CompatibleModules(ksp.VersionCriteria())
+ .CompatibleModules(inst.VersionCriteria())
.Where(m => !m.IsDLC);
- user.RaiseMessage("Modules compatible with KSP {0}", ksp.Version());
- user.RaiseMessage("");
+ _user.RaiseMessage("Mods compatible with {0} {1}\r\n", inst.game.ShortName, inst.Version());
- if (opts.detail)
+ if (opts.Detail)
{
- foreach (CkanModule module in compatible)
+ foreach (var module in compatible)
{
- user.RaiseMessage("* {0} ({1}) - {2} - {3}", module.identifier, module.version, module.name, module.@abstract);
+ _user.RaiseMessage("* {0} ({1}) - {2} - {3}", module.identifier, module.version, module.name, module.@abstract);
}
}
else
{
- foreach (CkanModule module in compatible)
+ foreach (var module in compatible)
{
- user.RaiseMessage("* {0} ({1}) - {2}", module.identifier, module.version, module.name);
+ _user.RaiseMessage("* {0} ({1}) - {2}", module.identifier, module.version, module.name);
}
}
- return Exit.OK;
+ return Exit.Ok;
}
}
+
+ [Verb("available", HelpText = "List available mods")]
+ internal class AvailableOptions : InstanceSpecificOptions
+ {
+ [Option("detail", HelpText = "Shows a short description of each mod")]
+ public bool Detail { get; set; }
+ }
}
diff --git a/Cmdline/Action/Cache.cs b/Cmdline/Action/Cache.cs
index 5463709664..613fb4bad7 100644
--- a/Cmdline/Action/Cache.cs
+++ b/Cmdline/Action/Cache.cs
@@ -1,244 +1,209 @@
-using CommandLine;
-using CommandLine.Text;
-using log4net;
-using CKAN.Configuration;
using Autofac;
+using CKAN.Configuration;
+using CommandLine;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for managing the CKAN cache.
+ ///
public class Cache : ISubCommand
{
- public Cache() { }
+ private GameInstanceManager _manager;
+ private IUser _user;
- private class CacheSubOptions : VerbCommandOptions
+ ///
+ /// Run the 'cache' command.
+ ///
+ ///
+ public int RunCommand(GameInstanceManager manager, object args)
{
- [VerbOption("list", HelpText = "List the download cache path")]
- public CommonOptions ListOptions { get; set; }
+ var s = args.ToString();
+ var opts = s.Replace(s.Substring(0, s.LastIndexOf('.') + 1), "").Split('+');
- [VerbOption("set", HelpText = "Set the download cache path")]
- public SetOptions SetOptions { get; set; }
+ CommonOptions options = new CommonOptions();
+ _user = new ConsoleUser(options.Headless);
+ _manager = manager ?? new GameInstanceManager(_user);
+ var exitCode = options.Handle(_manager, _user);
- [VerbOption("clear", HelpText = "Clear the download cache directory")]
- public CommonOptions ClearOptions { get; set; }
+ if (exitCode != Exit.Ok)
+ return exitCode;
- [VerbOption("reset", HelpText = "Set the download cache path to the default")]
- public CommonOptions ResetOptions { get; set; }
+ switch (opts[1])
+ {
+ case "ClearCache":
+ exitCode = ClearCacheDirectory();
+ break;
+ case "ListCache":
+ exitCode = ListCacheDirectory();
+ break;
+ case "ResetCache":
+ exitCode = ResetCacheDirectory();
+ break;
+ case "SetCache":
+ exitCode = SetCacheDirectory(args);
+ break;
+ case "SetCacheLimit":
+ exitCode = SetCacheSizeLimit(args);
+ break;
+ case "ShowCacheLimit":
+ exitCode = ShowCacheSizeLimit();
+ break;
+ default:
+ exitCode = Exit.BadOpt;
+ break;
+ }
- [VerbOption("showlimit", HelpText = "Show the cache size limit")]
- public CommonOptions ShowLimitOptions { get; set; }
+ return exitCode;
+ }
- [VerbOption("setlimit", HelpText = "Set the cache size limit")]
- public SetLimitOptions SetLimitOptions { get; set; }
+ ///
+ public string GetUsage(string prefix, string[] args)
+ {
+ if (args.Length == 1)
+ return $"{prefix} {args[0]} [options]";
- [HelpVerbOption]
- public string GetUsage(string verb)
+ switch (args[1])
{
- HelpText ht = HelpText.AutoBuild(this, verb);
- // Add a usage prefix line
- ht.AddPreOptionsLine(" ");
- if (string.IsNullOrEmpty(verb))
- {
- ht.AddPreOptionsLine("ckan cache - Manage the download cache path of CKAN");
- ht.AddPreOptionsLine($"Usage: ckan cache [options]");
- }
- else
- {
- ht.AddPreOptionsLine("cache " + verb + " - " + GetDescription(verb));
- switch (verb)
- {
- // First the commands with one string argument
- case "set":
- ht.AddPreOptionsLine($"Usage: ckan cache {verb} [options] path");
- break;
- case "setlimit":
- ht.AddPreOptionsLine($"Usage: ckan cache {verb} [options] megabytes");
- break;
-
- // Now the commands with only --flag type options
- case "list":
- case "clear":
- case "reset":
- case "showlimit":
- default:
- ht.AddPreOptionsLine($"Usage: ckan cache {verb} [options]");
- break;
- }
- }
- return ht;
+ case "set":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "setlimit":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "clear":
+ case "list":
+ case "reset":
+ case "showlimit":
+ return $"{prefix} {args[0]} {args[1]} [options]";
+ default:
+ return $"{prefix} {args[0]} [options]";
}
}
- private class SetOptions : CommonOptions
+ private int ClearCacheDirectory()
{
- [ValueOption(0)]
- public string Path { get; set; }
+ _manager.Cache.RemoveAll();
+ _user.RaiseMessage("Cleared download cache.");
+ return Exit.Ok;
}
- private class SetLimitOptions : CommonOptions
+ private int ListCacheDirectory()
{
- [ValueOption(0)]
- public long Megabytes { get; set; } = -1;
+ var cfg = ServiceLocator.Container.Resolve();
+ _user.RaiseMessage("Download cache is set to \"{0}\".", cfg.DownloadCacheDir);
+ PrintCacheInfo();
+ return Exit.Ok;
}
- ///
- /// Execute a cache subcommand
- ///
- /// GameInstanceManager object containing our instances and cache
- /// Command line options object
- /// Raw command line options
- ///
- /// Exit code for shell environment
- ///
- public int RunSubCommand(GameInstanceManager mgr, CommonOptions opts, SubCommandOptions unparsed)
+ private int ResetCacheDirectory()
{
- string[] args = unparsed.options.ToArray();
-
- int exitCode = Exit.OK;
- // Parse and process our sub-verbs
- Parser.Default.ParseArgumentsStrict(args, new CacheSubOptions(), (string option, object suboptions) =>
+ if (_manager.TrySetupCache("", out string failReason))
{
- // ParseArgumentsStrict calls us unconditionally, even with bad arguments
- if (!string.IsNullOrEmpty(option) && suboptions != null)
- {
- CommonOptions options = (CommonOptions)suboptions;
- options.Merge(opts);
- user = new ConsoleUser(options.Headless);
- manager = mgr ?? new GameInstanceManager(user);
- exitCode = options.Handle(manager, user);
- if (exitCode != Exit.OK)
- return;
-
- switch (option)
- {
- case "list":
- exitCode = ListCacheDirectory((CommonOptions)suboptions);
- break;
-
- case "set":
- exitCode = SetCacheDirectory((SetOptions)suboptions);
- break;
-
- case "clear":
- exitCode = ClearCacheDirectory((CommonOptions)suboptions);
- break;
-
- case "reset":
- exitCode = ResetCacheDirectory((CommonOptions)suboptions);
- break;
-
- case "showlimit":
- exitCode = ShowCacheSizeLimit((CommonOptions)suboptions);
- break;
-
- case "setlimit":
- exitCode = SetCacheSizeLimit((SetLimitOptions)suboptions);
- break;
-
- default:
- user.RaiseMessage("Unknown command: cache {0}", option);
- exitCode = Exit.BADOPT;
- break;
- }
- }
- }, () => { exitCode = MainClass.AfterHelp(); });
- return exitCode;
- }
+ var cfg = ServiceLocator.Container.Resolve();
+ _user.RaiseMessage("Download cache reset to \"{0}\".", cfg.DownloadCacheDir);
+ PrintCacheInfo();
+ }
+ else
+ {
+ _user.RaiseError("Can't reset cache path: {0}.", failReason);
+ return Exit.Error;
+ }
- private int ListCacheDirectory(CommonOptions options)
- {
- IConfiguration cfg = ServiceLocator.Container.Resolve();
- user.RaiseMessage(cfg.DownloadCacheDir);
- printCacheInfo();
- return Exit.OK;
+ return Exit.Ok;
}
- private int SetCacheDirectory(SetOptions options)
+ private int SetCacheDirectory(object args)
{
- if (string.IsNullOrEmpty(options.Path))
+ var opts = (CacheOptions.SetCache)args;
+ if (opts.Path == null)
{
- user.RaiseError("set - argument missing, perhaps you forgot it?");
- return Exit.BADOPT;
+ _user.RaiseMessage("set - argument missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
- string failReason;
- if (manager.TrySetupCache(options.Path, out failReason))
+ if (_manager.TrySetupCache(opts.Path, out string failReason))
{
- IConfiguration cfg = ServiceLocator.Container.Resolve();
- user.RaiseMessage($"Download cache set to {cfg.DownloadCacheDir}");
- printCacheInfo();
- return Exit.OK;
+ var cfg = ServiceLocator.Container.Resolve();
+ _user.RaiseMessage("Download cache set to \"{0}\".", cfg.DownloadCacheDir);
+ PrintCacheInfo();
}
else
{
- user.RaiseError($"Invalid path: {failReason}");
- return Exit.BADOPT;
+ _user.RaiseError("Invalid path: {0}.", failReason);
+ return Exit.Error;
}
- }
- private int ClearCacheDirectory(CommonOptions options)
- {
- manager.Cache.RemoveAll();
- user.RaiseMessage("Download cache cleared.");
- printCacheInfo();
- return Exit.OK;
+ return Exit.Ok;
}
- private int ResetCacheDirectory(CommonOptions options)
+ private int SetCacheSizeLimit(object args)
{
- string failReason;
- if (manager.TrySetupCache("", out failReason))
+ var opts = (CacheOptions.SetCacheLimit)args;
+ var cfg = ServiceLocator.Container.Resolve();
+ if (opts.Megabytes < 0)
{
- IConfiguration cfg = ServiceLocator.Container.Resolve();
- user.RaiseMessage($"Download cache reset to {cfg.DownloadCacheDir}");
- printCacheInfo();
+ cfg.CacheSizeLimit = null;
}
else
{
- user.RaiseError($"Can't reset cache path: {failReason}");
+ cfg.CacheSizeLimit = opts.Megabytes * 1024 * 1024;
}
- return Exit.OK;
+
+ ShowCacheSizeLimit();
+ return Exit.Ok;
}
- private int ShowCacheSizeLimit(CommonOptions options)
+ private int ShowCacheSizeLimit()
{
- IConfiguration cfg = ServiceLocator.Container.Resolve();
- if (cfg.CacheSizeLimit.HasValue)
- {
- user.RaiseMessage(CkanModule.FmtSize(cfg.CacheSizeLimit.Value));
- }
- else
- {
- user.RaiseMessage("Unlimited");
- }
- return Exit.OK;
+ var cfg = ServiceLocator.Container.Resolve();
+ var limit = cfg.CacheSizeLimit.HasValue
+ ? CkanModule.FmtSize(cfg.CacheSizeLimit.Value)
+ : "Unlimited";
+
+ _user.RaiseMessage("Cache limit set to {0}.", limit);
+ return Exit.Ok;
}
- private int SetCacheSizeLimit(SetLimitOptions options)
+ private void PrintCacheInfo()
{
- IConfiguration cfg = ServiceLocator.Container.Resolve();
- if (options.Megabytes < 0)
- {
- cfg.CacheSizeLimit = null;
- }
- else
- {
- cfg.CacheSizeLimit = options.Megabytes * (long)1024 * (long)1024;
- }
- return ShowCacheSizeLimit(null);
+ _manager.Cache.GetSizeInfo(out int fileCount, out long bytes);
+ _user.RaiseMessage("Cache currently has {0} files that use {1}.", fileCount, CkanModule.FmtSize(bytes));
}
+ }
+
+ [Verb("cache", HelpText = "Manage download cache path")]
+ [ChildVerbs(typeof(ClearCache), typeof(ListCache), typeof(ResetCache), typeof(SetCache), typeof(SetCacheLimit), typeof(ShowCacheLimit))]
+ internal class CacheOptions
+ {
+ [VerbExclude]
+ [Verb("clear", HelpText = "Clear the download cache directory")]
+ internal class ClearCache : CommonOptions { }
+
+ [VerbExclude]
+ [Verb("list", HelpText = "List the download cache path")]
+ internal class ListCache : CommonOptions { }
- private void printCacheInfo()
+ [VerbExclude]
+ [Verb("reset", HelpText = "Set the download cache path to the default")]
+ internal class ResetCache : CommonOptions { }
+
+ [VerbExclude]
+ [Verb("set", HelpText = "Set the download cache path")]
+ internal class SetCache : CommonOptions
{
- int fileCount;
- long bytes;
- manager.Cache.GetSizeInfo(out fileCount, out bytes);
- user.RaiseMessage($"{fileCount} files, {CkanModule.FmtSize(bytes)}");
+ [Value(0, MetaName = "Path", HelpText = "The path to set the download cache to")]
+ public string Path { get; set; }
}
- private GameInstanceManager manager;
- private IUser user;
+ [VerbExclude]
+ [Verb("setlimit", HelpText = "Set the cache size limit")]
+ internal class SetCacheLimit : CommonOptions
+ {
+ [Value(0, MetaName = "MB", HelpText = "The max amount of MB the download cache stores files")]
+ public long Megabytes { get; set; } = -1;
+ }
- private static readonly ILog log = LogManager.GetLogger(typeof(Cache));
+ [VerbExclude]
+ [Verb("showlimit", HelpText = "Show the cache size limit")]
+ internal class ShowCacheLimit : CommonOptions { }
}
-
}
diff --git a/Cmdline/Action/Compare.cs b/Cmdline/Action/Compare.cs
index 328358a8a7..a5f5b94f7a 100644
--- a/Cmdline/Action/Compare.cs
+++ b/Cmdline/Action/Compare.cs
@@ -1,61 +1,73 @@
using CKAN.Versioning;
+using CommandLine;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
- // Does not need an instance, so this is not an ICommand
- public class Compare
+ ///
+ /// Class for comparing version strings.
+ ///
+ public class Compare : ICommand
{
- private IUser user;
+ private readonly IUser _user;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current to raise messages to the user.
public Compare(IUser user)
{
- this.user = user;
+ _user = user;
}
- public int RunCommand(object rawOptions)
+ ///
+ /// Run the 'compare' command.
+ ///
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- var options = (CompareOptions)rawOptions;
+ var opts = (CompareOptions)args;
+ if (string.IsNullOrWhiteSpace(opts.Left) || string.IsNullOrWhiteSpace(opts.Right))
+ {
+ _user.RaiseMessage("compare - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
+
+ var leftVersion = new ModuleVersion(opts.Left);
+ var rightVersion = new ModuleVersion(opts.Right);
+
+ var compareResult = leftVersion.CompareTo(rightVersion);
- if (options.Left != null && options.Right != null)
+ if (opts.MachineReadable)
{
- var leftVersion = new ModuleVersion(options.Left);
- var rightVersion = new ModuleVersion(options.Right);
-
- int compareResult = leftVersion.CompareTo(rightVersion);
-
- if (options.machine_readable)
- {
- user.RaiseMessage(compareResult.ToString());
- }
- else if (compareResult == 0)
- {
- user.RaiseMessage(
- "\"{0}\" and \"{1}\" are the same versions.", leftVersion, rightVersion);
- }
- else if (compareResult < 0)
- {
- user.RaiseMessage(
- "\"{0}\" is lower than \"{1}\".", leftVersion, rightVersion);
- }
- else if (compareResult > 0)
- {
- user.RaiseMessage(
- "\"{0}\" is higher than \"{1}\".", leftVersion, rightVersion);
- }
- else
- {
- user.RaiseMessage(
- "Usage: ckan compare version1 version2");
- }
+ _user.RaiseMessage(compareResult.ToString());
+ }
+ else if (compareResult == 0)
+ {
+ _user.RaiseMessage("\"{0}\" and \"{1}\" are the same versions.", leftVersion, rightVersion);
+ }
+ else if (compareResult < 0)
+ {
+ _user.RaiseMessage("\"{0}\" is lower than \"{1}\".", leftVersion, rightVersion);
}
else
{
- user.RaiseMessage(
- "Usage: ckan compare version1 version2");
- return Exit.BADOPT;
+ _user.RaiseMessage("\"{0}\" is higher than \"{1}\".", leftVersion, rightVersion);
}
- return Exit.OK;
+ return Exit.Ok;
}
}
+
+ [Verb("compare", HelpText = "Compare version strings")]
+ internal class CompareOptions : CommonOptions
+ {
+ [Option("machine-readable", HelpText = "Output in a machine readable format: -1, 0 or 1")]
+ public bool MachineReadable { get; set; }
+
+ [Value(0, MetaName = "version1", HelpText = "The first version to compare")]
+ public string Left { get; set; }
+
+ [Value(1, MetaName = "version2", HelpText = "The second version to compare")]
+ public string Right { get; set; }
+ }
}
diff --git a/Cmdline/Action/Compat.cs b/Cmdline/Action/Compat.cs
index 785b82fe6c..8fa9c0c882 100644
--- a/Cmdline/Action/Compat.cs
+++ b/Cmdline/Action/Compat.cs
@@ -1,206 +1,212 @@
using System.Linq;
using CKAN.Versioning;
using CommandLine;
-using CommandLine.Text;
namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for managing KSP version compatibility.
+ ///
public class Compat : ISubCommand
{
- public Compat() { }
+ private GameInstanceManager _manager;
+ private IUser _user;
+
+ ///
+ /// Run the 'compat' command.
+ ///
+ ///
+ public int RunCommand(GameInstanceManager manager, object args)
+ {
+ var s = args.ToString();
+ var opts = s.Replace(s.Substring(0, s.LastIndexOf('.') + 1), "").Split('+');
+
+ CommonOptions options = new CommonOptions();
+ _user = new ConsoleUser(options.Headless);
+ _manager = manager ?? new GameInstanceManager(_user);
+ var exitCode = options.Handle(_manager, _user);
+
+ if (exitCode != Exit.Ok)
+ return exitCode;
+
+ switch (opts[1])
+ {
+ case "AddCompat":
+ exitCode = AddCompatibility(args);
+ break;
+ case "ForgetCompat":
+ exitCode = ForgetCompatibility(args);
+ break;
+ case "ListCompat":
+ exitCode = ListCompatibility();
+ break;
+ default:
+ exitCode = Exit.BadOpt;
+ break;
+ }
- public class CompatOptions : VerbCommandOptions
+ return exitCode;
+ }
+
+ ///
+ public string GetUsage(string prefix, string[] args)
{
- [VerbOption("list", HelpText = "List compatible KSP versions")]
- public CompatListOptions List { get; set; }
+ if (args.Length == 1)
+ return $"{prefix} {args[0]} [options]";
- [VerbOption("add", HelpText = "Add version to KSP compatibility list")]
- public CompatAddOptions Add { get; set; }
+ switch (args[1])
+ {
+ case "add":
+ case "forget":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "list":
+ return $"{prefix} {args[0]} {args[1]} [options]";
+ default:
+ return $"{prefix} {args[0]} [options]";
+ }
+ }
- [VerbOption("forget", HelpText = "Forget version on KSP compatibility list")]
- public CompatForgetOptions Forget { get; set; }
+ private int AddCompatibility(object args)
+ {
+ var opts = (CompatOptions.AddCompat)args;
+ if (opts.Version == null)
+ {
+ _user.RaiseMessage("add - argument missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
- [HelpVerbOption]
- public string GetUsage(string verb)
+ var inst = MainClass.GetGameInstance(_manager);
+ if (GameVersion.TryParse(opts.Version, out GameVersion gameVersion))
+ {
+ var newCompatibleVersion = inst.GetCompatibleVersions();
+ newCompatibleVersion.Add(gameVersion);
+ inst.SetCompatibleVersions(newCompatibleVersion);
+ _user.RaiseMessage("Successfully added \"{0}\".", gameVersion);
+ }
+ else
+ {
+ _user.RaiseError("Invalid KSP version.");
+ return Exit.Error;
+ }
+
+ return Exit.Ok;
+ }
+
+ private int ForgetCompatibility(object args)
+ {
+ var opts = (CompatOptions.ForgetCompat)args;
+ if (opts.Version == null)
{
- HelpText ht = HelpText.AutoBuild(this, verb);
- // Add a usage prefix line
- ht.AddPreOptionsLine(" ");
- if (string.IsNullOrEmpty(verb))
+ _user.RaiseMessage("forget - argument missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
+
+ var inst = MainClass.GetGameInstance(_manager);
+ if (GameVersion.TryParse(opts.Version, out GameVersion gameVersion))
+ {
+ if (gameVersion != inst.Version())
{
- ht.AddPreOptionsLine("ckan compat - Manage KSP version compatibility");
- ht.AddPreOptionsLine($"Usage: ckan compat [options]");
+ var newCompatibleVersion = inst.GetCompatibleVersions();
+ newCompatibleVersion.RemoveAll(i => i == gameVersion);
+ inst.SetCompatibleVersions(newCompatibleVersion);
+ _user.RaiseMessage("Successfully removed \"{0}\".", gameVersion);
}
else
{
- ht.AddPreOptionsLine("compat " + verb + " - " + GetDescription(verb));
- switch (verb)
- {
- // First the commands with one string argument
- case "add":
- case "forget":
- ht.AddPreOptionsLine($"Usage: ckan compat {verb} [options] version");
- break;
-
- // Now the commands with only --flag type options
- case "list":
- default:
- ht.AddPreOptionsLine($"Usage: ckan compat {verb} [options]");
- break;
- }
+ _user.RaiseError("Cannot forget actual KSP version.");
+ return Exit.Error;
}
- return ht;
}
- }
+ else
+ {
+ _user.RaiseError("Invalid KSP version.");
+ return Exit.Error;
+ }
- public class CompatListOptions : InstanceSpecificOptions { }
+ return Exit.Ok;
+ }
- public class CompatAddOptions : InstanceSpecificOptions
+ private int ListCompatibility()
{
- [ValueOption(0)] public string Version { get; set; }
+ const string versionHeader = "Version";
+ const string actualHeader = "Actual";
+
+ var inst = MainClass.GetGameInstance(_manager);
+
+ var output = inst
+ .GetCompatibleVersions()
+ .Select(i => new
+ {
+ Version = i,
+ Actual = false
+ })
+ .ToList();
+
+ output.Add(new
+ {
+ Version = inst.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);
+
+ _user.RaiseMessage("{0} {1}",
+ versionHeader.PadRight(versionWidth),
+ actualHeader.PadRight(actualWidth)
+ );
+
+ _user.RaiseMessage("{0} {1}",
+ new string('-', versionWidth),
+ new string('-', actualWidth)
+ );
+
+ foreach (var line in output)
+ {
+ _user.RaiseMessage("{0} {1}",
+ line.Version.ToString().PadRight(versionWidth),
+ line.Actual.ToString().PadRight(actualWidth)
+ );
+ }
+
+ return Exit.Ok;
}
+ }
- public class CompatForgetOptions : InstanceSpecificOptions
+ [Verb("compat", HelpText = "Manage KSP version compatibility")]
+ [ChildVerbs(typeof(AddCompat), typeof(ForgetCompat), typeof(ListCompat))]
+ internal class CompatOptions
+ {
+ [VerbExclude]
+ [Verb("add", HelpText = "Add version to KSP compatibility list")]
+ internal class AddCompat : InstanceSpecificOptions
{
- [ValueOption(0)] public string Version { get; set; }
+ [Value(0, MetaName = "KSP version", HelpText = "The KSP version to add as compatible")]
+ public string Version { get; set; }
}
- public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCommandOptions options)
+ [VerbExclude]
+ [Verb("forget", HelpText = "Forget version on KSP compatibility list")]
+ internal class ForgetCompat : InstanceSpecificOptions
{
- var exitCode = Exit.OK;
-
- Parser.Default.ParseArgumentsStrict(options.options.ToArray(), new CompatOptions(), (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);
- if (exitCode != Exit.OK)
- return;
-
- switch (option)
- {
- case "list":
- {
- var ksp = MainClass.GetGameInstance(_kspManager);
-
- const string versionHeader = "Version";
- const string actualHeader = "Actual";
-
- var output = ksp
- .GetCompatibleVersions()
- .Select(i => new
- {
- Version = i,
- Actual = false
- })
- .ToList();
-
- output.Add(new
- {
- Version = ksp.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(string.Format(columnFormat,
- line.Version.ToString().PadRight(versionWidth),
- line.Actual.ToString().PadRight(actualWidth)
- ));
- }
- }
- break;
-
- case "add":
- {
- var ksp = MainClass.GetGameInstance(_kspManager);
- var addOptions = (CompatAddOptions)suboptions;
-
- GameVersion GameVersion;
- if (GameVersion.TryParse(addOptions.Version, out GameVersion))
- {
- var newCompatibleVersion = ksp.GetCompatibleVersions();
- newCompatibleVersion.Add(GameVersion);
- ksp.SetCompatibleVersions(newCompatibleVersion);
- }
- else
- {
- _user.RaiseError("ERROR: Invalid KSP version.");
- exitCode = Exit.ERROR;
- }
- }
- break;
-
- case "forget":
- {
- var ksp = MainClass.GetGameInstance(_kspManager);
- var addOptions = (CompatForgetOptions)suboptions;
-
- GameVersion GameVersion;
- if (GameVersion.TryParse(addOptions.Version, out GameVersion))
- {
- if (GameVersion != ksp.Version())
- {
- var newCompatibleVersion = ksp.GetCompatibleVersions();
- newCompatibleVersion.RemoveAll(i => i == GameVersion);
- ksp.SetCompatibleVersions(newCompatibleVersion);
- }
- else
- {
- _user.RaiseError("ERROR: Cannot forget actual KSP version.");
- exitCode = Exit.ERROR;
- }
- }
- else
- {
- _user.RaiseError("ERROR: Invalid KSP version.");
- exitCode = Exit.ERROR;
- }
- }
- break;
-
- default:
- exitCode = Exit.BADOPT;
- break;
- }
- }
- }, () => { exitCode = MainClass.AfterHelp(); });
- return exitCode;
+ [Value(0, MetaName = "KSP version", HelpText = "The KSP version to remove as compatible")]
+ public string Version { get; set; }
}
- private GameInstanceManager _kspManager;
- private IUser _user;
+ [VerbExclude]
+ [Verb("list", HelpText = "List compatible KSP versions")]
+ internal class ListCompat : InstanceSpecificOptions { }
}
}
diff --git a/Cmdline/Action/GameInstance.cs b/Cmdline/Action/GameInstance.cs
index 5112d76a50..8272a6d78b 100644
--- a/Cmdline/Action/GameInstance.cs
+++ b/Cmdline/Action/GameInstance.cs
@@ -2,663 +2,596 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using CKAN.DLC;
+using CKAN.Games;
+using CKAN.Versioning;
using CommandLine;
-using CommandLine.Text;
using log4net;
-using CKAN.Versioning;
-using CKAN.Games;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for managing KSP installs.
+ ///
public class GameInstance : ISubCommand
{
- public GameInstance() { }
- protected static readonly ILog log = LogManager.GetLogger(typeof(GameInstance));
-
- internal class InstanceSubOptions : VerbCommandOptions
- {
- [VerbOption("list", HelpText = "List game instances")]
- public CommonOptions ListOptions { get; set; }
-
- [VerbOption("add", HelpText = "Add a game instance")]
- public AddOptions AddOptions { get; set; }
-
- [VerbOption("clone", HelpText = "Clone an existing game instance")]
- public CloneOptions CloneOptions { get; set; }
-
- [VerbOption("rename", HelpText = "Rename a game instance")]
- public RenameOptions RenameOptions { get; set; }
-
- [VerbOption("forget", HelpText = "Forget a game instance")]
- public ForgetOptions ForgetOptions { get; set; }
-
- [VerbOption("default", HelpText = "Set the default game instance")]
- public DefaultOptions DefaultOptions { get; set; }
-
- [VerbOption("fake", HelpText = "Fake a game instance")]
- public FakeOptions FakeOptions { get; set; }
-
- [HelpVerbOption]
- public string GetUsage(string verb)
- {
- HelpText ht = HelpText.AutoBuild(this, verb);
- // Add a usage prefix line
- ht.AddPreOptionsLine(" ");
- if (string.IsNullOrEmpty(verb))
- {
- ht.AddPreOptionsLine("ckan instance - Manage game instances");
- ht.AddPreOptionsLine($"Usage: ckan instance [options]");
- }
- else
- {
- ht.AddPreOptionsLine("instance " + verb + " - " + GetDescription(verb));
- switch (verb)
- {
- // First the commands with three string arguments
- case "fake":
- ht.AddPreOptionsLine($"Usage: ckan instance {verb} [options] name path version [--MakingHistory ] [--BreakingGround ]");
- break;
-
- case "clone":
- ht.AddPreOptionsLine($"Usage: ckan instance {verb} [options] instanceNameOrPath newname newpath");
- break;
-
- // Second the commands with two string arguments
- case "add":
- ht.AddPreOptionsLine($"Usage: ckan instance {verb} [options] name url");
- break;
- case "rename":
- ht.AddPreOptionsLine($"Usage: ckan instance {verb} [options] oldname newname");
- break;
-
- // Now the commands with one string argument
- case "remove":
- case "forget":
- case "use":
- case "default":
- ht.AddPreOptionsLine($"Usage: ckan instance {verb} [options] name");
- break;
+ private static readonly ILog Log = LogManager.GetLogger(typeof(GameInstance));
- // Now the commands with only --flag type options
- case "list":
- default:
- ht.AddPreOptionsLine($"Usage: ckan instance {verb} [options]");
- break;
+ private GameInstanceManager _manager;
+ private IUser _user;
- }
- }
- return ht;
- }
- }
-
- internal class AddOptions : CommonOptions
- {
- [ValueOption(0)] public string name { get; set; }
- [ValueOption(1)] public string path { get; set; }
- }
-
- internal class CloneOptions : CommonOptions
- {
- [ValueOption(0)] public string nameOrPath { get; set; }
- [ValueOption(1)] public string new_name { get; set; }
- [ValueOption(2)] public string new_path { get; set; }
- }
-
- internal class RenameOptions : CommonOptions
- {
- [ValueOption(0)] public string old_name { get; set; }
- [ValueOption(1)] public string new_name { get; set; }
- }
-
- internal class ForgetOptions : CommonOptions
- {
- [ValueOption(0)] public string name { get; set; }
- }
-
- internal class DefaultOptions : CommonOptions
- {
- [ValueOption(0)] public string name { get; set; }
- }
-
- internal class FakeOptions : CommonOptions
- {
- [ValueOption(0)] public string name { get; set; }
- [ValueOption(1)] public string path { get; set; }
- [ValueOption(2)] public string version { get; set; }
-
- [Option("MakingHistory", DefaultValue = "none", HelpText = "The version of the Making History DLC to be faked.")]
- public string makingHistoryVersion { get; set; }
- [Option("BreakingGround", DefaultValue = "none", HelpText = "The version of the Breaking Ground DLC to be faked.")]
- public string breakingGroundVersion { get; set; }
-
- [Option("set-default", DefaultValue = false, HelpText = "Set the new instance as the default one.")]
- public bool setDefault { get; set; }
- }
-
- // This is required by ISubCommand
- public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCommandOptions unparsed)
+ ///
+ /// Run the 'ksp' command.
+ ///
+ ///
+ public int RunCommand(GameInstanceManager manager, object args)
{
- string[] args = unparsed.options.ToArray();
-
- #region Aliases
-
- for (int i = 0; i < args.Length; i++)
- {
- switch (args[i])
- {
- case "use":
- args[i] = "default";
- break;
-
- default:
- break;
- }
+ var s = args.ToString();
+ var opts = s.Replace(s.Substring(0, s.LastIndexOf('.') + 1), "").Split('+');
+
+ CommonOptions options = new CommonOptions();
+ _user = new ConsoleUser(options.Headless);
+ _manager = manager ?? new GameInstanceManager(_user);
+ var exitCode = options.Handle(_manager, _user);
+
+ if (exitCode != Exit.Ok)
+ return exitCode;
+
+ switch (opts[1])
+ {
+ case "AddKsp":
+ exitCode = AddInstall(args);
+ break;
+ case "CloneKsp":
+ exitCode = CloneInstall(args);
+ break;
+ case "DefaultKsp":
+ exitCode = SetDefaultInstall(args);
+ break;
+ case "FakeKsp":
+ exitCode = FakeNewKspInstall(args);
+ break;
+ case "ForgetKsp":
+ exitCode = ForgetInstall(args);
+ break;
+ case "ListKsp":
+ exitCode = ListInstalls();
+ break;
+ case "RenameKsp":
+ exitCode = RenameInstall(args);
+ break;
+ default:
+ exitCode = Exit.BadOpt;
+ break;
}
- #endregion
-
- int exitCode = Exit.OK;
- // Parse and process our sub-verbs
- Parser.Default.ParseArgumentsStrict(args, new InstanceSubOptions(), (string option, object suboptions) =>
- {
- // ParseArgumentsStrict calls us unconditionally, even with bad arguments
- if (!string.IsNullOrEmpty(option) && suboptions != null)
- {
- CommonOptions options = (CommonOptions)suboptions;
- options.Merge(opts);
- User = new ConsoleUser(options.Headless);
- Manager = manager ?? new GameInstanceManager(User);
- exitCode = options.Handle(Manager, User);
- if (exitCode != Exit.OK)
- return;
-
- switch (option)
- {
- case "list":
- exitCode = ListInstalls();
- break;
-
- case "add":
- exitCode = AddInstall((AddOptions)suboptions);
- break;
-
- case "clone":
- exitCode = CloneInstall((CloneOptions)suboptions);
- break;
-
- case "rename":
- exitCode = RenameInstall((RenameOptions)suboptions);
- break;
-
- case "forget":
- exitCode = ForgetInstall((ForgetOptions)suboptions);
- break;
-
- case "use":
- case "default":
- exitCode = SetDefaultInstall((DefaultOptions)suboptions);
- break;
-
- case "fake":
- exitCode = FakeNewGameInstance((FakeOptions)suboptions);
- break;
-
- default:
- User.RaiseMessage("Unknown command: instance {0}", option);
- exitCode = Exit.BADOPT;
- break;
- }
- }
- }, () => { exitCode = MainClass.AfterHelp(); });
return exitCode;
}
- private GameInstanceManager Manager { get; set; }
- private IUser User { get; set; }
-
- #region option functions
-
- private int ListInstalls()
+ ///
+ public string GetUsage(string prefix, string[] args)
{
- var output = Manager.Instances
- .OrderByDescending(i => i.Value.Name == Manager.AutoStartInstance)
- .ThenByDescending(i => i.Value.Version() ?? GameVersion.Any)
- .ThenBy(i => i.Key)
- .Select(i => new
- {
- Name = i.Key,
- Version = i.Value.Version()?.ToString() ?? "",
- Default = i.Value.Name == Manager.AutoStartInstance ? "Yes" : "No",
- Path = i.Value.GameDir()
- })
- .ToList();
-
- const string nameHeader = "Name";
- const string versionHeader = "Version";
- const string defaultHeader = "Default";
- const string pathHeader = "Path";
-
- var nameWidth = Enumerable.Repeat(nameHeader, 1).Concat(output.Select(i => i.Name)).Max(i => i.Length);
- var versionWidth = Enumerable.Repeat(versionHeader, 1).Concat(output.Select(i => i.Version)).Max(i => i.Length);
- var defaultWidth = Enumerable.Repeat(defaultHeader, 1).Concat(output.Select(i => i.Default)).Max(i => i.Length);
- var pathWidth = Enumerable.Repeat(pathHeader, 1).Concat(output.Select(i => i.Path)).Max(i => i.Length);
-
- const string columnFormat = "{0} {1} {2} {3}";
-
- User.RaiseMessage(string.Format(columnFormat,
- nameHeader.PadRight(nameWidth),
- versionHeader.PadRight(versionWidth),
- defaultHeader.PadRight(defaultWidth),
- pathHeader.PadRight(pathWidth)
- ));
-
- User.RaiseMessage(string.Format(columnFormat,
- new string('-', nameWidth),
- new string('-', versionWidth),
- new string('-', defaultWidth),
- new string('-', pathWidth)
- ));
-
- foreach (var line in output)
- {
- User.RaiseMessage(string.Format(columnFormat,
- line.Name.PadRight(nameWidth),
- line.Version.PadRight(versionWidth),
- line.Default.PadRight(defaultWidth),
- line.Path.PadRight(pathWidth)
- ));
+ if (args.Length == 1)
+ return $"{prefix} {args[0]} [options]";
+
+ switch (args[1])
+ {
+ case "add":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "clone":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "fake":
+ return $"{prefix} {args[0]} {args[1]} [options] [--MakingHistory ] [--BreakingGround ]";
+ case "default":
+ case "forget":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "list":
+ return $"{prefix} {args[0]} {args[1]} [options]";
+ case "rename":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ default:
+ return $"{prefix} {args[0]} [options]";
}
-
- return Exit.OK;
}
- private int AddInstall(AddOptions options)
+ private int AddInstall(object args)
{
- if (options.name == null || options.path == null)
+ var opts = (KspOptions.AddKsp)args;
+ if (opts.Name == null || opts.Path == null)
{
- User.RaiseMessage("add - argument missing, perhaps you forgot it?");
- return Exit.BADOPT;
+ _user.RaiseMessage("add - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
- if (Manager.HasInstance(options.name))
+ if (_manager.HasInstance(opts.Name))
{
- User.RaiseMessage("Install with name \"{0}\" already exists, aborting..", options.name);
- return Exit.BADOPT;
+ _user.RaiseMessage("Install with the name \"{0}\" already exists, aborting...", opts.Name);
+ return Exit.BadOpt;
}
try
{
- string path = options.path;
- Manager.AddInstance(path, options.name, User);
- User.RaiseMessage("Added \"{0}\" with root \"{1}\" to known installs", options.name, options.path);
- return Exit.OK;
+ _manager.AddInstance(opts.Path, opts.Name, _user);
+ _user.RaiseMessage("Added \"{0}\" with root \"{1}\" to known installs.", opts.Name, opts.Path);
}
- catch (NotKSPDirKraken ex)
+ catch (NotKSPDirKraken kraken)
{
- User.RaiseMessage("Sorry, {0} does not appear to be a game instance", ex.path);
- return Exit.BADOPT;
+ _user.RaiseMessage("Sorry, \"{0}\" does not appear to be a KSP directory.", kraken.path);
+ return Exit.Error;
}
+
+ return Exit.Ok;
}
- private int CloneInstall(CloneOptions options)
+ private int CloneInstall(object args)
{
- if (options.nameOrPath == null || options.new_name == null || options.new_path == null)
+ var opts = (KspOptions.CloneKsp)args;
+ if (opts.NameOrPath == null || opts.NewName == null || opts.NewPath == null)
{
- User.RaiseMessage("instance clone - argument(s) missing");
- return Exit.BADOPT;
+ _user.RaiseMessage("clone - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
// Parse all options
- string instanceNameOrPath = options.nameOrPath;
- string newName = options.new_name;
- string newPath = options.new_path;
-
-
- log.Info("Cloning the game instance: " + options.nameOrPath);
+ var nameOrPath = opts.NameOrPath;
+ var newName = opts.NewName;
+ var newPath = opts.NewPath;
+ Log.InfoFormat("Cloning the KSP instance \"{0}\".", nameOrPath);
try
{
- // Try instanceNameOrPath as name and search the registry for it.
- if (Manager.HasInstance(instanceNameOrPath))
+ // Try nameOrPath as name and search the registry for it
+ if (_manager.HasInstance(nameOrPath))
{
- CKAN.GameInstance[] listOfInstances = Manager.Instances.Values.ToArray();
- foreach (CKAN.GameInstance instance in listOfInstances)
+ var listOfInstances = _manager.Instances.Values.ToArray();
+ foreach (var instance in listOfInstances)
{
- if (instance.Name == instanceNameOrPath)
+ if (instance.Name == nameOrPath)
{
- // Found it, now clone it.
- Manager.CloneInstance(instance, newName, newPath);
+ // Found it, now clone it
+ _manager.CloneInstance(instance, newName, newPath);
break;
}
}
}
- // Try to use instanceNameOrPath as a path and create a new game instance.
- // If it's valid, go on.
- else if (Manager.InstanceAt(instanceNameOrPath, newName) is CKAN.GameInstance instance && instance.Valid)
+ // Try to use nameOrPath as a path and create a new game object
+ else if (_manager.InstanceAt(nameOrPath, newName) is CKAN.GameInstance instance && instance.Valid)
{
- Manager.CloneInstance(instance, newName, newPath);
+ _manager.CloneInstance(instance, newName, newPath);
}
- // There is no instance with this name or at this path.
+ // There is no instance with this name or at this path
else
{
- throw new NoGameInstanceKraken();
+ throw new NoGameInstanceKraken(nameOrPath);
}
}
catch (NotKSPDirKraken kraken)
{
- // Two possible reasons:
- // First: The instance to clone is not a valid game instance.
- // Only occurs if user manipulated directory and deleted files/folders
- // which CKAN searches for in validity test.
+ // There are two possible reasons:
+ // First: The instance to clone is not a valid KSP instance
+ // This only occurs if the user manipulated the directory and deleted
+ // files/folders which CKAN searches for in the validity test
- // Second: Something went wrong adding the new instance to the registry,
- // most likely because the newly created directory is not valid.
+ // Second: Something went wrong with adding the new instance to the registry,
+ // most likely because the newly created directory is not valid
- log.Error(kraken);
- return Exit.ERROR;
+ _user.RaiseError("The specified instance \"{0}\" is not a valid KSP instance.\r\n {1}", nameOrPath, kraken.path);
+ return Exit.Error;
}
catch (PathErrorKraken kraken)
{
// The new path is not empty
- // The kraken contains a message to inform the user.
- log.Error(kraken.Message + kraken.path);
- return Exit.ERROR;
+ _user.RaiseError("The directory to clone to is not empty.\r\n {0}", kraken.path);
+ return Exit.Error;
}
- catch (IOException e)
+ catch (IOException ex)
{
- // Something went wrong copying the files. Contains a message.
- log.Error(e);
- return Exit.ERROR;
+ // Something went wrong while copying the files
+ _user.RaiseError(ex.ToString());
+ return Exit.Error;
}
catch (NoGameInstanceKraken)
{
- User.RaiseError(String.Format("No instance with this name or at this path: {0}\n See below for a list of known instances:\n", instanceNameOrPath));
+ // Did not find a known game instance
+ _user.RaiseError("No instance found with this name or at this path: \"{0}\".\r\nSee below for a list of known instances:\r\n", nameOrPath);
ListInstalls();
- return Exit.ERROR;
+ return Exit.Error;
}
catch (InstanceNameTakenKraken kraken)
{
- User.RaiseError("This instance name is already taken: {0}", kraken.instName);
- return Exit.BADOPT;
+ // Instance name already exists
+ _user.RaiseError("An instance with the name \"{0}\" already exists.", kraken.instName);
+ return Exit.BadOpt;
}
- // Test if the instance was added to the registry.
+ // Test if the instance was added to the registry
// No need to test if valid, because this is done in AddInstance(),
- // so if something went wrong, HasInstance is false.
- if (Manager.HasInstance(newName))
- {
- return Exit.OK;
- }
- else
+ // so if something went wrong, HasInstance is false
+ if (!_manager.HasInstance(newName))
{
- User.RaiseMessage("Something went wrong. Please look if the new directory has been created.\n",
- "Try to add the new instance manually with \"ckan instance add\".\n");
- return Exit.ERROR;
- }
- }
-
- private int RenameInstall(RenameOptions options)
- {
- if (options.old_name == null || options.new_name == null)
- {
- User.RaiseMessage("rename - argument missing, perhaps you forgot it?");
- return Exit.BADOPT;
- }
-
- if (!Manager.HasInstance(options.old_name))
- {
- User.RaiseMessage("Couldn't find install with name \"{0}\", aborting..", options.old_name);
- return Exit.BADOPT;
- }
-
- Manager.RenameInstance(options.old_name, options.new_name);
-
- User.RaiseMessage("Successfully renamed \"{0}\" to \"{1}\"", options.old_name, options.new_name);
- return Exit.OK;
- }
-
- private int ForgetInstall(ForgetOptions options)
- {
- if (options.name == null)
- {
- User.RaiseMessage("forget - argument missing, perhaps you forgot it?");
- return Exit.BADOPT;
- }
-
- if (!Manager.HasInstance(options.name))
- {
- User.RaiseMessage("Couldn't find install with name \"{0}\", aborting..", options.name);
- return Exit.BADOPT;
+ _user.RaiseMessage("Something went wrong. Please look if the new directory has been created.\r\nTry to add the new instance manually with 'ckan ksp add'.");
+ return Exit.Error;
}
- Manager.RemoveInstance(options.name);
-
- User.RaiseMessage("Successfully removed \"{0}\"", options.name);
- return Exit.OK;
+ _user.RaiseMessage("Successfully cloned the instance \"{0}\" into \"{1}\".", nameOrPath, newPath);
+ return Exit.Ok;
}
- private int SetDefaultInstall(DefaultOptions options)
+ private int SetDefaultInstall(object args)
{
- string name = options.name;
+ var opts = (KspOptions.DefaultKsp)args;
+ var name = opts.Name;
if (name == null)
{
- // No input argument from the user. Present a list of the possible instances.
- string message = "default - argument missing, please select from the list below.";
-
- // Check if there is a default instance.
- string defaultInstance = Manager.Configuration.AutoStartInstance;
- int defaultInstancePresent = 0;
+ // Check if there is a default instance
+ var defaultInstance = _manager.Configuration.AutoStartInstance;
+ var defaultInstancePresent = 0;
- if (!String.IsNullOrWhiteSpace(defaultInstance))
+ if (!string.IsNullOrWhiteSpace(defaultInstance))
{
defaultInstancePresent = 1;
}
- object[] keys = new object[Manager.Instances.Count + defaultInstancePresent];
+ var keys = new object[_manager.Instances.Count + defaultInstancePresent];
- // Populate the list of instances.
- for (int i = 0; i < Manager.Instances.Count; i++)
+ // Populate the list of instances
+ for (var i = 0; i < _manager.Instances.Count; i++)
{
- var instance = Manager.Instances.ElementAt(i);
+ var instance = _manager.Instances.ElementAt(i);
- keys[i + defaultInstancePresent] = String.Format("\"{0}\" - {1}", instance.Key, instance.Value.GameDir());
+ keys[i + defaultInstancePresent] = string.Format("\"{0}\" - {1}", instance.Key, instance.Value.GameDir());
}
- // Mark the default instance for the user.
- if (!String.IsNullOrWhiteSpace(defaultInstance))
+ // Mark the default instance for the user
+ if (!string.IsNullOrWhiteSpace(defaultInstance))
{
- keys[0] = Manager.Instances.IndexOfKey(defaultInstance);
+ keys[0] = _manager.Instances.IndexOfKey(defaultInstance);
}
int result;
-
try
{
- result = User.RaiseSelectionDialog(message, keys);
+ // No input argument from the user. Present a list of the possible instances
+ var message = "default - argument missing, please select an instance from the list below.";
+ result = _user.RaiseSelectionDialog(message, keys);
}
catch (Kraken)
{
- return Exit.BADOPT;
+ return Exit.BadOpt;
}
if (result < 0)
{
- return Exit.BADOPT;
+ return Exit.BadOpt;
}
- name = Manager.Instances.ElementAt(result).Key;
+ name = _manager.Instances.ElementAt(result).Key;
}
- if (!Manager.Instances.ContainsKey(name))
+ if (!_manager.Instances.ContainsKey(name))
{
- User.RaiseMessage("Couldn't find install with name \"{0}\", aborting..", name);
- return Exit.BADOPT;
+ _user.RaiseMessage("Couldn't find an install with the name \"{0}\", aborting...", name);
+ return Exit.BadOpt;
}
try
{
- Manager.SetAutoStart(name);
+ _manager.SetAutoStart(name);
}
- catch (NotKSPDirKraken k)
+ catch (NotKSPDirKraken kraken)
{
- User.RaiseMessage("Sorry, {0} does not appear to be a game instance", k.path);
- return Exit.BADOPT;
+ _user.RaiseMessage("Sorry, \"{0}\" does not appear to be a KSP directory.", kraken.path);
+ return Exit.Error;
}
- User.RaiseMessage("Successfully set \"{0}\" as the default game instance", name);
- return Exit.OK;
+ _user.RaiseMessage("Successfully set \"{0}\" as the default KSP installation.", name);
+ return Exit.Ok;
}
- ///
- /// Creates a new fake game instance after the conditions CKAN tests for valid install directories.
- /// Used for developing and testing purposes.
- ///
- private int FakeNewGameInstance(FakeOptions options)
+ private int FakeNewKspInstall(object args)
{
- int error()
+ var opts = (KspOptions.FakeKsp)args;
+ if (opts.Name == null || opts.Path == null || opts.Version == null)
{
- log.Debug("Instance faking failed, see console output for details.");
- User.RaiseMessage("--Error--");
- return Exit.ERROR;
+ _user.RaiseMessage("fake [--MakingHistory ] [--BreakingGround ] - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
- int badArgument()
+ // opts.Version is "none" if the DLC shouldn't be simulated
+ var dlcs = new Dictionary();
+ if (opts.MakingHistory != null && opts.MakingHistory.ToLower() != "none")
{
- log.Debug("Instance faking failed: bad argument(s). See console output for details.");
- User.RaiseMessage("--Error: bad argument(s)--");
- return Exit.BADOPT;
- }
-
-
- if (options.name == null || options.path == null || options.version == null)
- {
- User.RaiseMessage("instance fake " +
- "[--MakingHistory ] [--BreakingGround ] - argument(s) missing");
- return badArgument();
- }
-
- log.Debug("Parsing arguments...");
- // Parse all options
- string installName = options.name;
- string path = options.path;
- GameVersion version;
- bool setDefault = options.setDefault;
-
- // options.Version is "none" if the DLC should not be simulated.
- Dictionary dlcs = new Dictionary();
- if (options.makingHistoryVersion != null && options.makingHistoryVersion.ToLower() != "none")
- {
- if (GameVersion.TryParse(options.makingHistoryVersion, out GameVersion ver))
+ if (GameVersion.TryParse(opts.MakingHistory, out GameVersion ver))
{
- dlcs.Add(new DLC.MakingHistoryDlcDetector(), ver);
+ dlcs.Add(new MakingHistoryDlcDetector(), ver);
}
else
{
- User.RaiseError("Please check the Making History DLC version argument - Format it like Maj.Min.Patch - e.g. 1.1.0");
- return badArgument();
+ _user.RaiseError("Please check the Making History DLC version argument - Format it like Maj.Min.Patch - e.g. 1.1.0");
+ return Exit.BadOpt;
}
}
- if (options.breakingGroundVersion != null && options.breakingGroundVersion.ToLower() != "none")
+
+ if (opts.BreakingGround != null && opts.BreakingGround.ToLower() != "none")
{
- if (GameVersion.TryParse(options.breakingGroundVersion, out GameVersion ver))
+ if (GameVersion.TryParse(opts.BreakingGround, out GameVersion ver))
{
- dlcs.Add(new DLC.BreakingGroundDlcDetector(), ver);
+ dlcs.Add(new BreakingGroundDlcDetector(), ver);
}
else
{
- User.RaiseError("Please check the Breaking Ground DLC version argument - Format it like Maj.Min.Patch - e.g. 1.1.0");
- return badArgument();
+ _user.RaiseError("Please check the Breaking Ground DLC version argument - Format it like Maj.Min.Patch - e.g. 1.1.0");
+ return Exit.BadOpt;
}
}
- // Parse the choosen game version
+ // Parse all options
+ var name = opts.Name;
+ var path = opts.Path;
+ var setDefault = opts.SetDefault;
+ GameVersion version;
+
try
{
- version = GameVersion.Parse(options.version);
+ version = GameVersion.Parse(opts.Version);
}
catch (FormatException)
{
- // Thrown if there is anything besides numbers and points in the version string or a different syntactic error.
- User.RaiseError("Please check the version argument - Format it like Maj.Min.Patch[.Build] - e.g. 1.6.0 or 1.2.2.1622");
- return badArgument();
+ // Thrown if there is anything besides numbers and points in the version string or a different syntactic error
+ _user.RaiseError("Please check the version argument - Format it like Maj.Min.Patch[.Build] - e.g. 1.6.0 or 1.2.2.1622");
+ return Exit.BadOpt;
}
- // Get the full version including build number.
+ // Get the full version including build number
try
{
- version = version.RaiseVersionSelectionDialog(new KerbalSpaceProgram(), User);
+ version = version.RaiseVersionSelectionDialog(new KerbalSpaceProgram(), _user);
}
catch (BadGameVersionKraken)
{
- User.RaiseError("Couldn't find a valid game version for your input.\n" +
- "Make sure to enter the at least the version major and minor values in the form Maj.Min - e.g. 1.5");
- return badArgument();
+ _user.RaiseError("Couldn't find a valid KSP version for your input.\r\nMake sure to enter at least the version major and minor values in the form Maj.Min - e.g. 1.5");
+ return Exit.Error;
}
catch (CancelledActionKraken)
{
- User.RaiseError("Selection cancelled! Please call 'ckan instance fake' again.");
- return error();
+ _user.RaiseError("Selection cancelled! Please call 'ckan ksp fake' again.");
+ return Exit.Error;
}
- User.RaiseMessage(String.Format("Creating new fake game instance {0} at {1} with version {2}", installName, path, version.ToString()));
- log.Debug("Faking instance...");
+ _user.RaiseMessage("Creating a new fake KSP install with the name \"{0}\" at \"{1}\" with version \"{2}\".", name, path, version);
+ Log.Debug("Faking instance...");
try
{
- // Pass all arguments to CKAN.GameInstanceManager.FakeInstance() and create a new one.
- Manager.FakeInstance(new KerbalSpaceProgram(), installName, path, version, dlcs);
+ // Pass all arguments to CKAN.GameInstanceManager.FakeInstance() and create a new one
+ _manager.FakeInstance(new KerbalSpaceProgram(), name, path, version, dlcs);
if (setDefault)
{
- User.RaiseMessage("Setting new instance to default...");
- Manager.SetAutoStart(installName);
+ _user.RaiseMessage("Setting new instance to default...");
+ _manager.SetAutoStart(name);
}
}
catch (InstanceNameTakenKraken kraken)
{
- User.RaiseError("This instance name is already taken: {0}", kraken.instName);
- return badArgument();
+ // Instance name already exists
+ _user.RaiseError("An instance with the name \"{0}\" already exists.", kraken.instName);
+ return Exit.BadOpt;
}
- catch (BadInstallLocationKraken kraken)
+ catch (BadInstallLocationKraken)
{
- // The folder exists and is not empty.
- User.RaiseError(kraken.Message);
- return badArgument();
+ // The folder exists but is not empty
+ _user.RaiseError("The directory to clone to is not empty.\r\n {0}", path);
+ return Exit.Error;
}
catch (WrongGameVersionKraken kraken)
{
- // Thrown because the specified game instance is too old for one of the selected DLCs.
- User.RaiseError(kraken.Message);
- return badArgument();
+ // Thrown because the specified KSP version is too old for one of the selected DLCs
+ _user.RaiseError(kraken.Message);
+ return Exit.Error;
}
catch (NotKSPDirKraken kraken)
{
// Something went wrong adding the new instance to the registry,
- // most likely because the newly created directory is somehow not valid.
- log.Error(kraken);
- return error();
+ // most likely because the newly created directory is somehow not valid
+ _user.RaiseError("The specified instance \"{0}\" is not a valid KSP instance.\r\n {1}", name, kraken.path);
+ return Exit.Error;
}
catch (InvalidKSPInstanceKraken)
{
- // Thrown by Manager.SetAutoStart() if Manager.HasInstance returns false.
+ // Thrown by Manager.SetAutoStart() if Manager.HasInstance returns false
// Will be checked again down below with a proper error message
}
+ // Test if the instance was added to the registry
+ // No need to test if valid, because this is done in AddInstance(),
+ // so if something went wrong, HasInstance is false
+ if (!_manager.HasInstance(name))
+ {
+ _user.RaiseMessage("Something went wrong. Please look if the new directory has been created.\r\nTry to add the new instance manually with 'ckan ksp add'.");
+ return Exit.Error;
+ }
+
+ _user.RaiseMessage("Successfully faked the instance \"{0}\" into \"{1}\".", name, path);
+ return Exit.Ok;
+ }
- // Test if the instance was added to the registry.
- // No need to test if valid, because this is done in AddInstance().
- if (Manager.HasInstance(installName))
+ private int ForgetInstall(object args)
+ {
+ var opts = (KspOptions.ForgetKsp)args;
+ if (opts.Name == null)
{
- User.RaiseMessage("--Done--");
- return Exit.OK;
+ _user.RaiseMessage("forget - argument missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
- else
+
+ if (!_manager.HasInstance(opts.Name))
{
- User.RaiseError("Something went wrong. Try to add the instance yourself with \"ckan instance add\".",
- "Also look if the new directory has been created.");
- return error();
+ _user.RaiseMessage("Couldn't find an install with the name \"{0}\", aborting...", opts.Name);
+ return Exit.BadOpt;
}
+
+ _manager.RemoveInstance(opts.Name);
+
+ _user.RaiseMessage("Successfully removed \"{0}\".", opts.Name);
+ return Exit.Ok;
+ }
+
+ private int ListInstalls()
+ {
+ const string nameHeader = "Name";
+ const string versionHeader = "Version";
+ const string defaultHeader = "Default";
+ const string pathHeader = "Path";
+
+ var output = _manager.Instances
+ .OrderByDescending(i => i.Value.Name == _manager.AutoStartInstance)
+ .ThenByDescending(i => i.Value.Version() ?? GameVersion.Any)
+ .ThenBy(i => i.Key)
+ .Select(i => new
+ {
+ Name = i.Key,
+ Version = i.Value.Version()?.ToString() ?? "",
+ Default = i.Value.Name == _manager.AutoStartInstance ? "Yes" : "No",
+ Path = i.Value.GameDir()
+ })
+ .ToList();
+
+ var nameWidth = Enumerable.Repeat(nameHeader, 1).Concat(output.Select(i => i.Name)).Max(i => i.Length);
+ var versionWidth = Enumerable.Repeat(versionHeader, 1).Concat(output.Select(i => i.Version)).Max(i => i.Length);
+ var defaultWidth = Enumerable.Repeat(defaultHeader, 1).Concat(output.Select(i => i.Default)).Max(i => i.Length);
+ var pathWidth = Enumerable.Repeat(pathHeader, 1).Concat(output.Select(i => i.Path)).Max(i => i.Length);
+
+ _user.RaiseMessage("{0} {1} {2} {3}",
+ nameHeader.PadRight(nameWidth),
+ versionHeader.PadRight(versionWidth),
+ defaultHeader.PadRight(defaultWidth),
+ pathHeader.PadRight(pathWidth)
+ );
+
+ _user.RaiseMessage("{0} {1} {2} {3}",
+ new string('-', nameWidth),
+ new string('-', versionWidth),
+ new string('-', defaultWidth),
+ new string('-', pathWidth)
+ );
+
+ foreach (var line in output)
+ {
+ _user.RaiseMessage("{0} {1} {2} {3}",
+ line.Name.PadRight(nameWidth),
+ line.Version.PadRight(versionWidth),
+ line.Default.PadRight(defaultWidth),
+ line.Path.PadRight(pathWidth)
+ );
+ }
+
+ return Exit.Ok;
+ }
+
+ private int RenameInstall(object args)
+ {
+ var opts = (KspOptions.RenameKsp)args;
+ if (opts.OldName == null || opts.NewName == null)
+ {
+ _user.RaiseMessage("rename - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
+
+ if (!_manager.HasInstance(opts.OldName))
+ {
+ _user.RaiseMessage("Couldn't find an install with the name \"{0}\", aborting...", opts.OldName);
+ return Exit.BadOpt;
+ }
+
+ _manager.RenameInstance(opts.OldName, opts.NewName);
+
+ _user.RaiseMessage("Successfully renamed \"{0}\" to \"{1}\".", opts.OldName, opts.NewName);
+ return Exit.Ok;
+ }
+ }
+
+ [Verb("ksp", HelpText = "Manage KSP installs")]
+ [ChildVerbs(typeof(AddKsp), typeof(CloneKsp), typeof(DefaultKsp), typeof(FakeKsp), typeof(ForgetKsp), typeof(ListKsp), typeof(RenameKsp))]
+ internal class KspOptions
+ {
+ [VerbExclude]
+ [Verb("add", HelpText = "Add a KSP install")]
+ internal class AddKsp : CommonOptions
+ {
+ [Value(0, MetaName = "Name", HelpText = "The name of the new KSP install")]
+ public string Name { get; set; }
+
+ [Value(1, MetaName = "Path", HelpText = "The path where KSP is installed")]
+ public string Path { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("clone", HelpText = "Clone an existing KSP install")]
+ internal class CloneKsp : CommonOptions
+ {
+ [Value(0, MetaName = "NameOrPath", HelpText = "The name or path to clone")]
+ public string NameOrPath { get; set; }
+
+ [Value(1, MetaName = "NewName", HelpText = "The name of the new KSP install")]
+ public string NewName { get; set; }
+
+ [Value(2, MetaName = "NewPath", HelpText = "The path to clone the KSP install to")]
+ public string NewPath { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("default", HelpText = "Set the default KSP install")]
+ internal class DefaultKsp : CommonOptions
+ {
+ [Value(0, MetaName = "Name", HelpText = "The name of the KSP install to set as the default")]
+ public string Name { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("fake", HelpText = "Fake a KSP install")]
+ internal class FakeKsp : CommonOptions
+ {
+ [Option("MakingHistory", Default = "none", HelpText = "The version of the Making History DLC to be faked")]
+ public string MakingHistory { get; set; }
+
+ [Option("BreakingGround", Default = "none", HelpText = "The version of the Breaking Ground DLC to be faked")]
+ public string BreakingGround { get; set; }
+
+ [Option("set-default", HelpText = "Set the new instance as the default one")]
+ public bool SetDefault { get; set; }
+
+ [Value(0, MetaName = "Name", HelpText = "The name of the faked KSP install")]
+ public string Name { get; set; }
+
+ [Value(1, MetaName = "Path", HelpText = "The path to fake the KSP install to")]
+ public string Path { get; set; }
+
+ [Value(2, MetaName = "KSP version", HelpText = "The KSP version of the faked install")]
+ public string Version { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("forget", HelpText = "Forget a KSP install")]
+ internal class ForgetKsp : CommonOptions
+ {
+ [Value(0, MetaName = "Name", HelpText = "The name of the KSP install to remove")]
+ public string Name { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("list", HelpText = "List KSP installs")]
+ internal class ListKsp : CommonOptions { }
+
+ [VerbExclude]
+ [Verb("rename", HelpText = "Rename a KSP install")]
+ internal class RenameKsp : CommonOptions
+ {
+ [Value(0, MetaName = "Old name", HelpText = "The name of the KSP install to rename")]
+ public string OldName { get; set; }
+
+ [Value(1, MetaName = "New name", HelpText = "The new name of the KSP install")]
+ public string NewName { get; set; }
}
- #endregion
}
}
diff --git a/Cmdline/Action/ICommand.cs b/Cmdline/Action/ICommand.cs
index 85ba382baa..01008f2509 100644
--- a/Cmdline/Action/ICommand.cs
+++ b/Cmdline/Action/ICommand.cs
@@ -1,7 +1,16 @@
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
- public interface ICommand
+ ///
+ /// Interface for regular commands.
+ ///
+ internal interface ICommand
{
- int RunCommand(CKAN.GameInstance ksp, object options);
+ ///
+ /// Run the command.
+ ///
+ /// The game instance which to handle with mods.
+ /// The command line arguments handled by the parser.
+ /// An code.
+ int RunCommand(CKAN.GameInstance inst, object args);
}
}
diff --git a/Cmdline/Action/ISubCommand.cs b/Cmdline/Action/ISubCommand.cs
index 409f49dbee..5e3c5eebb9 100644
--- a/Cmdline/Action/ISubCommand.cs
+++ b/Cmdline/Action/ISubCommand.cs
@@ -1,7 +1,24 @@
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Interface for commands that use nested commands.
+ ///
internal interface ISubCommand
{
- int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCommandOptions options);
+ ///
+ /// Run the command with nested commands.
+ ///
+ /// The manager to provide game instances.
+ /// The command line arguments handled by the parser.
+ /// An code.
+ int RunCommand(GameInstanceManager manager, object args);
+
+ ///
+ /// Displays an USAGE prefix line in the help screen.
+ ///
+ /// The USAGE prefix with the app name.
+ /// The command line arguments handled by the parser.
+ /// An USAGE: which contains information on how to use the command.
+ string GetUsage(string prefix, string[] args);
}
}
diff --git a/Cmdline/Action/Import.cs b/Cmdline/Action/Import.cs
index 044d9170a7..2ebf83fec0 100644
--- a/Cmdline/Action/Import.cs
+++ b/Cmdline/Action/Import.cs
@@ -1,112 +1,118 @@
using System;
-using System.IO;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using CommandLine;
using log4net;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
-
///
- /// Handler for "ckan import" command.
- /// Imports manually downloaded ZIP files into the cache.
+ /// Class for managing the importing of mods.
///
public class Import : ICommand
{
+ private static readonly ILog Log = LogManager.GetLogger(typeof(Import));
+
+ private readonly GameInstanceManager _manager;
+ private readonly IUser _user;
///
- /// Initialize the command
+ /// Initializes a new instance of the class.
///
- /// IUser object for user interaction
- public Import(GameInstanceManager mgr, IUser user)
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ public Import(GameInstanceManager manager, IUser user)
{
- manager = mgr;
- this.user = user;
+ _manager = manager;
+ _user = user;
}
///
- /// Execute an import command
+ /// Run the 'import' command.
///
- /// Game instance into which to import
- /// Command line parameters from the user
- ///
- /// Process exit code
- ///
- public int RunCommand(CKAN.GameInstance ksp, object options)
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
+ var opts = (ImportOptions)args;
+ if (!opts.Paths.Any())
+ {
+ _user.RaiseMessage("import [ ...] - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
+
+ var toImport = GetFiles(opts);
try
{
- ImportOptions opts = options as ImportOptions;
- HashSet toImport = GetFiles(opts);
- if (toImport.Count < 1)
- {
- user.RaiseMessage("Usage: ckan import path [path2, ...]");
- return Exit.ERROR;
- }
- else
+ Log.InfoFormat("Importing {0} files...", toImport.Count);
+ var toInstall = new List();
+ var regMgr = RegistryManager.Instance(inst);
+ var installer = new ModuleInstaller(inst, _manager.Cache, _user);
+
+ installer.ImportFiles(toImport, _user, mod => toInstall.Add(mod.identifier), regMgr.registry, !opts.Headless);
+
+ HashSet possibleConfigOnlyDirs = null;
+ if (toInstall.Count > 0)
{
- log.InfoFormat("Importing {0} files", toImport.Count);
- List toInstall = new List();
- RegistryManager regMgr = RegistryManager.Instance(ksp);
- ModuleInstaller inst = new ModuleInstaller(ksp, manager.Cache, user);
- inst.ImportFiles(toImport, user, mod => toInstall.Add(mod.identifier), regMgr.registry, !opts.Headless);
- HashSet possibleConfigOnlyDirs = null;
- if (toInstall.Count > 0)
- {
- inst.InstallList(
- toInstall,
- new RelationshipResolverOptions(),
- regMgr,
- ref possibleConfigOnlyDirs
- );
- }
- return Exit.OK;
+ installer.InstallList(
+ toInstall,
+ new RelationshipResolverOptions(),
+ regMgr,
+ ref possibleConfigOnlyDirs
+ );
}
}
catch (Exception ex)
{
- user.RaiseError("Import error: {0}", ex.Message);
- return Exit.ERROR;
+ _user.RaiseError("Import error: {0}", ex.Message);
+ return Exit.Error;
}
+
+ _user.RaiseMessage("Successfully imported {0} files.", toImport.Count);
+ return Exit.Ok;
}
private HashSet GetFiles(ImportOptions options)
{
- HashSet files = new HashSet();
- foreach (string filename in options.paths)
+ var files = new HashSet();
+ foreach (var fileName in options.Paths)
{
- if (Directory.Exists(filename))
+ if (Directory.Exists(fileName))
{
// Import everything in this folder
- log.InfoFormat("{0} is a directory", filename);
- foreach (string dirfile in Directory.EnumerateFiles(filename))
+ Log.InfoFormat("{0} is a directory. Adding contents...", fileName);
+ foreach (var dirFile in Directory.EnumerateFiles(fileName))
{
- AddFile(files, dirfile);
+ AddFile(files, dirFile);
}
}
else
{
- AddFile(files, filename);
+ AddFile(files, fileName);
}
}
+
return files;
}
- private void AddFile(HashSet files, string filename)
+ private void AddFile(HashSet files, string fileName)
{
- if (File.Exists(filename))
+ if (File.Exists(fileName))
{
- log.InfoFormat("Attempting import of {0}", filename);
- files.Add(new FileInfo(filename));
+ Log.InfoFormat("Attempting import of \"{0}\".", fileName);
+ files.Add(new FileInfo(fileName));
}
else
{
- user.RaiseMessage("File not found: {0}", filename);
+ _user.RaiseMessage("File not found: \"{0}\".", fileName);
}
}
-
- private readonly GameInstanceManager manager;
- private readonly IUser user;
- private static readonly ILog log = LogManager.GetLogger(typeof(Import));
}
+ [Verb("import", HelpText = "Import manually downloaded mods")]
+ internal class ImportOptions : InstanceSpecificOptions
+ {
+ [Value(0, MetaName = "File path(s)", HelpText = "The path(s) of the files to import (can also be a directory)")]
+ public IEnumerable Paths { get; set; }
+ }
}
diff --git a/Cmdline/Action/Install.cs b/Cmdline/Action/Install.cs
index 4603cd8d4f..fe95abbb8e 100644
--- a/Cmdline/Action/Install.cs
+++ b/Cmdline/Action/Install.cs
@@ -1,307 +1,323 @@
using System;
-using System.IO;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
+using CommandLine;
using log4net;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for managing the installations of mods.
+ ///
public class Install : ICommand
{
- private static readonly ILog log = LogManager.GetLogger(typeof(Install));
+ private static readonly ILog Log = LogManager.GetLogger(typeof(Install));
- public IUser user { get; set; }
- private GameInstanceManager manager;
+ private readonly GameInstanceManager _manager;
+ private readonly IUser _user;
///
- /// Initialize the install command object
+ /// Initializes a new instance of the class.
///
- /// GameInstanceManager containing our instances
- /// IUser object for interaction
- public Install(GameInstanceManager mgr, IUser user)
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ public Install(GameInstanceManager manager, IUser user)
{
- manager = mgr;
- this.user = user;
+ _manager = manager;
+ _user = user;
}
///
- /// Installs a module, if available
+ /// Run the 'install' command.
///
- /// Game instance into which to install
- /// Command line options object
- ///
- /// Exit code for shell environment
- ///
- public int RunCommand(CKAN.GameInstance ksp, object raw_options)
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- InstallOptions options = (InstallOptions) raw_options;
+ var opts = (InstallOptions)args;
+ if (!opts.Mods.Any())
+ {
+ _user.RaiseMessage("install [ ...] - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
- if (options.ckan_files != null)
+ if (opts.CkanFiles.Any())
{
- // Oooh! We're installing from a CKAN file.
- foreach (string ckan_file in options.ckan_files)
+ // Ooh! We're installing from a CKAN file
+ foreach (var ckanFile in opts.CkanFiles)
{
- Uri ckan_uri;
+ Uri ckanUri;
- // Check if the argument if a wellformatted Uri.
- if (!Uri.IsWellFormedUriString(ckan_file, UriKind.Absolute))
+ // Check if the argument is a well formatted Uri
+ if (!Uri.IsWellFormedUriString(ckanFile, UriKind.Absolute))
{
- // Assume it is a local file, check if the file exists.
- if (File.Exists(ckan_file))
+ // Assume it's a local file, check if the file exists
+ if (File.Exists(ckanFile))
{
- // Get the full path of the file.
- ckan_uri = new Uri(Path.GetFullPath(ckan_file));
+ // Get the full path of the file
+ ckanUri = new Uri(Path.GetFullPath(ckanFile));
}
else
{
- // We have no further ideas as what we can do with this Uri, tell the user.
- user.RaiseError("Can not find file \"{0}\".", ckan_file);
- user.RaiseError("Exiting.");
- return Exit.ERROR;
+ // We have no further ideas as what we can do with this Uri, tell the user
+ _user.RaiseError("Can't find file \"{0}\".\r\nExiting.", ckanFile);
+ return Exit.Error;
}
}
else
{
- ckan_uri = new Uri(ckan_file);
+ ckanUri = new Uri(ckanFile);
}
- string filename = String.Empty;
+ string fileName;
- // If it is a local file, we already know the filename. If it is remote, create a temporary file and download the remote resource.
- if (ckan_uri.IsFile)
+ // If it's a local file, we already know the filename. If it's remote, create a temporary file and download the remote resource
+ if (ckanUri.IsFile)
{
- filename = ckan_uri.LocalPath;
- log.InfoFormat("Installing from local CKAN file \"{0}\"", filename);
+ fileName = ckanUri.LocalPath;
+ Log.InfoFormat("Installing from local CKAN file \"{0}\".", fileName);
}
else
{
- log.InfoFormat("Installing from remote CKAN file \"{0}\"", ckan_uri);
- filename = Net.Download(ckan_uri, null, user);
+ Log.InfoFormat("Installing from remote CKAN file \"{0}\".", ckanUri);
+ fileName = Net.Download(ckanUri, null, _user);
- log.DebugFormat("Temporary file for \"{0}\" is at \"{1}\".", ckan_uri, filename);
+ Log.DebugFormat("Temporary file for \"{0}\" is at \"{1}\".", ckanUri, fileName);
}
- // Parse the JSON file.
+ // Parse the JSON file
try
{
- CkanModule m = MainClass.LoadCkanFromFile(ksp, filename);
- options.modules.Add($"{m.identifier}={m.version}");
+ var m = MainClass.LoadCkanFromFile(inst, fileName);
+ opts.Mods.ToList().Add(string.Format("{0}={1}", m.identifier, m.version));
}
catch (Kraken kraken)
{
- user.RaiseError(kraken.InnerException == null
+ _user.RaiseError(kraken.InnerException == null
? kraken.Message
- : $"{kraken.Message}: {kraken.InnerException.Message}");
+ : string.Format("{0}: {1}", kraken.Message, kraken.InnerException.Message));
}
}
// At times RunCommand() calls itself recursively - in this case we do
// not want to be doing this again, so "consume" the option
- options.ckan_files = null;
+ opts.CkanFiles = null;
}
else
{
- Search.AdjustModulesCase(ksp, options.modules);
- }
-
- if (options.modules.Count == 0)
- {
- // What? No files specified?
- user.RaiseMessage(
- "Usage: ckan install [--with-suggests] [--with-all-suggests] [--no-recommends] [--headless] Mod [Mod2, ...]");
- return Exit.BADOPT;
+ Search.AdjustModulesCase(inst, opts.Mods.ToList());
}
// Prepare options. Can these all be done in the new() somehow?
- var install_ops = new RelationshipResolverOptions
+ var installOpts = new RelationshipResolverOptions
{
- with_all_suggests = options.with_all_suggests,
- with_suggests = options.with_suggests,
- with_recommends = !options.no_recommends,
- allow_incompatible = options.allow_incompatible
+ with_all_suggests = opts.WithAllSuggests,
+ with_suggests = opts.WithSuggests,
+ with_recommends = !opts.NoRecommends,
+ allow_incompatible = opts.AllowIncompatible
};
- if (user.Headless)
+ if (_user.Headless)
{
- install_ops.without_toomanyprovides_kraken = true;
- install_ops.without_enforce_consistency = true;
+ installOpts.without_toomanyprovides_kraken = true;
+ installOpts.without_enforce_consistency = true;
}
- RegistryManager regMgr = RegistryManager.Instance(ksp);
- List modules = options.modules;
+ var regMgr = RegistryManager.Instance(inst);
+ var modules = opts.Mods.ToList();
- for (bool done = false; !done; )
+ for (var done = false; !done;)
{
- // Install everything requested. :)
+ // Install everything requested
try
{
HashSet possibleConfigOnlyDirs = null;
- var installer = new ModuleInstaller(ksp, manager.Cache, user);
- installer.InstallList(modules, install_ops, regMgr, ref possibleConfigOnlyDirs);
- user.RaiseMessage("");
+ var installer = new ModuleInstaller(inst, _manager.Cache, _user);
+ installer.InstallList(modules, installOpts, regMgr, ref possibleConfigOnlyDirs);
+ _user.RaiseMessage("");
done = true;
}
- catch (DependencyNotSatisfiedKraken ex)
+ catch (DependencyNotSatisfiedKraken kraken)
{
- user.RaiseError(ex.Message);
- user.RaiseMessage("If you're lucky, you can do a `ckan update` and try again.");
- user.RaiseMessage("Try `ckan install --no-recommends` to skip installation of recommended modules.");
- user.RaiseMessage("Or `ckan install --allow-incompatible` to ignore module compatibility.");
- return Exit.ERROR;
+ _user.RaiseError(kraken.Message);
+ _user.RaiseMessage("If you're lucky, you can do a 'ckan update' and try again.");
+ _user.RaiseMessage("Try 'ckan install --no-recommends' to skip installation of recommended mods.");
+ _user.RaiseMessage("Or 'ckan install --allow-incompatible' to ignore mod compatibility.");
+ return Exit.Error;
}
- catch (ModuleNotFoundKraken ex)
+ catch (ModuleNotFoundKraken kraken)
{
- if (ex.version == null)
+ if (kraken.version == null)
{
- user.RaiseError("Module {0} required but it is not listed in the index, or not available for your version of KSP.",
- ex.module);
+ _user.RaiseError("The mod \"{0}\" is required but it is not listed in the index, or not available for your version of {1}.", kraken.module, inst.game.ShortName);
}
else
{
- user.RaiseError("Module {0} {1} required but it is not listed in the index, or not available for your version of KSP.",
- ex.module, ex.version);
+ _user.RaiseError("The mod \"{0}\" {1} is required but it is not listed in the index, or not available for your version of {2}.", kraken.module, kraken.version, inst.game.ShortName);
}
- user.RaiseMessage("If you're lucky, you can do a `ckan update` and try again.");
- user.RaiseMessage("Try `ckan install --no-recommends` to skip installation of recommended modules.");
- user.RaiseMessage("Or `ckan install --allow-incompatible` to ignore module compatibility.");
- return Exit.ERROR;
+
+ _user.RaiseMessage("If you're lucky, you can do a 'ckan update' and try again.");
+ _user.RaiseMessage("Try 'ckan install --no-recommends' to skip installation of recommended mods.");
+ _user.RaiseMessage("Or 'ckan install --allow-incompatible' to ignore mod compatibility.");
+ return Exit.Error;
}
- catch (BadMetadataKraken ex)
+ catch (BadMetadataKraken kraken)
{
- user.RaiseError("Bad metadata detected for module {0}: {1}",
- ex.module, ex.Message);
- return Exit.ERROR;
+ _user.RaiseError("Bad metadata detected for mod {0}.\r\n{1}", kraken.module, kraken.Message);
+ return Exit.Error;
}
- catch (TooManyModsProvideKraken ex)
+ catch (TooManyModsProvideKraken kraken)
{
- // Request the user selects one of the mods.
- string[] mods = new string[ex.modules.Count];
+ // Request the user to select one of the mods
+ var mods = new string[kraken.modules.Count];
- for (int i = 0; i < ex.modules.Count; i++)
+ for (var i = 0; i < kraken.modules.Count; i++)
{
- mods[i] = String.Format("{0} ({1})", ex.modules[i].identifier, ex.modules[i].name);
+ mods[i] = string.Format("{0} ({1})", kraken.modules[i].identifier, kraken.modules[i].name);
}
- string message = String.Format("Too many mods provide {0}. Please pick from the following:\r\n", ex.requested);
+ var message = string.Format("Too many mods provide \"{0}\". Please pick one from the following mods:\r\n", kraken.requested);
int result;
-
try
{
- result = user.RaiseSelectionDialog(message, mods);
+ result = _user.RaiseSelectionDialog(message, mods);
}
- catch (Kraken e)
+ catch (Kraken k)
{
- user.RaiseMessage(e.Message);
-
- return Exit.ERROR;
+ _user.RaiseMessage(k.Message);
+ return Exit.Error;
}
if (result < 0)
{
- user.RaiseMessage(String.Empty); // Looks tidier.
-
- return Exit.ERROR;
+ _user.RaiseMessage(string.Empty); // Looks tidier
+ return Exit.Error;
}
- // Add the module to the list.
- modules.Add($"{ex.modules[result].identifier}={ex.modules[result].version}");
- // DON'T return so we can loop around and try again
+ // Add the module to the list
+ modules.Add(string.Format("{0}={1}",
+ kraken.modules[result].identifier,
+ kraken.modules[result].version));
}
- catch (FileExistsKraken ex)
+ catch (FileExistsKraken kraken)
{
- if (ex.owningModule != null)
+ if (kraken.owningModule != null)
{
- user.RaiseError(
- "Oh no! We tried to overwrite a file owned by another mod!\r\n"+
- "Please try a `ckan update` and try again.\r\n\r\n"+
- "If this problem re-occurs, then it maybe a packaging bug.\r\n"+
+ _user.RaiseError(
+ "Oh no! We tried to overwrite a file owned by another mod!\r\n" +
+ "Please try a 'ckan update' and try again.\r\n\r\n" +
+ "If this problem re-occurs, then it may be a packaging bug.\r\n" +
"Please report it at:\r\n\r\n" +
- "https://github.com/KSP-CKAN/NetKAN/issues/new\r\n\r\n" +
- "Please including the following information in your report:\r\n\r\n" +
+ "https://github.com/KSP-CKAN/NetKAN/issues/new/choose\r\n\r\n" +
+ "Please include the following information in your report:\r\n\r\n" +
"File : {0}\r\n" +
"Installing Mod : {1}\r\n" +
"Owning Mod : {2}\r\n" +
"CKAN Version : {3}\r\n",
- ex.filename, ex.installingModule, ex.owningModule,
+ kraken.filename, kraken.installingModule, kraken.owningModule,
Meta.GetVersion(VersionFormat.Full)
);
}
else
{
- user.RaiseError(
- "Oh no!\r\n\r\n"+
- "It looks like you're trying to install a mod which is already installed,\r\n"+
- "or which conflicts with another mod which is already installed.\r\n\r\n"+
- "As a safety feature, CKAN will *never* overwrite or alter a file\r\n"+
- "that it did not install itself.\r\n\r\n"+
- "If you wish to install {0} via CKAN,\r\n"+
- "then please manually uninstall the mod which owns:\r\n\r\n"+
- "{1}\r\n\r\n"+"and try again.\r\n",
- ex.installingModule, ex.filename
+ _user.RaiseError(
+ "Oh no!\r\n\r\n" +
+ "It looks like you're trying to install a mod which is already installed,\r\n" +
+ "or which conflicts with another mod which is already installed.\r\n\r\n" +
+ "As a safety feature, CKAN will *never* overwrite or alter a file\r\n" +
+ "that it did not install itself.\r\n\r\n" +
+ "If you wish to install {0} via CKAN,\r\n" +
+ "then please manually uninstall the mod which owns:\r\n\r\n" +
+ "{1}\r\n\r\n" +
+ "and try again.\r\n",
+ kraken.installingModule, kraken.filename
);
}
- user.RaiseMessage("Your GameData has been returned to its original state.");
- return Exit.ERROR;
+ _user.RaiseMessage("Your GameData has been returned to its original state.");
+ return Exit.Error;
}
- catch (InconsistentKraken ex)
+ catch (InconsistentKraken kraken)
{
- // The prettiest Kraken formats itself for us.
- user.RaiseError(ex.InconsistenciesPretty);
- user.RaiseMessage("Install canceled. Your files have been returned to their initial state.");
- return Exit.ERROR;
+ // The prettiest Kraken formats itself for us
+ _user.RaiseError(kraken.InconsistenciesPretty);
+ _user.RaiseMessage("Install canceled. Your files have been returned to their initial state.");
+ return Exit.Error;
}
- catch (CancelledActionKraken k)
+ catch (CancelledActionKraken kraken)
{
- user.RaiseError("Installation aborted: {0}", k.Message);
- return Exit.ERROR;
+ _user.RaiseError("Installation aborted: {0}", kraken.Message);
+ return Exit.Error;
}
catch (MissingCertificateKraken kraken)
{
- // Another very pretty kraken.
- user.RaiseError(kraken.ToString());
- return Exit.ERROR;
+ // Another very pretty kraken
+ _user.RaiseError(kraken.ToString());
+ return Exit.Error;
}
catch (DownloadThrottledKraken kraken)
{
- user.RaiseError(kraken.ToString());
- user.RaiseMessage("Try the authtoken command. See {0} for details.",
- kraken.infoUrl);
- return Exit.ERROR;
+ _user.RaiseError(kraken.ToString());
+ _user.RaiseMessage("Try the authtoken command. See \"{0}\" for more details.", kraken.infoUrl);
+ return Exit.Error;
}
catch (DownloadErrorsKraken)
{
- user.RaiseError("One or more files failed to download, stopped.");
- return Exit.ERROR;
+ _user.RaiseError("One or more files failed to download, stopped.");
+ return Exit.Error;
}
catch (ModuleDownloadErrorsKraken kraken)
{
- user.RaiseError(kraken.ToString());
- return Exit.ERROR;
+ _user.RaiseError(kraken.ToString());
+ return Exit.Error;
}
catch (DirectoryNotFoundKraken kraken)
{
- user.RaiseError(kraken.Message);
- return Exit.ERROR;
+ _user.RaiseError("\r\n{0}", kraken.Message);
+ return Exit.Error;
}
catch (ModuleIsDLCKraken kraken)
{
- user.RaiseError("CKAN can't install expansion '{0}' for you.",
- kraken.module.name);
- var res = kraken?.module?.resources;
- var storePagesMsg = new Uri[] { res?.store, res?.steamstore }
+ _user.RaiseError("Can't install the expansion \"{0}\".", kraken.module.name);
+ var res = kraken.module?.resources;
+ var storePagesMsg = new[] { res?.store, res?.steamstore }
.Where(u => u != null)
.Aggregate("", (a, b) => $"{a}\r\n- {b}");
+
if (!string.IsNullOrEmpty(storePagesMsg))
{
- user.RaiseMessage($"To install this expansion, purchase it from one of its store pages:\r\n{storePagesMsg}");
+ _user.RaiseMessage("To install this expansion, purchase it from one of its store pages:\r\n {0}", storePagesMsg);
}
- return Exit.ERROR;
+
+ return Exit.Error;
}
}
- return Exit.OK;
+ _user.RaiseMessage("Successfully installed requested mods.");
+ return Exit.Ok;
}
}
+
+ [Verb("install", HelpText = "Install a mod")]
+ internal class InstallOptions : InstanceSpecificOptions
+ {
+ [Option('c', "ckanfiles", HelpText = "Local CKAN files to process")]
+ public IEnumerable CkanFiles { get; set; }
+
+ [Option("no-recommends", HelpText = "Do not install recommended mods")]
+ public bool NoRecommends { get; set; }
+
+ [Option("with-suggests", HelpText = "Install suggested mods")]
+ public bool WithSuggests { get; set; }
+
+ [Option("with-all-suggests", HelpText = "Install suggested mods all the way down")]
+ public bool WithAllSuggests { get; set; }
+
+ [Option("allow-incompatible", HelpText = "Install mods that are not compatible with the current game version")]
+ public bool AllowIncompatible { get; set; }
+
+ [Value(0, MetaName = "Mod name(s)", HelpText = "The mod name(s) to install")]
+ public IEnumerable Mods { get; set; }
+ }
}
diff --git a/Cmdline/Action/List.cs b/Cmdline/Action/List.cs
index 04b0220202..a92ceff2d5 100644
--- a/Cmdline/Action/List.cs
+++ b/Cmdline/Action/List.cs
@@ -3,63 +3,71 @@
using CKAN.Exporters;
using CKAN.Types;
using CKAN.Versioning;
+using CommandLine;
using log4net;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for listing the installed mods.
+ ///
public class List : ICommand
{
- private static readonly ILog log = LogManager.GetLogger(typeof(List));
+ private static readonly ILog Log = LogManager.GetLogger(typeof(List));
- public IUser user { get; set; }
+ private readonly IUser _user;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current to raise messages to the user.
public List(IUser user)
{
- this.user = user;
+ _user = user;
}
- public int RunCommand(CKAN.GameInstance ksp, object raw_options)
+ ///
+ /// Run the 'list' command.
+ ///
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- ListOptions options = (ListOptions) raw_options;
-
- IRegistryQuerier registry = RegistryManager.Instance(ksp).registry;
-
+ var opts = (ListOptions)args;
+ IRegistryQuerier registry = RegistryManager.Instance(inst).registry;
ExportFileType? exportFileType = null;
- if (!string.IsNullOrWhiteSpace(options.export))
+ if (!string.IsNullOrWhiteSpace(opts.Export))
{
- exportFileType = GetExportFileType(options.export);
-
+ exportFileType = GetExportFileType(opts.Export);
if (exportFileType == null)
{
- user.RaiseError("Unknown export format: {0}", options.export);
+ _user.RaiseError("Unknown export format: {0}", opts.Export);
}
}
- if (!(options.porcelain) && exportFileType == null)
+ if (!opts.Porcelain && exportFileType == null)
{
- user.RaiseMessage("\r\nKSP found at {0}\r\n", ksp.GameDir());
- user.RaiseMessage("KSP Version: {0}\r\n", ksp.Version());
-
- user.RaiseMessage("Installed Modules:\r\n");
+ _user.RaiseMessage("\r\nFound {0} at \"{1}\"", inst.game.ShortName, inst.GameDir());
+ _user.RaiseMessage("\r\n{0} version: \"{1}\"", inst.game.ShortName, inst.Version());
+ _user.RaiseMessage("\r\nInstalled Modules:\r\n");
}
if (exportFileType == null)
{
- var installed = new SortedDictionary(registry.Installed());
-
- foreach (KeyValuePair mod in installed)
+ var mods = new SortedDictionary(registry.Installed());
+ foreach (var mod in mods)
{
- ModuleVersion current_version = mod.Value;
- string modInfo = string.Format("{0} {1}", mod.Key, mod.Value);
- string bullet = "*";
+ var currentVersion = mod.Value;
+ var modInfo = string.Format("{0} {1}", mod.Key, mod.Value);
+ var bullet = "*";
- if (current_version is ProvidesModuleVersion)
+ if (currentVersion is ProvidesModuleVersion)
{
- // Skip virtuals for now.
+ // Skip virtuals for now
continue;
}
- else if (current_version is UnmanagedModuleVersion)
+
+ if (currentVersion is UnmanagedModuleVersion)
{
// Autodetected dll
bullet = "A";
@@ -68,23 +76,26 @@ public int RunCommand(CKAN.GameInstance ksp, object raw_options)
{
try
{
- // Check if upgrades are available, and show appropriately.
- log.DebugFormat("Check if upgrades are available for {0}", mod.Key);
- CkanModule latest = registry.LatestAvailable(mod.Key, ksp.VersionCriteria());
- CkanModule current = registry.GetInstalledVersion(mod.Key);
- InstalledModule inst = registry.InstalledModule(mod.Key);
+ // Check if upgrades are available, and show appropriately
+ Log.DebugFormat("Checking if upgrades are available for \"{0}\"...", mod.Key);
+ var latest = registry.LatestAvailable(mod.Key, inst.VersionCriteria());
+ var current = registry.GetInstalledVersion(mod.Key);
+ var installed = registry.InstalledModule(mod.Key);
if (latest == null)
{
// Not compatible!
- log.InfoFormat("Latest {0} is not compatible", mod.Key);
+ Log.InfoFormat("Latest \"{0}\" is not compatible.", mod.Key);
bullet = "X";
- if ( current == null ) log.DebugFormat( " {0} installed version not found in registry", mod.Key);
-
+ if (current == null)
+ {
+ Log.DebugFormat("No installed version of \"{0}\" found in the registry.", mod.Key);
+ }
+
// Check if mod is replaceable
if (current.replaced_by != null)
{
- ModuleReplacement replacement = registry.GetReplacement(mod.Key, ksp.VersionCriteria());
+ var replacement = registry.GetReplacement(mod.Key, inst.VersionCriteria());
if (replacement != null)
{
// Replaceable!
@@ -93,15 +104,16 @@ public int RunCommand(CKAN.GameInstance ksp, object raw_options)
}
}
}
- else if (latest.version.IsEqualTo(current_version))
+ else if (latest.version.IsEqualTo(currentVersion))
{
// Up to date
- log.InfoFormat("Latest {0} is {1}", mod.Key, latest.version);
- bullet = (inst?.AutoInstalled ?? false) ? "+" : "-";
+ Log.InfoFormat("Latest \"{0}\" is {1}", mod.Key, latest.version);
+ bullet = installed?.AutoInstalled ?? false ? "+" : "-";
+
// Check if mod is replaceable
if (current.replaced_by != null)
{
- ModuleReplacement replacement = registry.GetReplacement(latest.identifier, ksp.VersionCriteria());
+ var replacement = registry.GetReplacement(latest.identifier, inst.VersionCriteria());
if (replacement != null)
{
// Replaceable!
@@ -118,12 +130,12 @@ public int RunCommand(CKAN.GameInstance ksp, object raw_options)
}
catch (ModuleNotFoundKraken)
{
- log.InfoFormat("{0} is installed, but no longer in the registry", mod.Key);
+ Log.InfoFormat("\"{0}\" is installed, but no longer in the registry.", mod.Key);
bullet = "?";
}
}
- user.RaiseMessage("{0} {1}", bullet, modInfo);
+ _user.RaiseMessage("{0} {1}", bullet, modInfo);
}
}
else
@@ -133,13 +145,13 @@ public int RunCommand(CKAN.GameInstance ksp, object raw_options)
stream.Flush();
}
- if (!(options.porcelain) && exportFileType == null)
+ if (!opts.Porcelain && exportFileType == null)
{
- user.RaiseMessage("\r\nLegend: -: Up to date. +:Auto-installed. X: Incompatible. ^: Upgradable. >: Replaceable\r\n A: Autodetected. ?: Unknown. *: Broken. ");
// Broken mods are in a state that CKAN doesn't understand, and therefore can't handle automatically
+ _user.RaiseMessage("\r\nLegend: -: Up to date. +: Auto-installed. X: Incompatible. ^: Upgradable. >: Replaceable\r\n A: Autodetected. ?: Unknown. *: Broken. ");
}
- return Exit.OK;
+ return Exit.Ok;
}
private static ExportFileType? GetExportFileType(string export)
@@ -148,13 +160,29 @@ public int RunCommand(CKAN.GameInstance ksp, object raw_options)
switch (export)
{
- case "text": return ExportFileType.PlainText;
- case "markdown": return ExportFileType.Markdown;
- case "bbcode": return ExportFileType.BbCode;
- case "csv": return ExportFileType.Csv;
- case "tsv": return ExportFileType.Tsv;
- default: return null;
+ case "text":
+ return ExportFileType.PlainText;
+ case "markdown":
+ return ExportFileType.Markdown;
+ case "bbcode":
+ return ExportFileType.BbCode;
+ case "csv":
+ return ExportFileType.Csv;
+ case "tsv":
+ return ExportFileType.Tsv;
+ default:
+ return null;
}
}
}
+
+ [Verb("list", HelpText = "List installed mods")]
+ internal class ListOptions : InstanceSpecificOptions
+ {
+ [Option("porcelain", HelpText = "Dump a raw list of mods, good for shell scripting")]
+ public bool Porcelain { get; set; }
+
+ [Option("export", HelpText = "Export a list of mods in specified format to stdout")]
+ public string Export { get; set; }
+ }
}
diff --git a/Cmdline/Action/Mark.cs b/Cmdline/Action/Mark.cs
index 777f73aeb5..a0c42593da 100644
--- a/Cmdline/Action/Mark.cs
+++ b/Cmdline/Action/Mark.cs
@@ -1,166 +1,193 @@
using System.Collections.Generic;
+using System.Linq;
using CommandLine;
-using CommandLine.Text;
-using log4net;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
///
- /// Subcommand for setting flags on modules,
- /// currently the auto-installed flag
+ /// Class for setting flags on mods, currently the auto-installed flag.
///
public class Mark : ISubCommand
{
- ///
- /// Initialize the subcommand
- ///
- public Mark() { }
+ private GameInstanceManager _manager;
+ private IUser _user;
///
- /// Run the subcommand
+ /// Run the 'mark' command.
///
- /// Manager to provide game instances
- /// Command line parameters paritally handled by parser
- /// Command line parameters not yet handled by parser
- ///
- /// Exit code
- ///
- public int RunSubCommand(GameInstanceManager mgr, CommonOptions opts, SubCommandOptions unparsed)
+ ///
+ public int RunCommand(GameInstanceManager manager, object args)
{
- string[] args = unparsed.options.ToArray();
- int exitCode = Exit.OK;
- // Parse and process our sub-verbs
- Parser.Default.ParseArgumentsStrict(args, new MarkSubOptions(), (string option, object suboptions) =>
+ var s = args.ToString();
+ var opts = s.Replace(s.Substring(0, s.LastIndexOf('.') + 1), "").Split('+');
+
+ CommonOptions options = new CommonOptions();
+ _user = new ConsoleUser(options.Headless);
+ _manager = manager ?? new GameInstanceManager(_user);
+ var exitCode = options.Handle(_manager, _user);
+
+ if (exitCode != Exit.Ok)
+ return exitCode;
+
+ switch (opts[1])
{
- // ParseArgumentsStrict calls us unconditionally, even with bad arguments
- if (!string.IsNullOrEmpty(option) && suboptions != null)
- {
- CommonOptions options = (CommonOptions)suboptions;
- options.Merge(opts);
- user = new ConsoleUser(options.Headless);
- manager = mgr ?? new GameInstanceManager(user);
- exitCode = options.Handle(manager, user);
- if (exitCode != Exit.OK)
- return;
-
- switch (option)
- {
- case "auto":
- exitCode = MarkAuto((MarkAutoOptions)suboptions, true, option, "auto-installed");
- break;
-
- case "user":
- exitCode = MarkAuto((MarkAutoOptions)suboptions, false, option, "user-selected");
- break;
-
- default:
- user.RaiseMessage("Unknown command: mark {0}", option);
- exitCode = Exit.BADOPT;
- break;
- }
- }
- }, () => { exitCode = MainClass.AfterHelp(); });
+ case "MarkAuto":
+ exitCode = MarkAuto(args);
+ break;
+ case "MarkUser":
+ exitCode = MarkUser(args);
+ break;
+ default:
+ exitCode = Exit.BadOpt;
+ break;
+ }
+
return exitCode;
}
- private int MarkAuto(MarkAutoOptions opts, bool value, string verb, string descrip)
+ ///
+ public string GetUsage(string prefix, string[] args)
{
- if (opts.modules.Count < 1)
+ if (args.Length == 1)
+ return $"{prefix} {args[0]} [options]";
+
+ switch (args[1])
{
- user.RaiseMessage("Usage: ckan mark {0} Mod [Mod2 ...]", verb);
- return Exit.BADOPT;
+ case "auto":
+ case "user":
+ return $"{prefix} {args[0]} {args[1]} [options] [ ...]";
+ default:
+ return $"{prefix} {args[0]} [options]";
}
+ }
- int exitCode = opts.Handle(manager, user);
- if (exitCode != Exit.OK)
+ private int MarkAuto(object args)
+ {
+ var opts = (MarkOptions.MarkAuto)args;
+ if (!opts.Mods.Any())
{
- return exitCode;
+ _user.RaiseMessage("auto [ ...] - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
- var ksp = MainClass.GetGameInstance(manager);
- var regMgr = RegistryManager.Instance(ksp);
- bool needSave = false;
- Search.AdjustModulesCase(ksp, opts.modules);
- foreach (string id in opts.modules)
+ var exitCode = opts.Handle(_manager, _user);
+ if (exitCode != Exit.Ok)
+ return exitCode;
+
+ var inst = MainClass.GetGameInstance(_manager);
+ var regMgr = RegistryManager.Instance(inst);
+ var needSave = false;
+ Search.AdjustModulesCase(inst, opts.Mods.ToList());
+
+ foreach (var id in opts.Mods)
{
- InstalledModule im = regMgr.registry.InstalledModule(id);
- if (im == null)
+ var mod = regMgr.registry.InstalledModule(id);
+ if (mod == null)
{
- user.RaiseError("{0} is not installed.", id);
+ _user.RaiseError("\"{0}\" is not installed.", id);
}
- else if (im.AutoInstalled == value)
+ else if (mod.AutoInstalled)
{
- user.RaiseError("{0} is already marked as {1}.", id, descrip);
+ _user.RaiseError("\"{0}\" is already marked as auto-installed.", id);
}
else
{
- user.RaiseMessage("Marking {0} as {1}...", id, descrip);
+ _user.RaiseMessage("Marking \"{0}\" as auto-installed...", id);
try
{
- im.AutoInstalled = value;
+ mod.AutoInstalled = true;
needSave = true;
}
catch (ModuleIsDLCKraken kraken)
{
- user.RaiseMessage($"Can't mark expansion '{kraken.module.name}' as auto-installed.");
- return Exit.BADOPT;
+ _user.RaiseMessage("Can't mark expansion \"{0}\" as auto-installed.", kraken.module.name);
+ return Exit.BadOpt;
}
}
}
+
if (needSave)
{
regMgr.Save(false);
- user.RaiseMessage("Changes made!");
+ _user.RaiseMessage("Successfully applied changes.");
}
- return Exit.OK;
- }
- private GameInstanceManager manager { get; set; }
- private IUser user { get; set; }
-
- private static readonly ILog log = LogManager.GetLogger(typeof(Mark));
- }
-
- internal class MarkSubOptions : VerbCommandOptions
- {
- [VerbOption("auto", HelpText = "Mark modules as auto installed")]
- public MarkAutoOptions MarkAutoOptions { get; set; }
-
- [VerbOption("user", HelpText = "Mark modules as user selected (opposite of auto installed)")]
- public MarkAutoOptions MarkUserOptions { get; set; }
+ return Exit.Ok;
+ }
- [HelpVerbOption]
- public string GetUsage(string verb)
+ private int MarkUser(object args)
{
- HelpText ht = HelpText.AutoBuild(this, verb);
- // Add a usage prefix line
- ht.AddPreOptionsLine(" ");
- if (string.IsNullOrEmpty(verb))
+ var opts = (MarkOptions.MarkUser)args;
+ if (!opts.Mods.Any())
{
- ht.AddPreOptionsLine("ckan mark - Edit flags on modules");
- ht.AddPreOptionsLine($"Usage: ckan mark [options]");
+ _user.RaiseMessage("user [ ...] - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
- else
+
+ var exitCode = opts.Handle(_manager, _user);
+ if (exitCode != Exit.Ok)
+ return exitCode;
+
+ var inst = MainClass.GetGameInstance(_manager);
+ var regMgr = RegistryManager.Instance(inst);
+ var needSave = false;
+ Search.AdjustModulesCase(inst, opts.Mods.ToList());
+
+ foreach (var id in opts.Mods)
{
- ht.AddPreOptionsLine("ksp " + verb + " - " + GetDescription(verb));
- switch (verb)
+ var mod = regMgr.registry.InstalledModule(id);
+ if (mod == null)
{
- case "auto":
- ht.AddPreOptionsLine($"Usage: ckan mark {verb} [options] Mod [Mod2 ...]");
- break;
-
- case "user":
- ht.AddPreOptionsLine($"Usage: ckan mark {verb} [options] Mod [Mod2 ...]");
- break;
+ _user.RaiseError("\"{0}\" is not installed.", id);
}
+ else if (!mod.AutoInstalled)
+ {
+ _user.RaiseError("\"{0}\" is already marked as user-selected.", id);
+ }
+ else
+ {
+ _user.RaiseMessage("Marking \"{0}\" as user-selected...", id);
+ try
+ {
+ mod.AutoInstalled = false;
+ needSave = true;
+ }
+ catch (ModuleIsDLCKraken kraken)
+ {
+ _user.RaiseMessage("Can't mark expansion \"{0}\" as auto-installed.", kraken.module.name);
+ return Exit.BadOpt;
+ }
+ }
+ }
+
+ if (needSave)
+ {
+ regMgr.Save(false);
+ _user.RaiseMessage("Successfully applied changes.");
}
- return ht;
+
+ return Exit.Ok;
}
}
- internal class MarkAutoOptions : InstanceSpecificOptions
+ [Verb("mark", HelpText = "Edit flags on mods")]
+ [ChildVerbs(typeof(MarkAuto), typeof(MarkUser))]
+ internal class MarkOptions
{
- [ValueList(typeof(List))]
- public List modules { get; set; }
+ [VerbExclude]
+ [Verb("auto", HelpText = "Mark mods as auto installed")]
+ internal class MarkAuto : InstanceSpecificOptions
+ {
+ [Value(0, MetaName = "Mod name(s)", HelpText = "The mod name(s) to mark as auto-installed")]
+ public IEnumerable Mods { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("user", HelpText = "Mark mods as user selected (opposite of auto installed)")]
+ internal class MarkUser : InstanceSpecificOptions
+ {
+ [Value(0, MetaName = "Mod name(s)", HelpText = "The mod name(s) to mark as user-installed")]
+ public IEnumerable Mods { get; set; }
+ }
}
}
diff --git a/Cmdline/Action/Prompt.cs b/Cmdline/Action/Prompt.cs
index 520d0f847a..4fb1ca97a3 100644
--- a/Cmdline/Action/Prompt.cs
+++ b/Cmdline/Action/Prompt.cs
@@ -1,18 +1,24 @@
using System;
using CommandLine;
-using CommandLine.Text;
-using log4net;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
-
+ ///
+ /// Class for managing multiple commands.
+ ///
public class Prompt
{
- public Prompt() { }
+ private const string ExitCommand = "exit";
- public int RunCommand(GameInstanceManager manager, object raw_options)
+ ///
+ /// Run the 'prompt' command.
+ ///
+ /// The manager to provide game instances.
+ /// The command line arguments handled by the parser.
+ /// An code.
+ public int RunCommand(GameInstanceManager manager, object args, string[] argStrings)
{
- CommonOptions opts = raw_options as CommonOptions;
+ var opts = (PromptOptions)args;
// Print an intro if not in headless mode
if (!(opts?.Headless ?? false))
{
@@ -29,22 +35,27 @@ public int RunCommand(GameInstanceManager manager, object raw_options)
{
Console.Write(
manager.CurrentInstance != null
- ? $"CKAN {Meta.GetVersion()}: {manager.CurrentInstance.game.ShortName} {manager.CurrentInstance.Version()} ({manager.CurrentInstance.Name})> "
- : $"CKAN {Meta.GetVersion()}> "
+ ? string.Format("CKAN {0}: {1} {2} ({3})> ",
+ Meta.GetVersion(),
+ manager.CurrentInstance.game.ShortName,
+ manager.CurrentInstance.Version(),
+ manager.CurrentInstance.Name)
+ : string.Format("CKAN {0}> ", Meta.GetVersion())
);
}
+
// Get input
- string command = Console.ReadLine();
- if (command == null || command == exitCommand)
+ var command = Console.ReadLine();
+ if (command == null || command == ExitCommand)
{
done = true;
}
- else if (command != "")
+ else if (command != string.Empty)
{
// Parse input as if it was a normal command line,
- // but with a persistent GameInstanceManager object.
- int cmdExitCode = MainClass.Execute(manager, opts, command.Split(' '));
- if ((opts?.Headless ?? false) && cmdExitCode != Exit.OK)
+ // but with a persistent GameInstanceManager object
+ var cmdExitCode = MainClass.Execute(manager, command.Split(' '), argStrings);
+ if ((opts?.Headless ?? false) && cmdExitCode != Exit.Ok)
{
// Pass failure codes to calling process in headless mode
// (in interactive mode the user can see the error and try again)
@@ -52,10 +63,11 @@ public int RunCommand(GameInstanceManager manager, object raw_options)
}
}
}
- return Exit.OK;
- }
- private const string exitCommand = "exit";
+ return Exit.Ok;
+ }
}
+ [Verb("prompt", HelpText = "Run CKAN prompt for executing multiple commands in a row")]
+ internal class PromptOptions : CommonOptions { }
}
diff --git a/Cmdline/Action/Remove.cs b/Cmdline/Action/Remove.cs
index d25a186707..e80a41d862 100644
--- a/Cmdline/Action/Remove.cs
+++ b/Cmdline/Action/Remove.cs
@@ -1,119 +1,135 @@
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
+using CommandLine;
using log4net;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for managing the removal of mods.
+ ///
public class Remove : ICommand
{
- private static readonly ILog log = LogManager.GetLogger(typeof(Remove));
+ private static readonly ILog Log = LogManager.GetLogger(typeof(Remove));
- public IUser user { get; set; }
- private GameInstanceManager manager;
+ private readonly GameInstanceManager _manager;
+ private readonly IUser _user;
///
- /// Initialize the remove command object
+ /// Initializes a new instance of the class.
///
- /// GameInstanceManager containing our instances
- /// IUser object for interaction
- public Remove(GameInstanceManager mgr, IUser user)
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ public Remove(GameInstanceManager manager, IUser user)
{
- manager = mgr;
- this.user = user;
+ _manager = manager;
+ _user = user;
}
///
- /// Uninstalls a module, if it exists.
+ /// Run the 'remove' command.
///
- /// Game instance from which to remove
- /// Command line options object
- ///
- /// Exit code for shell environment
- ///
- public int RunCommand(CKAN.GameInstance ksp, object raw_options)
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- RemoveOptions options = (RemoveOptions) raw_options;
- RegistryManager regMgr = RegistryManager.Instance(ksp);
+ var opts = (RemoveOptions)args;
+ if (!opts.Mods.Any())
+ {
+ _user.RaiseMessage("remove [ ...] - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
+
+ var regMgr = RegistryManager.Instance(inst);
// Use one (or more!) regex to select the modules to remove
- if (options.regex)
+ if (opts.Regex)
{
- log.Debug("Attempting Regex");
+ Log.Debug("Attempting Regex...");
+
// Parse every "module" as a grumpy regex
- var justins = options.modules.Select(s => new Regex(s));
+ var justins = opts.Mods.Select(s => new Regex(s));
// Modules that have been selected by one regex
- List selectedModules = new List();
+ var selectedModules = new List();
// Get the list of installed modules
// Try every regex on every installed module:
// if it matches, select for removal
- foreach (string mod in regMgr.registry.InstalledModules.Select(mod => mod.identifier))
+ foreach (var mod in regMgr.registry.InstalledModules.Select(mod => mod.identifier))
{
if (justins.Any(re => re.IsMatch(mod)))
+ {
selectedModules.Add(mod);
+ }
}
// Replace the regular expressions with the selected modules
// and continue removal as usual
- options.modules = selectedModules;
+ opts.Mods = selectedModules;
}
- if (options.rmall)
+ if (opts.All)
{
- log.Debug("Removing all mods");
+ Log.Debug("Removing all mods...");
+
// Add the list of installed modules to the list that should be uninstalled
- options.modules.AddRange(
+ opts.Mods.ToList().AddRange(
regMgr.registry.InstalledModules
.Where(mod => !mod.Module.IsDLC)
.Select(mod => mod.identifier)
);
}
- if (options.modules != null && options.modules.Count > 0)
+ try
{
- try
- {
- HashSet possibleConfigOnlyDirs = null;
- var installer = new ModuleInstaller(ksp, manager.Cache, user);
- Search.AdjustModulesCase(ksp, options.modules);
- installer.UninstallList(options.modules, ref possibleConfigOnlyDirs, regMgr);
- user.RaiseMessage("");
- }
- catch (ModNotInstalledKraken kraken)
- {
- user.RaiseMessage("I can't do that, {0} isn't installed.", kraken.mod);
- user.RaiseMessage("Try `ckan list` for a list of installed mods.");
- return Exit.BADOPT;
- }
- catch (ModuleIsDLCKraken kraken)
- {
- user.RaiseMessage($"CKAN can't remove expansion '{kraken.module.name}' for you.");
- var res = kraken?.module?.resources;
- var storePagesMsg = new Uri[] { res?.store, res?.steamstore }
- .Where(u => u != null)
- .Aggregate("", (a, b) => $"{a}\r\n- {b}");
- if (!string.IsNullOrEmpty(storePagesMsg))
- {
- user.RaiseMessage($"To remove this expansion, follow the instructions for the store page from which you purchased it:\r\n{storePagesMsg}");
- }
- return Exit.BADOPT;
- }
- catch (CancelledActionKraken k)
+ HashSet possibleConfigOnlyDirs = null;
+ var installer = new ModuleInstaller(inst, _manager.Cache, _user);
+ Search.AdjustModulesCase(inst, opts.Mods.ToList());
+ installer.UninstallList(opts.Mods, ref possibleConfigOnlyDirs, regMgr);
+ _user.RaiseMessage("");
+ }
+ catch (ModNotInstalledKraken kraken)
+ {
+ _user.RaiseMessage("Can't remove \"{0}\" because it isn't installed.\r\nTry 'ckan list' for a list of installed mods.", kraken.mod);
+ return Exit.Error;
+ }
+ catch (ModuleIsDLCKraken kraken)
+ {
+ _user.RaiseMessage("Can't remove the expansion \"{0}\".", kraken.module.name);
+ var res = kraken?.module?.resources;
+ var storePagesMsg = new[] { res?.store, res?.steamstore }
+ .Where(u => u != null)
+ .Aggregate("", (a, b) => $"{a}\r\n- {b}");
+
+ if (!string.IsNullOrEmpty(storePagesMsg))
{
- user.RaiseMessage("Remove aborted: {0}", k.Message);
- return Exit.ERROR;
+ _user.RaiseMessage("To remove this expansion, follow the instructions for the store page from which you purchased it:\r\n {0}", storePagesMsg);
}
+
+ return Exit.Error;
}
- else
+ catch (CancelledActionKraken kraken)
{
- user.RaiseMessage("No mod selected, nothing to do");
- return Exit.BADOPT;
+ _user.RaiseMessage("Remove aborted: {0}", kraken.Message);
+ return Exit.Error;
}
- return Exit.OK;
+ _user.RaiseMessage("Successfully removed requested mods.");
+ return Exit.Ok;
}
}
+
+ [Verb("remove", HelpText = "Remove an installed mod")]
+ internal class RemoveOptions : InstanceSpecificOptions
+ {
+ [Option("re", HelpText = "Parse arguments as regular expressions")]
+ public bool Regex { get; set; }
+
+ [Option("all", HelpText = "Remove all installed mods")]
+ public bool All { get; set; }
+
+ [Value(0, MetaName = "Mod name(s)", HelpText = "The mod name(s) to remove")]
+ public IEnumerable Mods { get; set; }
+ }
}
diff --git a/Cmdline/Action/Repair.cs b/Cmdline/Action/Repair.cs
index fa8eeeb4c7..85f363e3d4 100644
--- a/Cmdline/Action/Repair.cs
+++ b/Cmdline/Action/Repair.cs
@@ -1,92 +1,76 @@
using CommandLine;
-using CommandLine.Text;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for managing repair tasks.
+ ///
public class Repair : ISubCommand
{
- public Repair() { }
+ private GameInstanceManager _manager;
+ private IUser _user;
- internal class RepairSubOptions : VerbCommandOptions
+ ///
+ /// Run the 'repair' command.
+ ///
+ ///
+ public int RunCommand(GameInstanceManager manager, object args)
{
- [VerbOption("registry", HelpText = "Try to repair the CKAN registry")]
- public InstanceSpecificOptions Registry { get; set; }
+ var s = args.ToString();
+ var opts = s.Replace(s.Substring(0, s.LastIndexOf('.') + 1), "").Split('+');
+
+ CommonOptions options = new CommonOptions();
+ _user = new ConsoleUser(options.Headless);
+ _manager = manager ?? new GameInstanceManager(_user);
+ var exitCode = options.Handle(_manager, _user);
- [HelpVerbOption]
- public string GetUsage(string verb)
+ if (exitCode != Exit.Ok)
+ return exitCode;
+
+ switch (opts[1])
{
- HelpText ht = HelpText.AutoBuild(this, verb);
- // Add a usage prefix line
- ht.AddPreOptionsLine(" ");
- if (string.IsNullOrEmpty(verb))
- {
- ht.AddPreOptionsLine("ckan repair - Attempt various automatic repairs");
- ht.AddPreOptionsLine($"Usage: ckan repair [options]");
- }
- else
- {
- ht.AddPreOptionsLine("repair " + verb + " - " + GetDescription(verb));
- switch (verb)
- {
- // Commands with only --flag type options
- case "registry":
- default:
- ht.AddPreOptionsLine($"Usage: ckan repair {verb} [options]");
- break;
- }
- }
- return ht;
+ case "RepairRegistry":
+ exitCode = Registry(MainClass.GetGameInstance(_manager));
+ break;
+ default:
+ exitCode = Exit.BadOpt;
+ break;
}
+
+ return exitCode;
}
- public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCommandOptions unparsed)
+ ///
+ public string GetUsage(string prefix, string[] args)
{
- int exitCode = Exit.OK;
- // Parse and process our sub-verbs
- Parser.Default.ParseArgumentsStrict(unparsed.options.ToArray(), new RepairSubOptions(), (string option, object suboptions) =>
- {
- // ParseArgumentsStrict calls us unconditionally, even with bad arguments
- if (!string.IsNullOrEmpty(option) && suboptions != null)
- {
- CommonOptions options = (CommonOptions)suboptions;
- options.Merge(opts);
- User = new ConsoleUser(options.Headless);
- if (manager == null)
- {
- manager = new GameInstanceManager(User);
- }
- exitCode = options.Handle(manager, User);
- if (exitCode != Exit.OK)
- return;
+ if (args.Length == 1)
+ return $"{prefix} {args[0]} [options]";
- switch (option)
- {
- case "registry":
- exitCode = Registry(MainClass.GetGameInstance(manager));
- break;
-
- default:
- User.RaiseMessage("Unknown command: repair {0}", option);
- exitCode = Exit.BADOPT;
- break;
- }
- }
- }, () => { exitCode = MainClass.AfterHelp(); });
- return exitCode;
+ switch (args[1])
+ {
+ case "registry":
+ return $"{prefix} {args[0]} {args[1]} [options]";
+ default:
+ return $"{prefix} {args[0]} [options]";
+ }
}
- private IUser User { get; set; }
-
- ///
- /// Try to repair our registry.
- ///
- private int Registry(CKAN.GameInstance ksp)
+ private int Registry(CKAN.GameInstance inst)
{
- RegistryManager manager = RegistryManager.Instance(ksp);
+ var manager = RegistryManager.Instance(inst);
manager.registry.Repair();
manager.Save();
- User.RaiseMessage("Registry repairs attempted. Hope it helped.");
- return Exit.OK;
+ _user.RaiseMessage("Attempted various registry repairs. Hope it helped.");
+ return Exit.Ok;
}
}
+
+ [Verb("repair", HelpText = "Attempt various automatic repairs")]
+ [ChildVerbs(typeof(RepairRegistry))]
+ internal class RepairOptions
+ {
+ [VerbExclude]
+ [Verb("registry", HelpText = "Try to repair the CKAN registry")]
+ internal class RepairRegistry : InstanceSpecificOptions { }
+ }
}
diff --git a/Cmdline/Action/Replace.cs b/Cmdline/Action/Replace.cs
index 8631e72a79..4ee273636f 100644
--- a/Cmdline/Action/Replace.cs
+++ b/Cmdline/Action/Replace.cs
@@ -1,177 +1,205 @@
using System.Collections.Generic;
using System.Linq;
-using System.Text.RegularExpressions;
-using log4net;
using CKAN.Versioning;
+using CommandLine;
+using log4net;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for managing the replacing of mods.
+ ///
public class Replace : ICommand
{
- private static readonly ILog log = LogManager.GetLogger(typeof(Replace));
+ private static readonly ILog Log = LogManager.GetLogger(typeof(Replace));
- public IUser User { get; set; }
+ private readonly GameInstanceManager _manager;
+ private readonly IUser _user;
- public Replace(CKAN.GameInstanceManager mgr, IUser user)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ public Replace(GameInstanceManager manager, IUser user)
{
- manager = mgr;
- User = user;
+ _manager = manager;
+ _user = user;
}
- private GameInstanceManager manager;
-
- public int RunCommand(CKAN.GameInstance ksp, object raw_options)
+ ///
+ /// Run the 'replace' command.
+ ///
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- ReplaceOptions options = (ReplaceOptions) raw_options;
-
- if (options.ckan_file != null)
+ var opts = (ReplaceOptions)args;
+ if (!opts.Mods.Any() && !opts.All)
{
- options.modules.Add(MainClass.LoadCkanFromFile(ksp, options.ckan_file).identifier);
+ _user.RaiseMessage("replace [ ...] - argument(s) missing, perhaps you forgot it?");
+ _user.RaiseMessage("If you want to replace all mods, use: ckan replace --all");
+ return Exit.BadOpt;
}
- if (options.modules.Count == 0 && ! options.replace_all)
+ if (opts.CkanFile != null)
{
- // What? No mods specified?
- User.RaiseMessage("Usage: ckan replace Mod [Mod2, ...]");
- User.RaiseMessage(" or ckan replace --all");
- return Exit.BADOPT;
+ opts.Mods.ToList().Add(MainClass.LoadCkanFromFile(inst, opts.CkanFile).identifier);
}
- // Prepare options. Can these all be done in the new() somehow?
- var replace_ops = new RelationshipResolverOptions
- {
- with_all_suggests = options.with_all_suggests,
- with_suggests = options.with_suggests,
- with_recommends = !options.no_recommends,
- allow_incompatible = options.allow_incompatible
- };
-
- var regMgr = RegistryManager.Instance(ksp);
+ var regMgr = RegistryManager.Instance(inst);
var registry = regMgr.registry;
- var to_replace = new List();
+ var toReplace = new List();
- if (options.replace_all)
+ if (opts.All)
{
- log.Debug("Running Replace all");
+ Log.Debug("Running replace all...");
var installed = new Dictionary(registry.Installed());
- foreach (KeyValuePair mod in installed)
+ foreach (var mod in installed)
{
- ModuleVersion current_version = mod.Value;
-
- if ((current_version is ProvidesModuleVersion) || (current_version is UnmanagedModuleVersion))
+ var currentVersion = mod.Value;
+ if (currentVersion is ProvidesModuleVersion || currentVersion is UnmanagedModuleVersion)
{
continue;
}
- else
+
+ try
{
- try
- {
- log.DebugFormat("Testing {0} {1} for possible replacement", mod.Key, mod.Value);
- // Check if replacement is available
+ Log.DebugFormat("Testing \"{0}\" {1} for possible replacement...", mod.Key, mod.Value);
- ModuleReplacement replacement = registry.GetReplacement(mod.Key, ksp.VersionCriteria());
- if (replacement != null)
- {
- // Replaceable
- log.InfoFormat("Replacement {0} {1} found for {2} {3}",
- replacement.ReplaceWith.identifier, replacement.ReplaceWith.version,
- replacement.ToReplace.identifier, replacement.ToReplace.version);
- to_replace.Add(replacement);
- }
- }
- catch (ModuleNotFoundKraken)
+ // Check if replacement is available
+ var replacement = registry.GetReplacement(mod.Key, inst.VersionCriteria());
+ if (replacement != null)
{
- log.InfoFormat("{0} is installed, but it or its replacement is not in the registry",
- mod.Key);
+ // Replaceable
+ Log.InfoFormat("Replacement \"{0}\" {1} found for \"{2}\" {3}.",
+ replacement.ReplaceWith.identifier, replacement.ReplaceWith.version,
+ replacement.ToReplace.identifier, replacement.ToReplace.version);
+ toReplace.Add(replacement);
}
}
+ catch (ModuleNotFoundKraken)
+ {
+ Log.InfoFormat("\"{0}\" is installed, but it, or its replacement, is not in the registry.", mod.Key);
+ }
}
}
else
{
- foreach (string mod in options.modules)
+ foreach (var mod in opts.Mods)
{
try
{
- log.DebugFormat("Checking that {0} is installed", mod);
- CkanModule modToReplace = registry.GetInstalledVersion(mod);
+ Log.DebugFormat("Checking that \"{0}\" is installed...", mod);
+ var modToReplace = registry.GetInstalledVersion(mod);
if (modToReplace != null)
{
- log.DebugFormat("Testing {0} {1} for possible replacement", modToReplace.identifier, modToReplace.version);
+ Log.DebugFormat("Testing \"{0}\" {1} for possible replacement...", modToReplace.identifier, modToReplace.version);
try
{
// Check if replacement is available
- ModuleReplacement replacement = registry.GetReplacement(modToReplace.identifier, ksp.VersionCriteria());
+ var replacement = registry.GetReplacement(modToReplace.identifier, inst.VersionCriteria());
if (replacement != null)
{
// Replaceable
- log.InfoFormat("Replacement {0} {1} found for {2} {3}",
+ Log.InfoFormat("Replacement \"{0}\" {1} found for \"{2}\" {3}.",
replacement.ReplaceWith.identifier, replacement.ReplaceWith.version,
replacement.ToReplace.identifier, replacement.ToReplace.version);
- to_replace.Add(replacement);
+ toReplace.Add(replacement);
}
+
if (modToReplace.replaced_by != null)
{
- log.InfoFormat("Attempt to replace {0} failed, replacement {1} is not compatible",
- mod, modToReplace.replaced_by.name);
+ Log.InfoFormat("The attempt to replace \"{0}\" failed, the replacement \"{1}\" is not compatible with your version of {2}.", mod, modToReplace.replaced_by.name, inst.game.ShortName);
}
else
{
- log.InfoFormat("Mod {0} has no replacement defined for the current version {1}",
- modToReplace.identifier, modToReplace.version);
+ Log.InfoFormat("The mod \"{0}\" has no replacement defined for the current version {1}.", modToReplace.identifier, modToReplace.version);
}
}
catch (ModuleNotFoundKraken)
{
- log.InfoFormat("{0} is installed, but its replacement {1} is not in the registry",
- mod, modToReplace.replaced_by.name);
+ Log.InfoFormat("\"{0}\" is installed, but its replacement \"{1}\" is not in the registry.", mod, modToReplace.replaced_by.name);
}
}
}
catch (ModuleNotFoundKraken kraken)
{
- User.RaiseMessage("Module {0} not found", kraken.module);
+ _user.RaiseMessage("The mod \"{0}\" could not found.", kraken.module);
}
}
}
- if (to_replace.Count() != 0)
+
+ if (toReplace.Count != 0)
{
- User.RaiseMessage("\r\nReplacing modules...\r\n");
- foreach (ModuleReplacement r in to_replace)
+ _user.RaiseMessage("\r\nReplacing modules...\r\n");
+ foreach (var r in toReplace)
{
- User.RaiseMessage("Replacement {0} {1} found for {2} {3}",
+ _user.RaiseMessage("Replacement \"{0}\" {1} found for \"{2}\" {3}.",
r.ReplaceWith.identifier, r.ReplaceWith.version,
r.ToReplace.identifier, r.ToReplace.version);
}
- bool ok = User.RaiseYesNoDialog("Continue?");
+ var ok = _user.RaiseYesNoDialog("Continue?");
if (!ok)
{
- User.RaiseMessage("Replacements canceled at user request.");
- return Exit.ERROR;
+ _user.RaiseMessage("Replacements canceled at user request.");
+ return Exit.Error;
}
- // TODO: These instances all need to go.
+ var replaceOpts = new RelationshipResolverOptions
+ {
+ with_all_suggests = opts.WithAllSuggests,
+ with_suggests = opts.WithSuggests,
+ with_recommends = !opts.NoRecommends,
+ allow_incompatible = opts.AllowIncompatible
+ };
+
+ // TODO: These instances all need to go
try
{
HashSet possibleConfigOnlyDirs = null;
- new ModuleInstaller(ksp, manager.Cache, User).Replace(to_replace, replace_ops, new NetAsyncModulesDownloader(User, manager.Cache), ref possibleConfigOnlyDirs, regMgr);
- User.RaiseMessage("");
+ new ModuleInstaller(inst, _manager.Cache, _user).Replace(toReplace, replaceOpts, new NetAsyncModulesDownloader(_user, _manager.Cache), ref possibleConfigOnlyDirs, regMgr);
+ _user.RaiseMessage("");
}
- catch (DependencyNotSatisfiedKraken ex)
+ catch (DependencyNotSatisfiedKraken kraken)
{
- User.RaiseMessage("Dependencies not satisfied for replacement, {0} requires {1} {2} but it is not listed in the index, or not available for your version of KSP.", ex.parent, ex.module, ex.version);
+ _user.RaiseMessage("Dependencies not satisfied for replacement, \"{0}\" requires \"{1}\" {2} but it is not listed in the index, or not available for your version of {3}.", kraken.parent, kraken.module, kraken.version, inst.game.ShortName);
}
}
else
{
- User.RaiseMessage("No replacements found.");
- return Exit.OK;
+ _user.RaiseMessage("No replacements found.");
+ return Exit.Ok;
}
- return Exit.OK;
+ return Exit.Ok;
}
}
+
+ [Verb("replace", HelpText = "Replace list of replaceable mods")]
+ internal class ReplaceOptions : InstanceSpecificOptions
+ {
+ [Option('c', "ckanfile", HelpText = "Local CKAN file to process")]
+ public string CkanFile { get; set; }
+
+ [Option("no-recommends", HelpText = "Do not install recommended mods")]
+ public bool NoRecommends { get; set; }
+
+ [Option("with-suggests", HelpText = "Install suggested mods")]
+ public bool WithSuggests { get; set; }
+
+ [Option("with-all-suggests", HelpText = "Install suggested mods all the way down")]
+ public bool WithAllSuggests { get; set; }
+
+ [Option("allow-incompatible", HelpText = "Install mods that are not compatible with the current game version")]
+ public bool AllowIncompatible { get; set; }
+
+ [Option("all", HelpText = "Replace all available replaced mods")]
+ public bool All { get; set; }
+
+ [Value(0, MetaName = "Mod name(s)", HelpText = "The mod name(s) to replace")]
+ public IEnumerable Mods { get; set; }
+ }
}
diff --git a/Cmdline/Action/Repo.cs b/Cmdline/Action/Repo.cs
index a3b4b2b545..308256422b 100644
--- a/Cmdline/Action/Repo.cs
+++ b/Cmdline/Action/Repo.cs
@@ -1,345 +1,283 @@
using System;
-using System.Collections.Generic;
using System.Linq;
-using System.Net;
-using Newtonsoft.Json;
using CommandLine;
-using CommandLine.Text;
using log4net;
+using Newtonsoft.Json;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
-
+ ///
+ /// Class for managing CKAN repositories.
+ ///
public class Repo : ISubCommand
{
- public Repo() { }
+ private static readonly ILog Log = LogManager.GetLogger(typeof(Repo));
- internal class RepoSubOptions : VerbCommandOptions
- {
- [VerbOption("available", HelpText = "List (canonical) available repositories")]
- public AvailableOptions AvailableOptions { get; set; }
+ private GameInstanceManager _manager;
+ private IUser _user;
- [VerbOption("list", HelpText = "List repositories")]
- public ListOptions ListOptions { get; set; }
-
- [VerbOption("add", HelpText = "Add a repository")]
- public AddOptions AddOptions { get; set; }
+ ///
+ /// Run the 'repo' command.
+ ///
+ ///
+ public int RunCommand(GameInstanceManager manager, object args)
+ {
+ var s = args.ToString();
+ var opts = s.Replace(s.Substring(0, s.LastIndexOf('.') + 1), "").Split('+');
- [VerbOption("forget", HelpText = "Forget a repository")]
- public ForgetOptions ForgetOptions { get; set; }
+ CommonOptions options = new CommonOptions();
+ _user = new ConsoleUser(options.Headless);
+ _manager = manager ?? new GameInstanceManager(_user);
+ var exitCode = options.Handle(_manager, _user);
- [VerbOption("default", HelpText = "Set the default repository")]
- public DefaultOptions DefaultOptions { get; set; }
+ if (exitCode != Exit.Ok)
+ return exitCode;
- [HelpVerbOption]
- public string GetUsage(string verb)
+ switch (opts[1])
{
- HelpText ht = HelpText.AutoBuild(this, verb);
- // Add a usage prefix line
- ht.AddPreOptionsLine(" ");
- if (string.IsNullOrEmpty(verb))
- {
- ht.AddPreOptionsLine("ckan repo - Manage CKAN repositories");
- ht.AddPreOptionsLine($"Usage: ckan repo [options]");
- }
- else
- {
- ht.AddPreOptionsLine("repo " + verb + " - " + GetDescription(verb));
- switch (verb)
- {
- // First the commands with two arguments
- case "add":
- ht.AddPreOptionsLine($"Usage: ckan repo {verb} [options] name url");
- break;
-
- // Then the commands with one argument
- case "remove":
- case "forget":
- case "default":
- ht.AddPreOptionsLine($"Usage: ckan repo {verb} [options] name");
- break;
-
- // Now the commands with only --flag type options
- case "available":
- case "list":
- default:
- ht.AddPreOptionsLine($"Usage: ckan repo {verb} [options]");
- break;
- }
- }
- return ht;
+ case "AddRepo":
+ exitCode = AddRepository(args);
+ break;
+ case "AvailableRepo":
+ exitCode = AvailableRepositories();
+ break;
+ case "DefaultRepo":
+ exitCode = DefaultRepository(args);
+ break;
+ case "ForgetRepo":
+ exitCode = ForgetRepository(args);
+ break;
+ case "ListRepo":
+ exitCode = ListRepositories();
+ break;
+ case "ResetRepo":
+ exitCode = ResetRepository();
+ break;
+ default:
+ exitCode = Exit.BadOpt;
+ break;
}
- }
-
- internal class AvailableOptions : CommonOptions { }
- internal class ListOptions : InstanceSpecificOptions { }
- internal class AddOptions : InstanceSpecificOptions
- {
- [ValueOption(0)] public string name { get; set; }
- [ValueOption(1)] public string uri { get; set; }
+ return exitCode;
}
- internal class DefaultOptions : InstanceSpecificOptions
+ ///
+ public string GetUsage(string prefix, string[] args)
{
- [ValueOption(0)] public string uri { get; set; }
- }
+ if (args.Length == 1)
+ return $"{prefix} {args[0]} [options]";
- internal class ForgetOptions : InstanceSpecificOptions
- {
- [ValueOption(0)] public string name { get; set; }
+ switch (args[1])
+ {
+ case "add":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "default":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "forget":
+ return $"{prefix} {args[0]} {args[1]} [options] ";
+ case "available":
+ case "list":
+ case "reset":
+ return $"{prefix} {args[0]} {args[1]} [options]";
+ default:
+ return $"{prefix} {args[0]} [options]";
+ }
}
- // This is required by ISubCommand
- public int RunSubCommand(GameInstanceManager manager, CommonOptions opts, SubCommandOptions unparsed)
+ private int AddRepository(object args)
{
- string[] args = unparsed.options.ToArray();
-
- #region Aliases
+ var opts = (RepoOptions.AddRepo)args;
+ if (opts.Name == null)
+ {
+ _user.RaiseMessage("add - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
- for (int i = 0; i < args.Length; i++)
+ if (opts.Url == null)
{
- switch (args[i])
+ RepositoryList repositoryList;
+ try
{
- case "remove":
- args[i] = "forget";
- break;
+ repositoryList = FetchMasterRepositoryList();
+ }
+ catch
+ {
+ _user.RaiseError("Couldn't fetch CKAN repositories master list from \"{0}\".", _manager.CurrentInstance.game.RepositoryListURL.ToString());
+ return Exit.Error;
}
- }
-
- #endregion
-
- int exitCode = Exit.OK;
- // Parse and process our sub-verbs
- Parser.Default.ParseArgumentsStrict(args, new RepoSubOptions(), (string option, object suboptions) =>
- {
- // ParseArgumentsStrict calls us unconditionally, even with bad arguments
- if (!string.IsNullOrEmpty(option) && suboptions != null)
+ foreach (var candidate in repositoryList.repositories)
{
- CommonOptions options = (CommonOptions)suboptions;
- options.Merge(opts);
- User = new ConsoleUser(options.Headless);
- Manager = manager ?? new GameInstanceManager(User);
- exitCode = options.Handle(Manager, User);
- if (exitCode != Exit.OK)
- return;
-
- switch (option)
+ if (string.Equals(candidate.name, opts.Name, StringComparison.OrdinalIgnoreCase))
{
- case "available":
- exitCode = AvailableRepositories();
- break;
-
- case "list":
- exitCode = ListRepositories();
- break;
-
- case "add":
- exitCode = AddRepository((AddOptions)suboptions);
- break;
-
- case "remove":
- case "forget":
- exitCode = ForgetRepository((ForgetOptions)suboptions);
- break;
-
- case "default":
- exitCode = DefaultRepository((DefaultOptions)suboptions);
- break;
-
- default:
- User.RaiseMessage("Unknown command: repo {0}", option);
- exitCode = Exit.BADOPT;
- break;
+ opts.Name = candidate.name;
+ opts.Url = candidate.uri.ToString();
}
}
- }, () => { exitCode = MainClass.AfterHelp(); });
- return exitCode;
- }
- private RepositoryList FetchMasterRepositoryList(Uri master_uri = null)
- {
- if (master_uri == null)
+ // Nothing found in the master list?
+ if (opts.Url == null)
+ {
+ _user.RaiseMessage("add - argument(s) missing, perhaps you forgot it?");
+ return Exit.BadOpt;
+ }
+ }
+
+ Log.DebugFormat("About to add the repository \"{0}\" - \"{1}\".", opts.Name, opts.Url);
+ var manager = RegistryManager.Instance(MainClass.GetGameInstance(_manager));
+ var repositories = manager.registry.Repositories;
+
+ if (repositories.ContainsKey(opts.Name))
{
- master_uri = MainClass.GetGameInstance(Manager).game.RepositoryListURL;
+ _user.RaiseMessage("A repository with the name \"{0}\" already exists, aborting...", opts.Name);
+ return Exit.BadOpt;
}
- string json = Net.DownloadText(master_uri);
- return JsonConvert.DeserializeObject(json);
+ repositories.Add(opts.Name, new Repository(opts.Name, opts.Url));
+
+ _user.RaiseMessage("Successfully added the repository \"{0}\" - \"{1}\".", opts.Name, opts.Url);
+ manager.Save();
+
+ return Exit.Ok;
}
private int AvailableRepositories()
{
- User.RaiseMessage("Listing all (canonical) available CKAN repositories:");
+ _user.RaiseMessage("Listing all (canonical) available CKAN repositories:");
RepositoryList repositories;
-
try
{
repositories = FetchMasterRepositoryList();
}
catch
{
- User.RaiseError("Couldn't fetch CKAN repositories master list from {0}", MainClass.GetGameInstance(Manager).game.RepositoryListURL.ToString());
- return Exit.ERROR;
+ _user.RaiseError("Couldn't fetch CKAN repositories master list from \"{0}\"", MainClass.GetGameInstance(_manager).game.RepositoryListURL.ToString());
+ return Exit.Error;
}
- int maxNameLen = 0;
- foreach (Repository repository in repositories.repositories)
+ var maxNameLen = 0;
+ foreach (var repository in repositories.repositories)
{
maxNameLen = Math.Max(maxNameLen, repository.name.Length);
}
- foreach (Repository repository in repositories.repositories)
+ foreach (var 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;
+ return Exit.Ok;
}
- private int ListRepositories()
+ private int DefaultRepository(object args)
{
- var manager = RegistryManager.Instance(MainClass.GetGameInstance(Manager));
- User.RaiseMessage("Listing all known repositories:");
- SortedDictionary repositories = manager.registry.Repositories;
-
- int maxNameLen = 0;
- foreach (Repository repository in repositories.Values)
+ var opts = (RepoOptions.DefaultRepo)args;
+ if (opts.Url == null)
{
- maxNameLen = Math.Max(maxNameLen, repository.name.Length);
+ _user.RaiseMessage("default - argument missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
- foreach (Repository repository in repositories.Values)
+ Log.DebugFormat("About to set \"{0}\" as the {1} repository.", opts.Url, Repository.default_ckan_repo_name);
+ var manager = RegistryManager.Instance(MainClass.GetGameInstance(_manager));
+ var repositories = manager.registry.Repositories;
+
+ if (repositories.ContainsKey(Repository.default_ckan_repo_name))
{
- User.RaiseMessage(" {0}: {1}: {2}", repository.name.PadRight(maxNameLen), repository.priority, repository.uri);
+ repositories.Remove(Repository.default_ckan_repo_name);
}
- return Exit.OK;
+ repositories.Add(Repository.default_ckan_repo_name, new Repository(Repository.default_ckan_repo_name, opts.Url));
+
+ _user.RaiseMessage("Set the {0} repository to \"{1}\".", Repository.default_ckan_repo_name, opts.Url);
+ manager.Save();
+
+ return Exit.Ok;
}
- private int AddRepository(AddOptions options)
+ private int ForgetRepository(object args)
{
- RegistryManager manager = RegistryManager.Instance(MainClass.GetGameInstance(Manager));
-
- if (options.name == null)
+ var opts = (RepoOptions.ForgetRepo)args;
+ if (opts.Name == null)
{
- User.RaiseMessage("add [ ] - argument missing, perhaps you forgot it?");
- return Exit.BADOPT;
+ _user.RaiseMessage("forget - argument missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
- if (options.uri == null)
- {
- RepositoryList repositoryList;
-
- try
- {
- repositoryList = FetchMasterRepositoryList();
- }
- catch
- {
- User.RaiseError("Couldn't fetch CKAN repositories master list from {0}", Manager.CurrentInstance.game.RepositoryListURL.ToString());
- return Exit.ERROR;
- }
-
- foreach (Repository candidate in repositoryList.repositories)
- {
- if (String.Equals(candidate.name, options.name, StringComparison.OrdinalIgnoreCase))
- {
- options.name = candidate.name;
- options.uri = candidate.uri.ToString();
- }
- }
+ Log.DebugFormat("About to forget the repository \"{0}\".", opts.Name);
+ var manager = RegistryManager.Instance(MainClass.GetGameInstance(_manager));
+ var repositories = manager.registry.Repositories;
- // Nothing found in the master list?
- if (options.uri == null)
+ var name = opts.Name;
+ if (!repositories.ContainsKey(opts.Name))
+ {
+ name = repositories.Keys.FirstOrDefault(repo => repo.Equals(opts.Name, StringComparison.OrdinalIgnoreCase));
+ if (name == null)
{
- User.RaiseMessage("Name {0} not found in master list, please provide name and uri.", options.name);
- return Exit.BADOPT;
+ _user.RaiseMessage("Couldn't find a repository with the name \"{0}\", aborting...", opts.Name);
+ return Exit.BadOpt;
}
- }
-
- log.DebugFormat("About to add repository '{0}' - '{1}'", options.name, options.uri);
- SortedDictionary repositories = manager.registry.Repositories;
- if (repositories.ContainsKey(options.name))
- {
- User.RaiseMessage("Repository with name \"{0}\" already exists, aborting..", options.name);
- return Exit.BADOPT;
+ _user.RaiseMessage("Removing insensitive match \"{0}\".", name);
}
- repositories.Add(options.name, new Repository(options.name, options.uri));
+ repositories.Remove(name);
- User.RaiseMessage("Added repository '{0}' - '{1}'", options.name, options.uri);
+ _user.RaiseMessage("Successfully removed \"{0}\".", opts.Name);
manager.Save();
- return Exit.OK;
+ return Exit.Ok;
}
- private int ForgetRepository(ForgetOptions options)
+ private int ListRepositories()
{
- if (options.name == null)
+ _user.RaiseMessage("Listing all known repositories:");
+ var manager = RegistryManager.Instance(MainClass.GetGameInstance(_manager));
+ var repositories = manager.registry.Repositories;
+
+ var maxNameLen = 0;
+ foreach (var repository in repositories.Values)
{
- User.RaiseError("forget - argument missing, perhaps you forgot it?");
- return Exit.BADOPT;
+ maxNameLen = Math.Max(maxNameLen, repository.name.Length);
}
- RegistryManager manager = RegistryManager.Instance(MainClass.GetGameInstance(Manager));
- var registry = manager.registry;
- log.DebugFormat("About to forget repository '{0}'", options.name);
-
- var repos = registry.Repositories;
-
- string name = options.name;
- if (!repos.ContainsKey(options.name))
+ foreach (var repository in repositories.Values)
{
- name = repos.Keys.FirstOrDefault(repo => repo.Equals(options.name, StringComparison.OrdinalIgnoreCase));
- if (name == null)
- {
- User.RaiseMessage("Couldn't find repository with name \"{0}\", aborting..", options.name);
- return Exit.BADOPT;
- }
- User.RaiseMessage("Removing insensitive match \"{0}\"", name);
+ _user.RaiseMessage(" {0}: {1}: {2}", repository.name.PadRight(maxNameLen), repository.priority, repository.uri);
}
- registry.Repositories.Remove(name);
- User.RaiseMessage("Successfully removed \"{0}\"", options.name);
- manager.Save();
-
- return Exit.OK;
+ return Exit.Ok;
}
- private int DefaultRepository(DefaultOptions options)
+ private int ResetRepository()
{
- RegistryManager manager = RegistryManager.Instance(MainClass.GetGameInstance(Manager));
-
- if (options.uri == null)
- {
- User.RaiseMessage("default - argument missing, perhaps you forgot it?");
- return Exit.BADOPT;
- }
-
- log.DebugFormat("About to add repository '{0}' - '{1}'", Repository.default_ckan_repo_name, options.uri);
- SortedDictionary repositories = manager.registry.Repositories;
+ Log.DebugFormat("About to reset the {0} repository.", Repository.default_ckan_repo_name);
+ var manager = RegistryManager.Instance(MainClass.GetGameInstance(_manager));
+ var repositories = manager.registry.Repositories;
if (repositories.ContainsKey(Repository.default_ckan_repo_name))
{
repositories.Remove(Repository.default_ckan_repo_name);
}
- repositories.Add(Repository.default_ckan_repo_name, new Repository(
- Repository.default_ckan_repo_name, options.uri));
+ repositories.Add(Repository.default_ckan_repo_name, new Repository(Repository.default_ckan_repo_name, MainClass.GetGameInstance(_manager).game.DefaultRepositoryURL));
- User.RaiseMessage("Set {0} repository to '{1}'", Repository.default_ckan_repo_name, options.uri);
+ _user.RaiseMessage("Reset the {0} repository to \"{1}\".", Repository.default_ckan_repo_name, MainClass.GetGameInstance(_manager).game.DefaultRepositoryURL);
manager.Save();
- return Exit.OK;
+ return Exit.Ok;
}
- private GameInstanceManager Manager { get; set; }
- private IUser User { get; set; }
+ private RepositoryList FetchMasterRepositoryList(Uri masterUrl = null)
+ {
+ if (masterUrl == null)
+ {
+ masterUrl = MainClass.GetGameInstance(_manager).game.RepositoryListURL;
+ }
- private static readonly ILog log = LogManager.GetLogger(typeof (Repo));
+ var json = Net.DownloadText(masterUrl);
+ return JsonConvert.DeserializeObject(json);
+ }
}
public struct RepositoryList
@@ -347,4 +285,47 @@ public struct RepositoryList
public Repository[] repositories;
}
+ [Verb("repo", HelpText = "Manage CKAN repositories")]
+ [ChildVerbs(typeof(AddRepo), typeof(AvailableRepo), typeof(DefaultRepo), typeof(ForgetRepo), typeof(ListRepo), typeof(ResetRepo))]
+ internal class RepoOptions
+ {
+ [VerbExclude]
+ [Verb("add", HelpText = "Add a repository")]
+ internal class AddRepo : InstanceSpecificOptions
+ {
+ [Value(0, MetaName = "Name", HelpText = "The name of the new repository")]
+ public string Name { get; set; }
+
+ [Value(1, MetaName = "Url", HelpText = "The url of the new repository")]
+ public string Url { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("available", HelpText = "List (canonical) available repositories")]
+ internal class AvailableRepo : CommonOptions { }
+
+ [VerbExclude]
+ [Verb("default", HelpText = "Set the default repository")]
+ internal class DefaultRepo : InstanceSpecificOptions
+ {
+ [Value(0, MetaName = "Url", HelpText = "The url of the repository to make the default")]
+ public string Url { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("forget", HelpText = "Forget a repository")]
+ internal class ForgetRepo : InstanceSpecificOptions
+ {
+ [Value(0, MetaName = "Name", HelpText = "The name of the repository to remove")]
+ public string Name { get; set; }
+ }
+
+ [VerbExclude]
+ [Verb("list", HelpText = "List repositories")]
+ internal class ListRepo : InstanceSpecificOptions { }
+
+ [VerbExclude]
+ [Verb("reset", HelpText = "Set the default repository to the default")]
+ internal class ResetRepo : InstanceSpecificOptions { }
+ }
}
diff --git a/Cmdline/Action/Search.cs b/Cmdline/Action/Search.cs
index 4064480f01..db0a8ff161 100644
--- a/Cmdline/Action/Search.cs
+++ b/Cmdline/Action/Search.cs
@@ -1,203 +1,217 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text.RegularExpressions;
using CKAN.Games;
+using CKAN.Versioning;
+using CommandLine;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for searching mods.
+ ///
public class Search : ICommand
{
- public IUser user { get; set; }
+ private readonly IUser _user;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current to raise messages to the user.
public Search(IUser user)
{
- this.user = user;
+ _user = user;
}
- public int RunCommand(CKAN.GameInstance ksp, object raw_options)
+ ///
+ /// Run the 'search' command.
+ ///
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- SearchOptions options = (SearchOptions)raw_options;
-
- // Check the input.
- if (String.IsNullOrWhiteSpace(options.search_term) && String.IsNullOrWhiteSpace(options.author_term))
+ var opts = (SearchOptions)args;
+ if (string.IsNullOrWhiteSpace(opts.SearchTerm) && string.IsNullOrWhiteSpace(opts.Author))
{
- user.RaiseError("No search term?");
-
- return Exit.BADOPT;
+ _user.RaiseMessage("search - argument missing, perhaps you forgot it?");
+ _user.RaiseMessage("If you just want to search by author, use: ckan search --author ");
+ return Exit.BadOpt;
}
- List matching_compatible = PerformSearch(ksp, options.search_term, options.author_term, false);
- List matching_incompatible = new List();
- if (options.all)
+ var matchingCompatible = PerformSearch(inst, opts.SearchTerm, opts.Author);
+ var matchingIncompatible = new List();
+ if (opts.All)
{
- matching_incompatible = PerformSearch(ksp, options.search_term, options.author_term, true);
+ matchingIncompatible = PerformSearch(inst, opts.SearchTerm, opts.Author, true);
}
- // Show how many matches we have.
- if (options.all && !String.IsNullOrWhiteSpace(options.author_term))
- {
- user.RaiseMessage("Found {0} compatible and {1} incompatible mods matching \"{2}\" by \"{3}\".",
- matching_compatible.Count().ToString(),
- matching_incompatible.Count().ToString(),
- options.search_term,
- options.author_term);
- }
- else if (options.all && String.IsNullOrWhiteSpace(options.author_term))
+ string message;
+
+ // Return if no mods were found
+ if (!matchingCompatible.Any() && !matchingIncompatible.Any())
{
- user.RaiseMessage("Found {0} compatible and {1} incompatible mods matching \"{2}\".",
- matching_compatible.Count().ToString(),
- matching_incompatible.Count().ToString(),
- options.search_term);
+ message = "Couldn't find any mod";
+
+ if (!string.IsNullOrWhiteSpace(opts.SearchTerm))
+ {
+ message += string.Format(" matching \"{0}\"", opts.SearchTerm);
+ }
+
+ if (!string.IsNullOrWhiteSpace(opts.Author))
+ {
+ message += string.Format(" by \"{0}\"", opts.Author);
+ }
+
+ _user.RaiseMessage("{0}.", message);
+ return Exit.Ok;
}
- else if (!options.all && !String.IsNullOrWhiteSpace(options.author_term))
+
+ // Show how many matches we have
+ message = string.Format("Found {0} compatible", matchingCompatible.Count.ToString());
+
+ if (opts.All)
{
- user.RaiseMessage("Found {0} compatible mods matching \"{1}\" by \"{2}\".",
- matching_compatible.Count().ToString(),
- options.search_term,
- options.author_term);
+ message += string.Format(" and {0} incompatible", matchingIncompatible.Count.ToString());
}
- else if (!options.all && String.IsNullOrWhiteSpace(options.author_term))
+
+ message += " mods";
+
+ if (!string.IsNullOrWhiteSpace(opts.SearchTerm))
{
- user.RaiseMessage("Found {0} compatible mods matching \"{1}\".",
- matching_compatible.Count().ToString(),
- options.search_term);
+ message += string.Format(" matching \"{0}\"", opts.SearchTerm);
}
- // Present the results.
- if (!matching_compatible.Any() && (!options.all || !matching_incompatible.Any()))
+ if (!string.IsNullOrWhiteSpace(opts.Author))
{
- return Exit.OK;
+ message += string.Format(" by \"{0}\"", opts.Author);
}
- if (options.detail)
+ _user.RaiseMessage("{0}.", message);
+
+ // Show detailed information of matches
+ if (opts.Detail)
{
- user.RaiseMessage("Matching compatible mods:");
- foreach (CkanModule mod in matching_compatible)
+ if (matchingCompatible.Any())
{
- user.RaiseMessage("* {0} ({1}) - {2} by {3} - {4}",
- mod.identifier,
- mod.version,
- mod.name,
- mod.author == null ? "N/A" : String.Join(", ", mod.author),
- mod.@abstract);
+ _user.RaiseMessage("Matching compatible mods:");
+ foreach (var mod in matchingCompatible)
+ {
+ _user.RaiseMessage("* {0} ({1}) - {2} by {3} - {4}",
+ mod.identifier,
+ mod.version,
+ mod.name,
+ mod.author == null ? "N/A" : string.Join(", ", mod.author),
+ mod.@abstract);
+ }
}
- if (matching_incompatible.Any())
+ if (matchingIncompatible.Any())
{
- user.RaiseMessage("Matching incompatible mods:");
- foreach (CkanModule mod in matching_incompatible)
+ _user.RaiseMessage("Matching incompatible mods:");
+ foreach (var mod in matchingIncompatible)
{
- Registry.GetMinMaxVersions(new List { mod } , out _, out _, out var minKsp, out var maxKsp);
- string GameVersion = Versioning.GameVersionRange.VersionSpan(ksp.game, minKsp, maxKsp).ToString();
+ Registry.GetMinMaxVersions(new List { mod }, out _, out _, out GameVersion minVer, out GameVersion maxVer);
+ var gameVersion = GameVersionRange.VersionSpan(inst.game, minVer, maxVer);
- user.RaiseMessage("* {0} ({1} - {2}) - {3} by {4} - {5}",
+ _user.RaiseMessage("* {0} ({1} - {2}) - {3} by {4} - {5}",
mod.identifier,
mod.version,
- GameVersion,
+ gameVersion,
mod.name,
- mod.author == null ? "N/A" : String.Join(", ", mod.author),
+ mod.author == null ? "N/A" : string.Join(", ", mod.author),
mod.@abstract);
}
}
}
else
{
- List matching = matching_compatible.Concat(matching_incompatible).ToList();
+ var matching = matchingCompatible.Concat(matchingIncompatible).ToList();
matching.Sort((x, y) => string.Compare(x.identifier, y.identifier, StringComparison.Ordinal));
- foreach (CkanModule mod in matching)
+ foreach (var mod in matching)
{
- user.RaiseMessage(mod.identifier);
+ _user.RaiseMessage(mod.identifier);
}
}
- return Exit.OK;
+ return Exit.Ok;
}
///
- /// Searches for the term in the list of compatible or incompatible modules for the ksp instance.
- /// Looks in name, identifier and description fields, and if given, restricts to authors matching the author term.
+ /// Searches for the in the list of compatible or incompatible modules for the game instance.
+ /// Looks in name, identifier and description fields, and if given, restricts to authors matching the term.
///
- /// List of matching modules.
- /// The KSP instance to perform the search for.
- /// The search term. Case insensitive.
- public List PerformSearch(CKAN.GameInstance ksp, string term, string author = null, bool searchIncompatible = false)
+ /// The game instance which to handle with mods.
+ /// The string to search for in mods. This is case insensitive.
+ /// The author to search for. Default is .
+ /// Boolean to also search for incompatible mods or not. Default is .
+ /// A of matching modules as a .
+ public List PerformSearch(CKAN.GameInstance inst, string term, string author = null, bool searchIncompatible = false)
{
- // Remove spaces and special characters from the search term.
- term = String.IsNullOrWhiteSpace(term) ? string.Empty : CkanModule.nonAlphaNums.Replace(term, "");
- author = String.IsNullOrWhiteSpace(author) ? string.Empty : CkanModule.nonAlphaNums.Replace(author, "");
+ // Remove spaces and special characters from the search term
+ term = string.IsNullOrWhiteSpace(term) ? string.Empty : CkanModule.nonAlphaNums.Replace(term, "");
+ author = string.IsNullOrWhiteSpace(author) ? string.Empty : CkanModule.nonAlphaNums.Replace(author, "");
- var registry = RegistryManager.Instance(ksp).registry;
+ var registry = RegistryManager.Instance(inst).registry;
if (!searchIncompatible)
{
return registry
- .CompatibleModules(ksp.VersionCriteria())
- .Where((module) =>
- {
- // Look for a match in each string.
- return (module.SearchableName.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
- || module.SearchableIdentifier.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
- || module.SearchableAbstract.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
- || module.SearchableDescription.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1)
- && module.SearchableAuthors.Any((auth) => auth.IndexOf(author, StringComparison.OrdinalIgnoreCase) > -1);
- }).ToList();
+ .CompatibleModules(inst.VersionCriteria())
+ .Where(module =>
+ {
+ // Look for a match in each string
+ return (module.SearchableName.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
+ || module.SearchableIdentifier.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
+ || module.SearchableAbstract.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
+ || module.SearchableDescription.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1)
+ && module.SearchableAuthors.Any(auth => auth.IndexOf(author, StringComparison.OrdinalIgnoreCase) > -1);
+ }).ToList();
}
- else
- {
- return registry
- .IncompatibleModules(ksp.VersionCriteria())
- .Where((module) =>
+
+ return registry
+ .IncompatibleModules(inst.VersionCriteria())
+ .Where(module =>
{
- // Look for a match in each string.
+ // Look for a match in each string
return (module.SearchableName.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
- || module.SearchableIdentifier.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
- || module.SearchableAbstract.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
- || module.SearchableDescription.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1)
- && module.SearchableAuthors.Any((auth) => auth.IndexOf(author, StringComparison.OrdinalIgnoreCase) > -1);
+ || module.SearchableIdentifier.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
+ || module.SearchableAbstract.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
+ || module.SearchableDescription.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1)
+ && module.SearchableAuthors.Any(auth => auth.IndexOf(author, StringComparison.OrdinalIgnoreCase) > -1);
}).ToList();
- }
}
- ///
- /// Find the proper capitalization of an identifier
- ///
- /// List of valid mods from the registry
- /// Identifier to be treated as case insensitive
- ///
- /// The mod's properly capitalized identifier, or the original string if it doesn't exist
- ///
private static string CaseInsensitiveExactMatch(List mods, string module)
{
// Look for a matching mod with a case insensitive search
- CkanModule found = mods.FirstOrDefault(
- (CkanModule m) => string.Equals(m.identifier, module, StringComparison.OrdinalIgnoreCase)
- );
+ var found = mods.FirstOrDefault(m => string.Equals(m.identifier, module, StringComparison.OrdinalIgnoreCase));
+
// If we don't find anything, use the original string so the main code can raise errors
return found?.identifier ?? module;
}
///
- /// Convert case insensitive mod names from the user to case sensitive identifiers
+ /// Convert case insensitive mod names from the user to case sensitive identifiers.
///
- /// Game instance forgetting the mods
- /// List of strings to convert, format 'identifier' or 'identifier=version'
- public static void AdjustModulesCase(CKAN.GameInstance ksp, List modules)
+ /// The game instance which to handle with mods.
+ /// The of strings to convert.
+ public static void AdjustModulesCase(CKAN.GameInstance inst, List modules)
{
- IRegistryQuerier registry = RegistryManager.Instance(ksp).registry;
+ IRegistryQuerier registry = RegistryManager.Instance(inst).registry;
+
// Get the list of all compatible and incompatible mods
- List mods = registry.CompatibleModules(ksp.VersionCriteria()).ToList();
- mods.AddRange(registry.IncompatibleModules(ksp.VersionCriteria()));
- for (int i = 0; i < modules.Count; ++i)
+ var mods = registry.CompatibleModules(inst.VersionCriteria()).ToList();
+ mods.AddRange(registry.IncompatibleModules(inst.VersionCriteria()));
+
+ for (var i = 0; i < modules.Count; ++i)
{
- Match match = CkanModule.idAndVersionMatcher.Match(modules[i]);
+ var match = CkanModule.idAndVersionMatcher.Match(modules[i]);
if (match.Success)
{
- // Handle name=version format
- string ident = match.Groups["mod"].Value;
- string version = match.Groups["version"].Value;
- modules[i] = $"{CaseInsensitiveExactMatch(mods, ident)}={version}";
+ // Handle 'name=version' format
+ var ident = match.Groups["mod"].Value;
+ var version = match.Groups["version"].Value;
+ modules[i] = string.Format("{0}={1}", CaseInsensitiveExactMatch(mods, ident), version);
}
else
{
@@ -205,6 +219,21 @@ public static void AdjustModulesCase(CKAN.GameInstance ksp, List modules
}
}
}
+ }
+
+ [Verb("search", HelpText = "Search for mods")]
+ 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 author")]
+ public string Author { get; set; }
+ [Value(0, MetaName = "Search term", HelpText = "The term to search for")]
+ public string SearchTerm { get; set; }
}
}
diff --git a/Cmdline/Action/Show.cs b/Cmdline/Action/Show.cs
index e7c55d6ec9..d92f972497 100644
--- a/Cmdline/Action/Show.cs
+++ b/Cmdline/Action/Show.cs
@@ -2,90 +2,98 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using CommandLine;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for showing information about a mod.
+ ///
public class Show : ICommand
{
- public IUser user { get; set; }
+ private readonly IUser _user;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current to raise messages to the user.
public Show(IUser user)
{
- this.user = user;
+ _user = user;
}
- public int RunCommand(CKAN.GameInstance ksp, object raw_options)
+ ///
+ /// Run the 'show' command.
+ ///
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- ShowOptions options = (ShowOptions) raw_options;
-
- if (options.Modname == null)
+ var opts = (ShowOptions)args;
+ if (string.IsNullOrWhiteSpace(opts.ModName))
{
- // empty argument
- user.RaiseMessage("show - module name argument missing, perhaps you forgot it?");
- return Exit.BADOPT;
+ _user.RaiseMessage("show - argument missing, perhaps you forgot it?");
+ return Exit.BadOpt;
}
- // Check installed modules for an exact match.
- var registry = RegistryManager.Instance(ksp).registry;
- var installedModuleToShow = registry.InstalledModule(options.Modname);
+ // Check installed modules for an exact match
+ var registry = RegistryManager.Instance(inst).registry;
+ var installedModuleToShow = registry.InstalledModule(opts.ModName);
if (installedModuleToShow != null)
{
- // Show the installed module.
+ // Show the installed module
return ShowMod(installedModuleToShow);
}
// Module was not installed, look for an exact match in the available modules,
// either by "name" (the user-friendly display name) or by identifier
- CkanModule moduleToShow = registry
- .CompatibleModules(ksp.VersionCriteria())
- .SingleOrDefault(
- mod => mod.name == options.Modname
- || mod.identifier == options.Modname
- );
+ var moduleToShow = registry
+ .CompatibleModules(inst.VersionCriteria())
+ .SingleOrDefault(
+ mod => mod.name == opts.ModName
+ || mod.identifier == opts.ModName
+ );
if (moduleToShow == null)
{
- // No exact match found. Try to look for a close match for this KSP version.
- user.RaiseMessage("{0} not found or installed.", options.Modname);
- user.RaiseMessage("Looking for close matches in mods compatible with KSP {0}.", ksp.Version());
+ // No exact match found. Try to look for a close match for this game version
+ _user.RaiseMessage("\"{0}\" was not found or installed.\r\nLooking for close matches in mods compatible with {1} {2}.", opts.ModName, inst.game.ShortName, inst.Version());
- Search search = new Search(user);
- var matches = search.PerformSearch(ksp, options.Modname);
+ var search = new Search(_user);
+ var matches = search.PerformSearch(inst, opts.ModName);
- // Display the results of the search.
+ // Display the results of the search
if (!matches.Any())
{
- // No matches found.
- user.RaiseMessage("No close matches found.");
- return Exit.BADOPT;
+ // No matches found
+ _user.RaiseMessage("No close matches found.");
+ return Exit.BadOpt;
}
- else if (matches.Count() == 1)
- {
- // If there is only 1 match, display it.
- user.RaiseMessage("Found 1 close match: {0}", matches[0].name);
- user.RaiseMessage("");
+ if (matches.Count == 1)
+ {
+ // If there is only 1 match, display it
+ _user.RaiseMessage("Found 1 close match: \"{0}\".\r\n", matches[0].name);
moduleToShow = matches[0];
}
else
{
- // Display the found close matches.
- string[] strings_matches = new string[matches.Count];
+ // Display the found close matches
+ var stringsMatches = new string[matches.Count];
- for (int i = 0; i < matches.Count; i++)
+ for (var i = 0; i < matches.Count; i++)
{
- strings_matches[i] = matches[i].name;
+ stringsMatches[i] = matches[i].name;
}
- int selection = user.RaiseSelectionDialog("Close matches", strings_matches);
+ var selection = _user.RaiseSelectionDialog("Close matches", stringsMatches);
if (selection < 0)
{
- return Exit.BADOPT;
+ return Exit.BadOpt;
}
- // Mark the selection as the one to show.
+ // Mark the selection as the one to show
moduleToShow = matches[selection];
}
}
@@ -93,158 +101,167 @@ public int RunCommand(CKAN.GameInstance ksp, object raw_options)
return ShowMod(moduleToShow);
}
- ///
- /// Shows information about the mod.
- ///
- /// Success status.
- /// The module to show.
- public int ShowMod(InstalledModule module)
+ private int ShowMod(InstalledModule module)
{
- // Display the basic info.
- int return_value = ShowMod(module.Module);
+ // Display the basic info
+ var returnValue = ShowMod(module.Module);
- // Display InstalledModule specific information.
- ICollection files = module.Files as ICollection;
- if (files == null) throw new InvalidCastException();
+ // Display InstalledModule specific information
+ if (!(module.Files is ICollection files))
+ {
+ throw new InvalidCastException();
+ }
if (!module.Module.IsDLC)
{
- user.RaiseMessage("\r\nShowing {0} installed files:", files.Count);
- foreach (string file in files)
+ _user.RaiseMessage("\r\nShowing {0} installed files:", files.Count);
+ foreach (var file in files)
{
- user.RaiseMessage("- {0}", file);
+ _user.RaiseMessage("- {0}", file);
}
}
- return return_value;
+ return returnValue;
}
- ///
- /// Shows information about the mod.
- ///
- /// Success status.
- /// The module to show.
- public int ShowMod(CkanModule module)
+ private int ShowMod(CkanModule module)
{
- #region Abstract and description
+ // Abstract and description
+
if (!string.IsNullOrEmpty(module.@abstract))
{
- user.RaiseMessage("{0}: {1}", module.name, module.@abstract);
+ _user.RaiseMessage("{0}: {1}", module.name, module.@abstract);
}
else
{
- user.RaiseMessage("{0}", module.name);
+ _user.RaiseMessage("{0}", module.name);
}
if (!string.IsNullOrEmpty(module.description))
{
- user.RaiseMessage("\r\n{0}\r\n", module.description);
+ _user.RaiseMessage("\r\n{0}\r\n", module.description);
}
- #endregion
- #region General info (author, version...)
- user.RaiseMessage("\r\nModule info:");
- user.RaiseMessage("- version:\t{0}", module.version);
+ // General info (author, version...)
+
+ _user.RaiseMessage("\r\nModule info:\r\n- version:\t{0}", module.version);
if (module.author != null)
{
- user.RaiseMessage("- authors:\t{0}", string.Join(", ", module.author));
+ _user.RaiseMessage("- authors:\t{0}", string.Join(", ", module.author));
}
else
{
// Did you know that authors are optional in the spec?
- // You do now. #673.
- user.RaiseMessage("- authors:\tUNKNOWN");
+ // You do now. #673
+ _user.RaiseMessage("- authors:\tUNKNOWN");
}
- user.RaiseMessage("- status:\t{0}", module.release_status);
- user.RaiseMessage("- license:\t{0}", string.Join(", ", module.license));
- #endregion
+ _user.RaiseMessage("- status:\t{0}", module.release_status);
+ _user.RaiseMessage("- license:\t{0}", string.Join(", ", module.license));
+
+ // Relationships
- #region Relationships
if (module.depends != null && module.depends.Count > 0)
{
- user.RaiseMessage("\r\nDepends:");
- foreach (RelationshipDescriptor dep in module.depends)
- user.RaiseMessage("- {0}", RelationshipToPrintableString(dep));
+ _user.RaiseMessage("\r\nDepends:");
+ foreach (var dep in module.depends)
+ {
+ _user.RaiseMessage("- {0}", RelationshipToPrintableString(dep));
+ }
}
if (module.recommends != null && module.recommends.Count > 0)
{
- user.RaiseMessage("\r\nRecommends:");
- foreach (RelationshipDescriptor dep in module.recommends)
- user.RaiseMessage("- {0}", RelationshipToPrintableString(dep));
+ _user.RaiseMessage("\r\nRecommends:");
+ foreach (var dep in module.recommends)
+ {
+ _user.RaiseMessage("- {0}", RelationshipToPrintableString(dep));
+ }
}
if (module.suggests != null && module.suggests.Count > 0)
{
- user.RaiseMessage("\r\nSuggests:");
- foreach (RelationshipDescriptor dep in module.suggests)
- user.RaiseMessage("- {0}", RelationshipToPrintableString(dep));
+ _user.RaiseMessage("\r\nSuggests:");
+ foreach (var dep in module.suggests)
+ {
+ _user.RaiseMessage("- {0}", RelationshipToPrintableString(dep));
+ }
}
if (module.ProvidesList != null && module.ProvidesList.Count > 0)
{
- user.RaiseMessage("\r\nProvides:");
- foreach (string prov in module.ProvidesList)
- user.RaiseMessage("- {0}", prov);
+ _user.RaiseMessage("\r\nProvides:");
+ foreach (var prov in module.ProvidesList)
+ {
+ _user.RaiseMessage("- {0}", prov);
+ }
}
- #endregion
- user.RaiseMessage("\r\nResources:");
+ _user.RaiseMessage("\r\nResources:");
+
if (module.resources != null)
{
if (module.resources.bugtracker != null)
{
- user.RaiseMessage("- bugtracker: {0}", Uri.EscapeUriString(module.resources.bugtracker.ToString()));
+ _user.RaiseMessage("- bugtracker: {0}", Uri.EscapeUriString(module.resources.bugtracker.ToString()));
}
+
if (module.resources.homepage != null)
{
- user.RaiseMessage("- homepage: {0}", Uri.EscapeUriString(module.resources.homepage.ToString()));
+ _user.RaiseMessage("- homepage: {0}", Uri.EscapeUriString(module.resources.homepage.ToString()));
}
+
if (module.resources.spacedock != null)
{
- user.RaiseMessage("- spacedock: {0}", Uri.EscapeUriString(module.resources.spacedock.ToString()));
+ _user.RaiseMessage("- spacedock: {0}", Uri.EscapeUriString(module.resources.spacedock.ToString()));
}
+
if (module.resources.repository != null)
{
- user.RaiseMessage("- repository: {0}", Uri.EscapeUriString(module.resources.repository.ToString()));
+ _user.RaiseMessage("- repository: {0}", Uri.EscapeUriString(module.resources.repository.ToString()));
}
+
if (module.resources.curse != null)
{
- user.RaiseMessage("- curse: {0}", Uri.EscapeUriString(module.resources.curse.ToString()));
+ _user.RaiseMessage("- curse: {0}", Uri.EscapeUriString(module.resources.curse.ToString()));
}
+
if (module.resources.store != null)
{
- user.RaiseMessage("- store: {0}", Uri.EscapeUriString(module.resources.store.ToString()));
+ _user.RaiseMessage("- store: {0}", Uri.EscapeUriString(module.resources.store.ToString()));
}
+
if (module.resources.steamstore != null)
{
- user.RaiseMessage("- steamstore: {0}", Uri.EscapeUriString(module.resources.steamstore.ToString()));
+ _user.RaiseMessage("- steamstore: {0}", Uri.EscapeUriString(module.resources.steamstore.ToString()));
}
}
if (!module.IsDLC)
{
- // Compute the CKAN filename.
- string file_uri_hash = NetFileCache.CreateURLHash(module.download);
- string file_name = CkanModule.StandardName(module.identifier, module.version);
-
- user.RaiseMessage("\r\nFilename: {0}", file_uri_hash + "-" + file_name);
+ // Compute the CKAN filename
+ var fileUriHash = NetFileCache.CreateURLHash(module.download);
+ var fileName = CkanModule.StandardName(module.identifier, module.version);
+
+ _user.RaiseMessage("\r\nFilename: {0}", fileUriHash + "-" + fileName);
}
- return Exit.OK;
+ return Exit.Ok;
}
- ///
- /// Formats a RelationshipDescriptor into a user-readable string:
- /// Name, version: x, min: x, max: x
- ///
private static string RelationshipToPrintableString(RelationshipDescriptor dep)
{
- StringBuilder sb = new StringBuilder();
- sb.Append(dep.ToString());
+ var sb = new StringBuilder();
+ sb.Append(dep);
return sb.ToString();
}
}
+
+ [Verb("show", HelpText = "Show information about a mod")]
+ internal class ShowOptions : InstanceSpecificOptions
+ {
+ [Value(0, MetaName = "Mod name", HelpText = "The mod name to show information about")]
+ public string ModName { get; set; }
+ }
}
diff --git a/Cmdline/Action/Update.cs b/Cmdline/Action/Update.cs
index cbb8d5bd8b..fd60067f67 100644
--- a/Cmdline/Action/Update.cs
+++ b/Cmdline/Action/Update.cs
@@ -1,127 +1,116 @@
using System.Collections.Generic;
using System.Linq;
+using CommandLine;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for updating the list of mods.
+ ///
public class Update : ICommand
{
- public IUser user { get; set; }
- private GameInstanceManager manager;
+ private readonly GameInstanceManager _manager;
+ private readonly IUser _user;
///
- /// Initialize the update command object
+ /// Initializes a new instance of the class.
///
- /// GameInstanceManager containing our instances
- /// IUser object for interaction
- public Update(GameInstanceManager mgr, IUser user)
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ public Update(GameInstanceManager manager, IUser user)
{
- manager = mgr;
- this.user = user;
+ _manager = manager;
+ _user = user;
}
///
- /// Update the registry
+ /// Run the 'update' command.
///
- /// Game instance to update
- /// Command line options object
- ///
- /// Exit code for shell environment
- ///
- public int RunCommand(CKAN.GameInstance ksp, object raw_options)
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- UpdateOptions options = (UpdateOptions) raw_options;
+ var opts = (UpdateOptions)args;
- List compatible_prior = null;
+ List compatiblePrior = null;
- if (options.list_changes)
+ if (opts.ListChanges)
{
- // Get a list of compatible modules prior to the update.
- var registry = RegistryManager.Instance(ksp).registry;
- compatible_prior = registry.CompatibleModules(ksp.VersionCriteria()).ToList();
+ // Get a list of compatible modules prior to the update
+ var registry = RegistryManager.Instance(inst).registry;
+ compatiblePrior = registry.CompatibleModules(inst.VersionCriteria()).ToList();
}
- // If no repository is selected, select all.
- if (options.repo == null)
+ // If no repository is selected, select all
+ if (opts.Repo == null)
{
- options.update_all = true;
+ opts.All = true;
}
try
{
- if (options.update_all)
+ if (opts.All)
{
- UpdateRepository(ksp);
+ UpdateRepository(inst);
}
else
{
- UpdateRepository(ksp, options.repo);
+ UpdateRepository(inst, opts.Repo);
}
}
catch (ReinstallModuleKraken rmk)
{
- Upgrade.UpgradeModules(manager, user, ksp, false, rmk.Modules);
+ Upgrade.UpgradeModules(_manager, _user, inst, false, rmk.Modules);
}
catch (MissingCertificateKraken kraken)
{
- // Handling the kraken means we have prettier output.
- user.RaiseMessage(kraken.ToString());
- return Exit.ERROR;
+ // Handling the kraken means we have prettier output
+ _user.RaiseMessage(kraken.ToString());
+ return Exit.Error;
}
- if (options.list_changes)
+ if (opts.ListChanges)
{
- var registry = RegistryManager.Instance(ksp).registry;
- PrintChanges(compatible_prior, registry.CompatibleModules(ksp.VersionCriteria()).ToList());
+ var registry = RegistryManager.Instance(inst).registry;
+ PrintChanges(compatiblePrior, registry.CompatibleModules(inst.VersionCriteria()).ToList());
}
- return Exit.OK;
+ return Exit.Ok;
}
- ///
- /// Locates the changes between the prior and post state of the modules..
- ///
- /// List of the compatible modules prior to the update.
- /// List of the compatible modules after the update.
- private void PrintChanges(List modules_prior, List modules_post)
+ private void PrintChanges(List modulesPrior, List modulesPost)
{
- var prior = new HashSet(modules_prior, new NameComparer());
- var post = new HashSet(modules_post, new NameComparer());
-
+ var prior = new HashSet(modulesPrior, new NameComparer());
+ var post = new HashSet(modulesPost, new NameComparer());
var added = new HashSet(post.Except(prior, new NameComparer()));
var removed = new HashSet(prior.Except(post, new NameComparer()));
-
- var unchanged = post.Intersect(prior);//Default compare includes versions
+ // Default compare includes versions
+ var unchanged = post.Intersect(prior);
var updated = post.Except(unchanged).Except(added).Except(removed).ToList();
- // Print the changes.
- user.RaiseMessage("Found {0} new modules, {1} removed modules and {2} updated modules.", added.Count(), removed.Count(), updated.Count());
+ // Print the changes
+ _user.RaiseMessage("Found {0} new mods, {1} removed mods and {2} updated mods.", added.Count, removed.Count, updated.Count);
if (added.Count > 0)
{
- PrintModules("New modules [Name (CKAN identifier)]:", added);
+ PrintModules("New mods [Name (CKAN identifier)]:", added);
}
if (removed.Count > 0)
{
- PrintModules("Removed modules [Name (CKAN identifier)]:", removed);
+ PrintModules("Removed mods [Name (CKAN identifier)]:", removed);
}
if (updated.Count > 0)
{
- PrintModules("Updated modules [Name (CKAN identifier)]:", updated);
+ PrintModules("Updated mods [Name (CKAN identifier)]:", updated);
}
}
- ///
- /// Prints a message and a list of modules. Ends with a blank line.
- ///
- /// The message to print.
- /// The modules to list.
private void PrintModules(string message, IEnumerable modules)
{
- // Check input.
+ // Check input
if (message == null)
{
throw new BadCommandKraken("Message cannot be null.");
@@ -132,30 +121,38 @@ private void PrintModules(string message, IEnumerable modules)
throw new BadCommandKraken("List of modules cannot be null.");
}
- user.RaiseMessage(message);
+ _user.RaiseMessage(message);
- foreach (CkanModule module in modules)
+ foreach (var module in modules)
{
- user.RaiseMessage("{0} ({1})", module.name, module.identifier);
+ _user.RaiseMessage("{0} ({1})", module.name, module.identifier);
}
- user.RaiseMessage("");
+ _user.RaiseMessage("");
}
- ///
- /// Updates the repository.
- ///
- /// The KSP instance to work on.
- /// Repository to update. If null all repositories are used.
- private void UpdateRepository(CKAN.GameInstance ksp, string repository = null)
+ private void UpdateRepository(CKAN.GameInstance inst, string repository = null)
{
- RegistryManager registry_manager = RegistryManager.Instance(ksp);
+ var registryManager = RegistryManager.Instance(inst);
- var updated = repository == null
- ? CKAN.Repo.UpdateAllRepositories(registry_manager, ksp, manager.Cache, user) != CKAN.RepoUpdateResult.Failed
- : CKAN.Repo.Update(registry_manager, ksp, user, repository);
+ _ = repository == null
+ ? CKAN.Repo.UpdateAllRepositories(registryManager, inst, _manager.Cache, _user) != RepoUpdateResult.Failed
+ : CKAN.Repo.Update(registryManager, inst, _user, repository);
- user.RaiseMessage("Updated information on {0} compatible modules", registry_manager.registry.CompatibleModules(ksp.VersionCriteria()).Count());
+ _user.RaiseMessage("Updated information on {0} compatible mods.", registryManager.registry.CompatibleModules(inst.VersionCriteria()).Count());
}
}
+
+ [Verb("update", HelpText = "Update list of available mods")]
+ internal class UpdateOptions : InstanceSpecificOptions
+ {
+ [Option('r', "repo", HelpText = "CKAN repository to use (experimental!)")]
+ public string Repo { get; set; }
+
+ [Option("all", HelpText = "Upgrade all available updated modules")]
+ public bool All { get; set; }
+
+ [Option("list-changes", HelpText = "List new and removed modules")]
+ public bool ListChanges { get; set; }
+ }
}
diff --git a/Cmdline/Action/Upgrade.cs b/Cmdline/Action/Upgrade.cs
index b0bac76861..7225a402af 100644
--- a/Cmdline/Action/Upgrade.cs
+++ b/Cmdline/Action/Upgrade.cs
@@ -1,104 +1,103 @@
-using System;
+using System.Collections.Generic;
using System.Linq;
-using System.Collections.Generic;
using System.Transactions;
-using log4net;
using CKAN.Versioning;
+using CommandLine;
+using log4net;
-namespace CKAN.CmdLine
+namespace CKAN.CmdLine.Action
{
+ ///
+ /// Class for managing the upgrading of mods.
+ ///
public class Upgrade : ICommand
{
- private static readonly ILog log = LogManager.GetLogger(typeof(Upgrade));
+ private static readonly ILog Log = LogManager.GetLogger(typeof(Upgrade));
- public IUser User { get; set; }
- private GameInstanceManager manager;
+ private readonly GameInstanceManager _manager;
+ private readonly IUser _user;
///
- /// Initialize the upgrade command object
+ /// Initializes a new instance of the class.
///
- /// GameInstanceManager containing our instances
- /// IUser object for interaction
- public Upgrade(GameInstanceManager mgr, IUser user)
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ public Upgrade(GameInstanceManager manager, IUser user)
{
- manager = mgr;
- User = user;
+ _manager = manager;
+ _user = user;
}
///
- /// Upgrade an installed module
+ /// Run the 'upgrade' command.
///
- /// Game instance from which to remove
- /// Command line options object
- ///
- /// Exit code for shell environment
- ///
- public int RunCommand(CKAN.GameInstance ksp, object raw_options)
+ ///
+ public int RunCommand(CKAN.GameInstance inst, object args)
{
- UpgradeOptions options = (UpgradeOptions) raw_options;
-
- if (options.ckan_file != null)
- {
- options.modules.Add(MainClass.LoadCkanFromFile(ksp, options.ckan_file).identifier);
- }
-
- if (options.modules.Count == 0 && !options.upgrade_all)
+ var opts = (UpgradeOptions)args;
+ if (!opts.Mods.Any() && !opts.All)
{
- // What? No files specified?
- User.RaiseMessage("Usage: ckan upgrade Mod [Mod2, ...]");
- User.RaiseMessage(" or ckan upgrade --all");
+ _user.RaiseMessage("upgrade [ ...] - argument(s) missing, perhaps you forgot it?");
+ _user.RaiseMessage("If you want to upgrade all mods, use: ckan upgrade --all");
if (AutoUpdate.CanUpdate)
{
- User.RaiseMessage(" or ckan upgrade ckan");
+ _user.RaiseMessage("To update CKAN itself, use: ckan upgrade ckan");
}
- return Exit.BADOPT;
+
+ return Exit.BadOpt;
+ }
+
+ if (opts.CkanFile != null)
+ {
+ opts.Mods.ToList().Add(MainClass.LoadCkanFromFile(inst, opts.CkanFile).identifier);
}
- if (!options.upgrade_all && options.modules[0] == "ckan" && AutoUpdate.CanUpdate)
+ if (!opts.All && opts.Mods.ToList()[0] == "ckan" && AutoUpdate.CanUpdate)
{
- User.RaiseMessage("Querying the latest CKAN version");
+ _user.RaiseMessage("Getting the latest CKAN version...");
AutoUpdate.Instance.FetchLatestReleaseInfo();
+
var latestVersion = AutoUpdate.Instance.latestUpdate.Version;
var currentVersion = new ModuleVersion(Meta.GetVersion(VersionFormat.Short));
if (latestVersion.IsGreaterThan(currentVersion))
{
- User.RaiseMessage("New CKAN version available - " + latestVersion);
var releaseNotes = AutoUpdate.Instance.latestUpdate.ReleaseNotes;
- User.RaiseMessage(releaseNotes);
- User.RaiseMessage("\r\n");
- if (User.RaiseYesNoDialog("Proceed with install?"))
+ _user.RaiseMessage("There is a new CKAN version available: {0} ", latestVersion);
+ _user.RaiseMessage("{0}\r\n", releaseNotes);
+
+ if (_user.RaiseYesNoDialog("Proceed with install?"))
{
- User.RaiseMessage("Upgrading CKAN, please wait..");
+ _user.RaiseMessage("Upgrading CKAN, please wait...");
AutoUpdate.Instance.StartUpdateProcess(false);
}
}
else
{
- User.RaiseMessage("You already have the latest version.");
+ _user.RaiseMessage("You already have the latest version of CKAN.");
}
- return Exit.OK;
+ return Exit.Ok;
}
try
{
- var regMgr = RegistryManager.Instance(ksp);
+ var regMgr = RegistryManager.Instance(inst);
var registry = regMgr.registry;
- if (options.upgrade_all)
+ if (opts.All)
{
- var to_upgrade = new List();
+ var toUpgrade = new List();
- foreach (KeyValuePair mod in registry.Installed(false))
+ foreach (var mod in registry.Installed(false))
{
try
{
// Check if upgrades are available
- var latest = registry.LatestAvailable(mod.Key, ksp.VersionCriteria());
+ var latest = registry.LatestAvailable(mod.Key, inst.VersionCriteria());
- // This may be an unindexed mod. If so,
- // skip rather than crash. See KSP-CKAN/CKAN#841.
+ // This may be an un-indexed mod. If so,
+ // skip rather than crash. See KSP-CKAN/CKAN#841
if (latest == null || latest.IsDLC)
{
continue;
@@ -107,83 +106,86 @@ public int RunCommand(CKAN.GameInstance ksp, object raw_options)
if (latest.version.IsGreaterThan(mod.Value))
{
// Upgradable
- log.InfoFormat("New version {0} found for {1}",
- latest.version, latest.identifier);
- to_upgrade.Add(latest);
+ Log.InfoFormat("Found a new version for \"{0}\".", latest.identifier);
+ toUpgrade.Add(latest);
}
-
}
catch (ModuleNotFoundKraken)
{
- log.InfoFormat("{0} is installed, but no longer in the registry",
- mod.Key);
+ Log.InfoFormat("\"{0}\" is installed, but is no longer in the registry.", mod.Key);
}
}
- UpgradeModules(manager, User, ksp, true, to_upgrade);
+
+ UpgradeModules(_manager, _user, inst, true, toUpgrade);
}
else
{
- Search.AdjustModulesCase(ksp, options.modules);
- UpgradeModules(manager, User, ksp, options.modules);
+ Search.AdjustModulesCase(inst, opts.Mods.ToList());
+ UpgradeModules(_manager, _user, inst, opts.Mods.ToList());
}
- User.RaiseMessage("");
+
+ _user.RaiseMessage("");
}
- catch (CancelledActionKraken k)
+ catch (CancelledActionKraken kraken)
{
- User.RaiseMessage("Upgrade aborted: {0}", k.Message);
- return Exit.ERROR;
+ _user.RaiseMessage("Upgrade aborted: {0}.", kraken.Message);
+ return Exit.Error;
}
catch (ModuleNotFoundKraken kraken)
{
- User.RaiseMessage("Module {0} not found", kraken.module);
- return Exit.ERROR;
+ _user.RaiseMessage("Could not find \"{0}\".", kraken.module);
+ return Exit.Error;
}
catch (InconsistentKraken kraken)
{
- User.RaiseMessage(kraken.ToString());
- return Exit.ERROR;
+ _user.RaiseMessage(kraken.ToString());
+ return Exit.Error;
}
catch (ModuleIsDLCKraken kraken)
{
- User.RaiseMessage($"CKAN can't upgrade expansion '{kraken.module.name}' for you.");
+ _user.RaiseMessage("Can't upgrade the expansion \"{0}\".", kraken.module.name);
var res = kraken?.module?.resources;
- var storePagesMsg = new Uri[] { res?.store, res?.steamstore }
+ var storePagesMsg = new[] { res?.store, res?.steamstore }
.Where(u => u != null)
.Aggregate("", (a, b) => $"{a}\r\n- {b}");
+
if (!string.IsNullOrEmpty(storePagesMsg))
{
- User.RaiseMessage($"To upgrade this expansion, download any updates from the store page from which you purchased it:\r\n{storePagesMsg}");
+ _user.RaiseMessage("To upgrade this expansion, download any updates from the store page from which you purchased it:\r\n {0}", storePagesMsg);
}
- return Exit.ERROR;
+
+ return Exit.Error;
}
- return Exit.OK;
+ _user.RaiseMessage("Successfully upgraded requested mods.");
+ return Exit.Ok;
}
///
- /// Upgrade some modules by their CkanModules
+ /// Upgrade some modules by their s.
///
- /// Game instance manager to use
- /// IUser object for output
- /// Game instance to use
- /// List of modules to upgrade
- public static void UpgradeModules(GameInstanceManager manager, IUser user, CKAN.GameInstance ksp, bool ConfirmPrompt, List modules)
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ /// The game instance which to handle with mods.
+ /// Whether to confirm the prompt.
+ /// List of modules to upgrade.
+ public static void UpgradeModules(GameInstanceManager manager, IUser user, CKAN.GameInstance ksp, bool confirmPrompt, List modules)
{
UpgradeModules(manager, user, ksp,
(ModuleInstaller installer, NetAsyncModulesDownloader downloader, RegistryManager regMgr, ref HashSet possibleConfigOnlyDirs) =>
installer.Upgrade(modules, downloader,
- ref possibleConfigOnlyDirs, regMgr, true, true, ConfirmPrompt),
+ ref possibleConfigOnlyDirs, regMgr, true, true, confirmPrompt),
m => modules.Add(m)
);
}
///
- /// Upgrade some modules by their identifier and (optional) version
+ /// Upgrade some modules by their identifier and (optional) version.
///
- /// Game instance manager to use
- /// IUser object for output
- /// Game instance to use
- /// List of identifier[=version] to upgrade
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ /// The game instance which to handle with mods.
+ /// List of identifier[=version] to upgrade.
public static void UpgradeModules(GameInstanceManager manager, IUser user, CKAN.GameInstance ksp, List identsAndVersions)
{
UpgradeModules(manager, user, ksp,
@@ -198,27 +200,23 @@ public static void UpgradeModules(GameInstanceManager manager, IUser user, CKAN.
private delegate void AttemptUpgradeAction(ModuleInstaller installer, NetAsyncModulesDownloader downloader, RegistryManager regMgr, ref HashSet possibleConfigOnlyDirs);
///
- /// The core of the module upgrading logic, with callbacks to
- /// support different input formats managed by the calling code.
- /// Handles transactions, creating commonly required objects,
- /// looping logic, prompting for TooManyModsProvideKraken resolution.
+ /// The core of the module upgrading logic, with callbacks to support different input formats managed by the calling code.
+ /// Handles transactions, creating commonly required objects, looping logic, prompting for resolution.
///
- /// Game instance manager to use
- /// IUser object for output
- /// Game instance to use
- /// Function to call to try to perform the actual upgrade, may throw TooManyModsProvideKraken
- /// Function to call when the user has requested a new module added to the change set in response to TooManyModsProvideKraken
- private static void UpgradeModules(
- GameInstanceManager manager, IUser user, CKAN.GameInstance ksp,
- AttemptUpgradeAction attemptUpgradeCallback,
- System.Action addUserChoiceCallback)
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ /// The game instance which to handle with mods.
+ /// Function to call to try to perform the actual upgrade, may throw .
+ /// Function to call when the user has requested a new module added to the change set in response to .
+ private static void UpgradeModules(GameInstanceManager manager, IUser user, CKAN.GameInstance ksp, AttemptUpgradeAction attemptUpgradeCallback, System.Action addUserChoiceCallback)
{
using (TransactionScope transact = CkanTransaction.CreateTransactionScope()) {
- var installer = new ModuleInstaller(ksp, manager.Cache, user);
+ var installer = new ModuleInstaller(ksp, manager.Cache, user);
var downloader = new NetAsyncModulesDownloader(user, manager.Cache);
- var regMgr = RegistryManager.Instance(ksp);
+ var regMgr = RegistryManager.Instance(ksp);
HashSet possibleConfigOnlyDirs = null;
bool done = false;
+
while (!done)
{
try
@@ -232,6 +230,7 @@ private static void UpgradeModules(
int choice = user.RaiseSelectionDialog(
$"Choose a module to provide {k.requested}:",
k.modules.Select(m => $"{m.identifier} ({m.name})").ToArray());
+
if (choice < 0)
{
return;
@@ -246,4 +245,26 @@ private static void UpgradeModules(
}
}
+
+ [Verb("upgrade", HelpText = "Upgrade an installed mod")]
+ internal class UpgradeOptions : InstanceSpecificOptions
+ {
+ [Option('c', "ckanfile", HelpText = "Local CKAN file to process")]
+ public string CkanFile { get; set; }
+
+ [Option("no-recommends", HelpText = "Do not install recommended mods")]
+ public bool NoRecommends { get; set; }
+
+ [Option("with-suggests", HelpText = "Install suggested mods")]
+ public bool WithSuggests { get; set; }
+
+ [Option("with-all-suggests", HelpText = "Install suggested mods all the way down")]
+ public bool WithAllSuggests { get; set; }
+
+ [Option("all", HelpText = "Upgrade all available updated mods")]
+ public bool All { get; set; }
+
+ [Value(0, MetaName = "Mod name(s)", HelpText = "The mod name(s) to upgrade")]
+ public IEnumerable Mods { get; set; }
+ }
}
diff --git a/Cmdline/CKAN-cmdline.csproj b/Cmdline/CKAN-cmdline.csproj
index bf7e0b3da6..d3e06f923c 100644
--- a/Cmdline/CKAN-cmdline.csproj
+++ b/Cmdline/CKAN-cmdline.csproj
@@ -38,7 +38,7 @@
-
+
@@ -75,10 +75,9 @@
-
-
+
diff --git a/Cmdline/ConsoleUser.cs b/Cmdline/ConsoleUser.cs
index 1f0c511e8d..551a08b80a 100644
--- a/Cmdline/ConsoleUser.cs
+++ b/Cmdline/ConsoleUser.cs
@@ -1,40 +1,38 @@
using System;
-using System.Linq;
+using System.Linq;
using System.Text.RegularExpressions;
using log4net;
namespace CKAN.CmdLine
{
///
- /// The commandline implementation of the IUser interface.
+ /// The commandline implementation of the interface.
///
public class ConsoleUser : IUser
{
- ///
- /// A logger for this class.
- /// ONLY FOR INTERNAL USE!
- ///
- private static readonly ILog log = LogManager.GetLogger(typeof(ConsoleUser));
+ private static readonly ILog Log = LogManager.GetLogger(typeof(ConsoleUser));
+
+ private int _previousPercent = -1;
+ private bool _atStartOfLine = true;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// If set to true, supress interactive dialogs like Yes/No-Dialog or SelectionDialog
- public ConsoleUser (bool headless)
+ /// If set to , suppresses interactive dialogs like the Yes/No-Dialog or the SelectionDialog.
+ public ConsoleUser(bool headless)
{
Headless = headless;
}
///
- /// Gets a value indicating whether this is headless
+ /// Gets a value indicating whether this is headless.
///
- /// true if headless; otherwise, false
public bool Headless { get; }
///
- /// Ask the user for a yes or no input
+ /// Asks the user for a yes or no input.
///
- /// Question
+ /// The question to display.
public bool RaiseYesNoDialog(string question)
{
if (Headless)
@@ -49,7 +47,7 @@ public bool RaiseYesNoDialog(string question)
if (input == null)
{
- log.ErrorFormat("No console available for input, assuming no.");
+ Log.Error("No console available for input, assuming no.");
return false;
}
@@ -59,13 +57,15 @@ public bool RaiseYesNoDialog(string question)
{
return true;
}
+
if (input.Equals("n") || input.Equals("no"))
{
return false;
}
+
if (input.Equals(string.Empty))
{
- // User pressed enter without any text, assuming default choice.
+ // User pressed enter without any text, assuming default choice
return true;
}
@@ -74,52 +74,53 @@ public bool RaiseYesNoDialog(string question)
}
///
- /// Ask the user to select one of the elements of the array.
+ /// Asks the user to select one of the elements of the array.
/// The output is index 0 based.
/// To supply a default option, make the first option an integer indicating the index of it.
///
- /// The selection dialog
- /// Message
- /// Array of available options
+ /// The user inputted integer of the selection dialog.
+ /// The message to display.
+ /// The arguments to format the message.
+ /// Thrown if or is .
public int RaiseSelectionDialog(string message, params object[] args)
{
- const int return_cancel = -1;
+ const int returnCancel = -1;
- // Check for the headless flag.
+ // Check for the headless flag
if (Headless)
{
- // Return that the user cancelled the selection process.
- return return_cancel;
+ // Return that the user cancelled the selection process
+ return returnCancel;
}
- // Validate input.
- if (String.IsNullOrWhiteSpace(message))
+ // Validate input
+ if (string.IsNullOrWhiteSpace(message))
{
- throw new Kraken("Passed message string must be non-empty.");
+ throw new Kraken("The passed message string must be non-empty.");
}
if (args.Length == 0)
{
- throw new Kraken("Passed list of selection candidates must be non-empty.");
+ throw new Kraken("The passed list of selection candidates must be non-empty.");
}
- // Check if we have a default selection.
- int defaultSelection = -1;
+ // Check if we have a default selection
+ var defaultSelection = -1;
- if (args[0] is int)
+ if (args[0] is int @int)
{
- // Check that the default selection makes sense.
- defaultSelection = (int)args[0];
+ // Check that the default selection makes sense
+ defaultSelection = @int;
if (defaultSelection < 0 || defaultSelection > args.Length - 1)
{
- throw new Kraken("Passed default arguments is out of range of the selection candidates.");
+ throw new Kraken("The passed default arguments are out of range of the selection candidates.");
}
- // Extract the relevant arguments.
- object[] newArgs = new object[args.Length - 1];
+ // Extract the relevant arguments
+ var newArgs = new object[args.Length - 1];
- for (int i = 1; i < args.Length; i++)
+ for (var i = 1; i < args.Length; i++)
{
newArgs[i - 1] = args[i];
}
@@ -127,10 +128,10 @@ public int RaiseSelectionDialog(string message, params object[] args)
args = newArgs;
}
- // Further data validation.
- foreach (object argument in args)
+ // Further data validation
+ foreach (var argument in args)
{
- if (String.IsNullOrWhiteSpace(argument.ToString()))
+ if (string.IsNullOrWhiteSpace(argument.ToString()))
{
throw new Kraken("Candidate may not be empty.");
}
@@ -139,67 +140,70 @@ public int RaiseSelectionDialog(string message, params object[] args)
// Write passed message
RaiseMessage(message);
- // List options.
- for (int i = 0; i < args.Length; i++)
+ // List options
+ for (var i = 0; i < args.Length; i++)
{
- string CurrentRow = String.Format("{0}", i + 1);
+ var currentRow = string.Format("{0}", i + 1);
if (i == defaultSelection)
{
- CurrentRow += "*";
+ currentRow += "*";
}
- CurrentRow += String.Format(") {0}", args[i]);
+ currentRow += string.Format(") {0}", args[i]);
- RaiseMessage(CurrentRow);
+ RaiseMessage(currentRow);
}
- // Create message string.
- string output = String.Format("Enter a number between {0} and {1} (To cancel press \"c\" or \"n\".", 1, args.Length);
+ // Create message string
+ var output = "\r\n";
+
+ output += args.Length == 1
+ ? string.Format("Enter the number {0} or press \"Enter\" to select {0}", 1)
+ : string.Format("Enter a number between {0} and {1} to select an instance", 1, args.Length);
+
+ output += ". To cancel, press \"c\" or \"n\".";
if (defaultSelection >= 0)
{
- output += String.Format(" \"Enter\" will select {0}.", defaultSelection + 1);
+ output += string.Format(" \"Enter\" will select {0}.", defaultSelection + 1);
}
- output += "): ";
-
RaiseMessage(output);
- bool valid = false;
- int result = 0;
+ var valid = false;
+ var result = 0;
while (!valid)
{
- // Wait for input from the command line.
- string input = Console.In.ReadLine();
+ // Wait for input from the command line
+ var input = Console.In.ReadLine();
if (input == null)
{
- // No console present, cancel the process.
- return return_cancel;
+ // No console present, cancel the process
+ return returnCancel;
}
input = input.Trim().ToLower();
- // Check for default selection.
- if (String.IsNullOrEmpty(input))
+ // Check for default selection
+ if (string.IsNullOrEmpty(input))
{
- if (defaultSelection >= 0)
+ if (defaultSelection >= 0 || args.Length == 1)
{
return defaultSelection;
}
}
- // Check for cancellation characters.
+ // Check for cancellation characters
if (input == "c" || input == "n")
{
RaiseMessage("Selection cancelled.");
-
- return return_cancel;
+ return returnCancel;
}
- // Attempt to parse the input.
+ // Attempt to parse the input
try
{
result = Convert.ToInt32(input);
@@ -215,26 +219,23 @@ public int RaiseSelectionDialog(string message, params object[] args)
continue;
}
- // Check the input against the boundaries.
+ // Check the input against the boundaries
if (result > args.Length)
{
- RaiseMessage("The number in the input is too large.");
- RaiseMessage(output);
-
+ RaiseMessage("The number in the input is too large.\r\n{0}", output);
continue;
}
- else if (result < 1)
- {
- RaiseMessage("The number in the input is too small.");
- RaiseMessage(output);
+ if (result < 1)
+ {
+ RaiseMessage("The number in the input is too small.\r\n{0}", output);
continue;
}
- // The list we provide is index 1 based, but the array is index 0 based.
+ // The list we provide is index 1 based, but the array is index 0 based
result--;
- // We have checked for all errors and have gotten a valid result. Stop the input loop.
+ // We have checked for all errors and have gotten a valid result. Stop the input loop
valid = true;
}
@@ -242,46 +243,43 @@ public int RaiseSelectionDialog(string message, params object[] args)
}
///
- /// Write an error to the console
+ /// Writes an error message to the console.
///
- /// Message
- /// Possible arguments to format the message
+ /// The message to display.
+ /// The arguments to format the message.
public void RaiseError(string message, params object[] args)
{
GoToStartOfLine();
if (Headless)
{
- // Special GitHub Action formatting for mutli-line errors
- log.ErrorFormat(
- message.Replace("\r\n", "%0A"),
- args.Select(a => a.ToString().Replace("\r\n", "%0A")).ToArray()
- );
+ // Special GitHub Action formatting for multi-line errors
+ Log.ErrorFormat(message.Replace("\r\n", "%0A"), args.Select(a => a.ToString().Replace("\r\n", "%0A")).ToArray());
}
else
{
Console.Error.WriteLine(message, args);
}
- atStartOfLine = true;
+
+ _atStartOfLine = true;
}
///
- /// Write a progress message including the percentage to the console.
+ /// Writes a progress message including the percentage to the console.
/// Rewrites the line, so the console is not cluttered by progress messages.
///
- /// Message
- /// Progress in percent
+ /// The message to display.
+ /// The current progress in percent.
public void RaiseProgress(string message, int percent)
{
if (Regex.IsMatch(message, "download", RegexOptions.IgnoreCase))
{
// In headless mode, only print a new message if the percent has changed,
// to reduce clutter in Jenkins for large downloads
- if (!Headless || percent != previousPercent)
+ if (!Headless || percent != _previousPercent)
{
// The \r at the front here causes download messages to *overwrite* each other.
- Console.Write(
- "\r{0} - {1}% ", message, percent);
- previousPercent = percent;
+ Console.Write("\r{0} - {1}% ", message, percent);
+ _previousPercent = percent;
}
}
else
@@ -292,37 +290,31 @@ public void RaiseProgress(string message, int percent)
GoToStartOfLine();
Console.Write("{0}", message);
}
+
// These messages leave the cursor at the end of a line of text
- atStartOfLine = false;
+ _atStartOfLine = false;
}
///
- /// Needed for
+ /// Writes an informative message to the console.
///
- private int previousPercent = -1;
-
- ///
- /// Writes a message to the console
- ///
- /// Message
- /// Arguments to format the message
+ /// The message to display.
+ /// The arguments to format the message.
public void RaiseMessage(string message, params object[] args)
{
GoToStartOfLine();
Console.WriteLine(message, args);
- atStartOfLine = true;
+ _atStartOfLine = true;
}
private void GoToStartOfLine()
{
- if (!atStartOfLine)
+ if (!_atStartOfLine)
{
// Carriage return
Console.WriteLine("");
- atStartOfLine = true;
+ _atStartOfLine = true;
}
}
-
- private bool atStartOfLine = true;
}
}
diff --git a/Cmdline/Exit.cs b/Cmdline/Exit.cs
deleted file mode 100644
index 12c1418582..0000000000
--- a/Cmdline/Exit.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace CKAN.CmdLine
-{
- public static class Exit
- {
- public static readonly int OK = 0;
- public static readonly int ERROR = 1;
- public static readonly int BADOPT = 2;
- }
-}
-
diff --git a/Cmdline/Main.cs b/Cmdline/Main.cs
index 78fe0887e3..3803ff48cd 100644
--- a/Cmdline/Main.cs
+++ b/Cmdline/Main.cs
@@ -6,25 +6,27 @@
using System;
using System.Net;
using System.Diagnostics;
-using System.IO;
using System.Linq;
using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
using CKAN.CmdLine.Action;
+using CommandLine;
+using CommandLine.Text;
using log4net;
using log4net.Core;
namespace CKAN.CmdLine
{
- internal class MainClass
+ public class MainClass
{
- private static readonly ILog log = LogManager.GetLogger(typeof (MainClass));
+ private static readonly ILog Log = LogManager.GetLogger(typeof(MainClass));
+
+ private static GameInstanceManager _manager;
+ private static IUser _user;
/*
* When the STAThread is applied, it changes the apartment state of the current thread to be single threaded.
* Without getting into a huge discussion about COM and threading,
- * this attribute ensures the communication mechanism between the current thread an
+ * this attribute ensures the communication mechanism between the current thread and
* other threads that may want to talk to it via COM. When you're using Windows Forms,
* depending on the feature you're using, it may be using COM interop in order to communicate with
* operating system components. Good examples of this are the Clipboard and the File Dialogs.
@@ -32,32 +34,53 @@ internal class MainClass
[STAThread]
public static int Main(string[] args)
{
- // Launch debugger if the "--debugger" flag is present in the command line arguments.
- // We want to do this as early as possible so just check the flag manually, rather than doing the
- // more robust argument parsing.
- if (args.Any(i => i == "--debugger"))
- {
- Debugger.Launch();
- }
-
- // Default to GUI if there are no command line args or if the only args are flags rather than commands.
- if (args.All(a => a == "--verbose" || a == "--debug" || a == "--asroot" || a == "--show-console"))
+ try
{
- var guiCommand = args.ToList();
- guiCommand.Insert(0, "gui");
- args = guiCommand.ToArray();
- }
-
- Logging.Initialize();
- log.Info("CKAN started.");
+ // Launch debugger if the "--debugger" flag is present in the command line arguments.
+ // We want to do this as early as possible so just check the flag manually, rather than doing the
+ // more robust argument parsing.
+ if (args.Any(i => i == "--debugger"))
+ {
+ Debugger.Launch();
+ }
- // Force-allow TLS 1.2 for HTTPS URLs, because GitHub requires it.
- // This is on by default in .NET 4.6, but not in 4.5.
- ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
+ // Default to GUI if there are no command line args or if the only args are flags rather than commands.
+ if (args.All(a => a == "-v" || a == "--verbose" || a == "-d" || a == "--debug" || a == "--asroot" || a == "--show-console" || a == "--debugger"))
+ {
+ var guiCommand = args.ToList();
+ guiCommand.Insert(0, "gui");
+ args = guiCommand.ToArray();
+ }
- try
- {
- return Execute(null, null, args);
+ var types = LoadVerbs();
+ var parser = new Parser(c => c.HelpWriter = null).ParseVerbs(args, types);
+ var result = parser.MapResult(opts => Execute(_manager, opts, args), errs =>
+ {
+ if (errs.IsVersion())
+ {
+ Console.WriteLine(Meta.GetVersion(VersionFormat.Full));
+ }
+ else
+ {
+ var ht = HelpText.AutoBuild(parser, h =>
+ {
+ h.AddDashesToOption = true; // Add dashes to options
+ h.AddNewLineBetweenHelpSections = true; // Add blank line between heading and usage
+ h.AutoHelp = false; // Hide built-in help option
+ h.AutoVersion = false; // Hide built-in version option
+ h.Heading = $"CKAN {Meta.GetVersion(VersionFormat.Full)}"; // Create custom heading
+ h.Copyright = $"Copyright © 2014-{DateTime.Now.Year}"; // Create custom copyright
+ h.AddPreOptionsLine(GetUsage(args)); // Show usage
+ return HelpText.DefaultParsingErrorsHandler(parser, h);
+ }, e => e, true);
+
+ Console.WriteLine(ht);
+ }
+
+ return Exit.Ok;
+ });
+
+ return result;
}
finally
{
@@ -65,292 +88,274 @@ public static int Main(string[] args)
}
}
- public static int Execute(GameInstanceManager manager, CommonOptions opts, string[] args)
+ ///
+ /// This is purely made for the tests to be able to pass over a .
+ /// ONLY FOR INTERNAL USE !!!
+ ///
+ /// The command line arguments handled by the parser.
+ /// The dummy manager to provide dummy game instances.
+ /// An code.
+ public static int MainForTests(string[] args, GameInstanceManager manager = null)
{
- // We shouldn't instantiate Options if it's a subcommand.
- // It breaks command-specific help, for starters.
- try
- {
- switch (args[0])
- {
- case "repair":
- return (new Repair()).RunSubCommand(manager, opts, new SubCommandOptions(args));
+ _manager = manager;
+ return Main(args);
+ }
- case "instance":
- return (new GameInstance()).RunSubCommand(manager, opts, new SubCommandOptions(args));
+ private static Type[] LoadVerbs()
+ {
+ return Assembly.GetExecutingAssembly().GetTypes()
+ .Where(t => t.GetCustomAttribute() != null)
+ .Except(Assembly.GetExecutingAssembly().GetTypes()
+ .Where(t => t.GetCustomAttribute() != null)
+ .ToArray())
+ .ToArray();
+ }
- case "compat":
- return (new Compat()).RunSubCommand(manager, opts, new SubCommandOptions(args));
+ ///
+ /// Executes the provided command.
+ ///
+ /// The manager to provide game instances.
+ /// The command line arguments handled by the parser.
+ /// An code.
+ public static int Execute(GameInstanceManager manager, object args, string[] argStrings)
+ {
+ var s = args.ToString();
+ var opts = s.Replace(s.Substring(0, s.LastIndexOf('.') + 1), "").Split('+');
- case "repo":
- return (new Repo()).RunSubCommand(manager, opts, new SubCommandOptions(args));
+ try
+ {
+ Logging.Initialize();
+ Log.Info("CKAN started.");
- case "authtoken":
- return (new AuthToken()).RunSubCommand(manager, opts, new SubCommandOptions(args));
+ // Force-allow TLS 1.2 for HTTPS URLs, because GitHub requires it.
+ // This is on by default in .NET 4.6, but not in 4.5.
+ ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
- case "cache":
- return (new Cache()).RunSubCommand(manager, opts, new SubCommandOptions(args));
-
- case "mark":
- return (new Mark()).RunSubCommand(manager, opts, new SubCommandOptions(args));
+ switch (opts[0])
+ {
+ case "AuthTokenOptions":
+ return new AuthToken().RunCommand(manager, args);
+ case "CacheOptions":
+ return new Cache().RunCommand(manager, args);
+ case "CompatOptions":
+ return new Compat().RunCommand(manager, args);
+ case "KspOptions":
+ return new Action.GameInstance().RunCommand(manager, args);
+ case "MarkOptions":
+ return new Mark().RunCommand(manager, args);
+ case "RepairOptions":
+ return new Repair().RunCommand(manager, args);
+ case "RepoOptions":
+ return new Action.Repo().RunCommand(manager, args);
}
}
catch (NoGameInstanceKraken)
{
- return printMissingInstanceError(new ConsoleUser(false));
+ return PrintMissingInstanceError(new ConsoleUser(false));
}
finally
{
- log.Info("CKAN exiting.");
+ Log.Info("CKAN exiting.");
}
- Options cmdline;
- try
- {
- cmdline = new Options(args);
- }
- catch (BadCommandKraken)
- {
- return AfterHelp();
- }
- finally
- {
- log.Info("CKAN exiting.");
- }
-
- // Process commandline options.
- CommonOptions options = (CommonOptions)cmdline.options;
- options.Merge(opts);
- IUser user = new ConsoleUser(options.Headless);
+ CommonOptions options = new CommonOptions();
+ _user = new ConsoleUser(options.Headless);
if (manager == null)
{
- manager = new GameInstanceManager(user);
+ manager = new GameInstanceManager(_user);
}
else
{
- manager.User = user;
+ manager.User = _user;
}
try
{
- int exitCode = options.Handle(manager, user);
- if (exitCode != Exit.OK)
+ var exitCode = options.Handle(manager, _user);
+ if (exitCode != Exit.Ok)
return exitCode;
- // Don't bother with instances or registries yet because some commands don't need them.
- return RunSimpleAction(cmdline, options, args, user, manager);
- }
- finally
- {
- log.Info("CKAN exiting.");
- }
- }
- public static int AfterHelp()
- {
- // Our help screen will already be shown. Let's add some extra data.
- new ConsoleUser(false).RaiseMessage("You are using CKAN version {0}", Meta.GetVersion(VersionFormat.Full));
- return Exit.BADOPT;
- }
-
- public static CKAN.GameInstance GetGameInstance(GameInstanceManager manager)
- {
- CKAN.GameInstance inst = manager.CurrentInstance
- ?? manager.GetPreferredInstance();
- if (inst == null)
- {
- throw new NoGameInstanceKraken();
- }
- return inst;
- }
-
- ///
- /// Run whatever action the user has provided
- ///
- /// The exit status that should be returned to the system.
- private static int RunSimpleAction(Options cmdline, CommonOptions options, string[] args, IUser user, GameInstanceManager manager)
- {
- try
- {
- switch (cmdline.action)
+ var instance = GetGameInstance(manager);
+ switch (opts[0])
{
- case "gui":
- return Gui(manager, (GuiOptions)options, args);
-
- case "consoleui":
- return ConsoleUi(manager, (ConsoleUIOptions)options, args);
-
- case "prompt":
- return new Prompt().RunCommand(manager, cmdline.options);
-
- case "version":
- return Version(user);
-
- case "update":
- return (new Update(manager, user)).RunCommand(GetGameInstance(manager), cmdline.options);
-
- case "available":
- return (new Available(user)).RunCommand(GetGameInstance(manager), cmdline.options);
-
- case "add":
- case "install":
- Scan(GetGameInstance(manager), user, cmdline.action);
- return (new Install(manager, user)).RunCommand(GetGameInstance(manager), cmdline.options);
-
- case "scan":
- return Scan(GetGameInstance(manager), user);
-
- case "list":
- return (new List(user)).RunCommand(GetGameInstance(manager), cmdline.options);
-
- case "show":
- return (new Show(user)).RunCommand(GetGameInstance(manager), cmdline.options);
-
- case "replace":
- Scan(GetGameInstance(manager), user, cmdline.action);
- return (new Replace(manager, user)).RunCommand(GetGameInstance(manager), (ReplaceOptions)cmdline.options);
-
- case "upgrade":
- Scan(GetGameInstance(manager), user, cmdline.action);
- return (new Upgrade(manager, user)).RunCommand(GetGameInstance(manager), cmdline.options);
-
- case "search":
- return (new Search(user)).RunCommand(GetGameInstance(manager), options);
-
- case "uninstall":
- case "remove":
- return (new Remove(manager, user)).RunCommand(GetGameInstance(manager), cmdline.options);
-
- case "import":
- return (new Import(manager, user)).RunCommand(GetGameInstance(manager), options);
-
- case "clean":
- return Clean(manager.Cache);
-
- case "compare":
- return (new Compare(user)).RunCommand(cmdline.options);
-
+ case "AvailableOptions":
+ return new Available(_user).RunCommand(instance, args);
+ case "CompareOptions":
+ return new Compare(_user).RunCommand(instance, args);
+ case "ConsoleUiOptions":
+ return ConsoleUi(manager, args);
+ case "GuiOptions":
+ return Gui(manager, args, argStrings);
+ case "ImportOptions":
+ return new Import(manager, _user).RunCommand(instance, args);
+ case "InstallOptions":
+ Scan(instance, _user, "install");
+ return new Install(manager, _user).RunCommand(instance, args);
+ case "ListOptions":
+ return new List(_user).RunCommand(instance, args);
+ case "PromptOptions":
+ return new Prompt().RunCommand(manager, args, argStrings);
+ case "RemoveOptions":
+ return new Remove(manager, _user).RunCommand(instance, args);
+ case "ReplaceOptions":
+ Scan(instance, _user, "replace");
+ return new Replace(manager, _user).RunCommand(instance, args);
+ case "ScanOptions":
+ return Scan(instance, _user);
+ case "SearchOptions":
+ return new Search(_user).RunCommand(instance, args);
+ case "ShowOptions":
+ return new Show(_user).RunCommand(instance, args);
+ case "UpdateOptions":
+ return new Update(manager, _user).RunCommand(instance, args);
+ case "UpgradeOptions":
+ Scan(instance, _user, "upgrade");
+ return new Upgrade(manager, _user).RunCommand(instance, args);
default:
- user.RaiseMessage("Unknown command, try --help");
- return Exit.BADOPT;
+ return Exit.BadOpt;
}
}
- catch (NoGameInstanceKraken)
- {
- return printMissingInstanceError(user);
- }
finally
{
- RegistryManager.DisposeAll();
+ Log.Info("CKAN exiting.");
}
}
- internal static CkanModule LoadCkanFromFile(CKAN.GameInstance current_instance, string ckan_file)
+ private static string GetUsage(string[] args)
{
- CkanModule module = CkanModule.FromFile(ckan_file);
-
- // We'll need to make some registry changes to do this.
- RegistryManager registry_manager = RegistryManager.Instance(current_instance);
-
- // Remove this version of the module in the registry, if it exists.
- registry_manager.registry.RemoveAvailable(module);
-
- // Sneakily add our version in...
- registry_manager.registry.AddAvailable(module);
-
- return module;
+ const string prefix = "USAGE:\r\n ckan";
+ switch (args[0])
+ {
+ case "authtoken":
+ return new AuthToken().GetUsage(prefix, args);
+ case "cache":
+ return new Cache().GetUsage(prefix, args);
+ case "compat":
+ return new Compat().GetUsage(prefix, args);
+ case "ksp":
+ return new Action.GameInstance().GetUsage(prefix, args);
+ case "mark":
+ return new Mark().GetUsage(prefix, args);
+ case "repair":
+ return new Repair().GetUsage(prefix, args);
+ case "repo":
+ return new Action.Repo().GetUsage(prefix, args);
+ case "install":
+ case "remove":
+ case "replace":
+ case "upgrade":
+ return $"{prefix} {args[0]} [options] [ ...]";
+ case "show":
+ return $"{prefix} {args[0]} [options] ";
+ case "compare":
+ return $"{prefix} {args[0]} [options] ";
+ case "import":
+ return $"{prefix} {args[0]} [options] [ ...]";
+ case "search":
+ return $"{prefix} {args[0]} [options] ";
+ case "available":
+ case "consoleui":
+ case "gui":
+ case "list":
+ case "prompt":
+ case "scan":
+ case "update":
+ return $"{prefix} {args[0]} [options]";
+ default:
+ return $"{prefix} [options]";
+ }
}
- private static int printMissingInstanceError(IUser user)
+ private static int PrintMissingInstanceError(IUser user)
{
- user.RaiseMessage("I don't know where a game instance is installed.");
- user.RaiseMessage("Use 'ckan instance help' for assistance in setting this.");
- return Exit.ERROR;
+ user.RaiseMessage("I don't know where KSP is installed.");
+ user.RaiseMessage("Use 'ckan ksp --help' for assistance in setting this.");
+ return Exit.Error;
}
- private static int Gui(GameInstanceManager manager, GuiOptions options, string[] args)
+ ///
+ /// Gets the current, or preferred, game instance to manipulate.
+ ///
+ /// The manager to provide game instances.
+ /// The current instance.
+ /// Throws if no valid instance was found.
+ public static GameInstance GetGameInstance(GameInstanceManager manager)
{
- // TODO: Sometimes when the GUI exits, we get a System.ArgumentException,
- // but trying to catch it here doesn't seem to help. Dunno why.
-
- GUI.Main_(args, manager, options.ShowConsole);
+ GameInstance instance = manager.CurrentInstance ?? manager.GetPreferredInstance();
+ if (instance == null)
+ {
+ throw new NoGameInstanceKraken(null);
+ }
- return Exit.OK;
+ return instance;
}
- private static int ConsoleUi(GameInstanceManager manager, ConsoleUIOptions opts, string[] args)
+ private static int ConsoleUi(GameInstanceManager manager, object args)
{
// Debug/verbose output just messes up the screen
LogManager.GetRepository().Threshold = Level.Warn;
- return CKAN.ConsoleUI.ConsoleUI.Main_(args, manager,
- opts.Theme ?? Environment.GetEnvironmentVariable("CKAN_CONSOLEUI_THEME") ?? "default",
- opts.Debug);
+
+ var opts = (ConsoleUiOptions)args;
+ _user.RaiseMessage("Starting ConsoleUI, please wait...");
+ return ConsoleUI.ConsoleUI.Main_(args.ToString().Split(), manager, opts.Theme ?? Environment.GetEnvironmentVariable("CKAN_CONSOLEUI_THEME") ?? "default", opts.Debug);
}
- private static int Version(IUser user)
+ private static int Gui(GameInstanceManager manager, object args, string[] argStrings)
{
- user.RaiseMessage(Meta.GetVersion(VersionFormat.Full));
+ // TODO: Sometimes when the GUI exits, we get a System.ArgumentException,
+ // but trying to catch it here doesn't seem to help. Dunno why.
- return Exit.OK;
+ var opts = (GuiOptions)args;
+ _user.RaiseMessage("Starting GUI, please wait...");
+ GUI.Main_(argStrings, manager, opts.ShowConsole);
+ return Exit.Ok;
}
- ///
- /// Scans the game instance. Detects installed mods to mark as auto-detected and checks the consistency
- ///
- /// The instance to scan
- ///
- /// Changes the output message if set.
- /// Exit.OK if instance is consistent, Exit.ERROR otherwise
- private static int Scan(CKAN.GameInstance inst, IUser user, string next_command = null)
+ private static int Scan(GameInstance instance, IUser user, string nextCommand = null)
{
try
{
- inst.Scan();
- return Exit.OK;
+ instance.Scan();
+ return Exit.Ok;
}
catch (InconsistentKraken kraken)
{
-
- if (next_command == null)
+ if (nextCommand == null)
{
user.RaiseError(kraken.InconsistenciesPretty);
user.RaiseError("The repo has not been saved.");
}
else
{
- user.RaiseMessage("Preliminary scanning shows that the install is in a inconsistent state.");
- user.RaiseMessage("Use ckan.exe scan for more details");
- user.RaiseMessage("Proceeding with {0} in case it fixes it.\r\n", next_command);
+ user.RaiseMessage("Preliminary scanning shows that the install is in an inconsistent state.");
+ user.RaiseMessage("Use 'ckan scan --help' for more details.");
+ user.RaiseMessage("Proceeding with {0} in case it fixes it.\r\n", nextCommand);
}
- return Exit.ERROR;
+ return Exit.Error;
}
}
- private static int Clean(NetModuleCache cache)
+ ///
+ /// Loads a .ckan file from the given path, reads it and creates a from it.
+ ///
+ /// The current instance to modify the module.
+ /// The path to the .ckan file.
+ /// A .
+ internal static CkanModule LoadCkanFromFile(GameInstance currentInstance, string ckanFile)
{
- cache.RemoveAll();
- return Exit.OK;
- }
- }
+ CkanModule module = CkanModule.FromFile(ckanFile);
- public class NoGameInstanceKraken : Kraken
- {
- public NoGameInstanceKraken() { }
- }
+ // We'll need to make some registry changes to do this
+ RegistryManager registryManager = RegistryManager.Instance(currentInstance);
- public class CmdLineUtil
- {
- public static uint GetUID()
- {
- if (Platform.IsUnix || Platform.IsMac)
- {
- return getuid();
- }
+ // Remove this version of the module in the registry, if it exists
+ registryManager.registry.RemoveAvailable(module);
- return 1;
- }
+ // Sneakily add our version in...
+ registryManager.registry.AddAvailable(module);
- [DllImport("libc")]
- private static extern uint getuid();
+ return module;
+ }
}
}
diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs
index 0e88b1a902..5bee05fe83 100644
--- a/Cmdline/Options.cs
+++ b/Cmdline/Options.cs
@@ -1,221 +1,45 @@
using System;
using System.IO;
using System.Reflection;
-using System.Collections.Generic;
+using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
+using CommandLine;
using log4net;
using log4net.Core;
-using CommandLine;
-using CommandLine.Text;
namespace CKAN.CmdLine
{
- // Look, parsing options is so easy and beautiful I made
- // it into a special class for you to admire!
-
- public class Options
- {
- public string action { get; set; }
- public object options { get; set; }
-
- ///
- /// Returns an options object on success. Prints a default help
- /// screen and throws a BadCommandKraken on failure.
- ///
- public Options(string[] args)
- {
- Parser.Default.ParseArgumentsStrict
- (
- args, new Actions(), (verb, suboptions) =>
- {
- action = verb;
- options = suboptions;
- },
- delegate
- {
- throw new BadCommandKraken();
- }
- );
- }
- }
-
- // Actions supported by our client go here.
-
- internal class Actions : VerbCommandOptions
- {
- [VerbOption("gui", HelpText = "Start the CKAN GUI")]
- public GuiOptions GuiOptions { get; set; }
-
- [VerbOption("consoleui", HelpText = "Start the CKAN console UI")]
- public ConsoleUIOptions ConsoleUIOptions { get; set; }
-
- [VerbOption("prompt", HelpText = "Run CKAN prompt for executing multiple commands in a row")]
- public CommonOptions PromptOptions { get; set; }
-
- [VerbOption("search", HelpText = "Search for mods")]
- public SearchOptions SearchOptions { get; set; }
-
- [VerbOption("upgrade", HelpText = "Upgrade an installed mod")]
- public UpgradeOptions Upgrade { get; set; }
-
- [VerbOption("update", HelpText = "Update list of available mods")]
- public UpdateOptions Update { get; set; }
-
- [VerbOption("available", HelpText = "List available mods")]
- public AvailableOptions Available { get; set; }
-
- [VerbOption("install", HelpText = "Install a mod")]
- public InstallOptions Install { get; set; }
-
- [VerbOption("remove", HelpText = "Remove an installed mod")]
- public RemoveOptions Remove { get; set; }
-
- [VerbOption("import", HelpText = "Import manually downloaded mods")]
- public ImportOptions Import { get; set; }
-
- [VerbOption("scan", HelpText = "Scan for manually installed mods")]
- public ScanOptions Scan { get; set; }
-
- [VerbOption("list", HelpText = "List installed modules")]
- public ListOptions List { get; set; }
-
- [VerbOption("show", HelpText = "Show information about a mod")]
- public ShowOptions Show { get; set; }
-
- [VerbOption("clean", HelpText = "Clean away downloaded files from the cache")]
- public CleanOptions Clean { get; set; }
-
- [VerbOption("repair", HelpText = "Attempt various automatic repairs")]
- public SubCommandOptions Repair { get; set; }
-
- [VerbOption("replace", HelpText = "Replace list of replaceable mods")]
- public ReplaceOptions Replace { get; set; }
-
- [VerbOption("repo", HelpText = "Manage CKAN repositories")]
- public SubCommandOptions Repo { get; set; }
-
- [VerbOption("mark", HelpText = "Edit flags on modules")]
- public SubCommandOptions Mark { get; set; }
-
- [VerbOption("instance", HelpText = "Manage game instances")]
- public SubCommandOptions Instance { get; set; }
-
- [VerbOption("authtoken", HelpText = "Manage authentication tokens")]
- public AuthTokenSubOptions AuthToken { get; set; }
-
- [VerbOption("cache", HelpText = "Manage download cache path")]
- public SubCommandOptions Cache { get; set; }
-
- [VerbOption("compat", HelpText = "Manage game version compatibility")]
- public SubCommandOptions Compat { get; set; }
-
- [VerbOption("compare", HelpText = "Compare version strings")]
- public CompareOptions Compare { get; set; }
-
- [VerbOption("version", HelpText = "Show the version of the CKAN client being used")]
- public VersionOptions Version { get; set; }
-
- [HelpVerbOption]
- public string GetUsage(string verb)
- {
- HelpText ht = HelpText.AutoBuild(this, verb);
-
- // Add a usage prefix line
- ht.AddPreOptionsLine(" ");
- if (string.IsNullOrEmpty(verb))
- {
- ht.AddPreOptionsLine($"Usage: ckan [options]");
- }
- else
- {
- ht.AddPreOptionsLine(verb + " - " + GetDescription(verb));
- switch (verb)
- {
- // First the commands that deal with mods
- case "add":
- case "install":
- case "remove":
- case "uninstall":
- case "upgrade":
- ht.AddPreOptionsLine($"Usage: ckan {verb} [options] modules");
- break;
- case "show":
- ht.AddPreOptionsLine($"Usage: ckan {verb} [options] module");
- break;
-
- // Now the commands with other string arguments
- case "search":
- ht.AddPreOptionsLine($"Usage: ckan {verb} [options] substring");
- break;
- case "compare":
- ht.AddPreOptionsLine($"Usage: ckan {verb} [options] version1 version2");
- break;
- case "import":
- ht.AddPreOptionsLine($"Usage: ckan {verb} [options] paths");
- break;
-
- // Now the commands with only --flag type options
- case "gui":
- case "available":
- case "list":
- case "update":
- case "scan":
- case "clean":
- case "version":
- default:
- ht.AddPreOptionsLine($"Usage: ckan {verb} [options]");
- break;
- }
- }
- return ht;
- }
-
- }
-
- public abstract class VerbCommandOptions
+ ///
+ /// Common options for all commands.
+ ///
+ internal class CommonOptions
{
- protected string GetDescription(string verb)
- {
- var info = this.GetType().GetProperties();
- foreach (var property in info)
- {
- BaseOptionAttribute attrib = (BaseOptionAttribute)Attribute.GetCustomAttribute(
- property, typeof(BaseOptionAttribute), false);
- if (attrib != null && attrib.LongName == verb)
- return attrib.HelpText;
- }
- return "";
- }
- }
-
- // Options common to all classes.
+ private static readonly ILog Log = LogManager.GetLogger(typeof(CommonOptions));
- public class CommonOptions
- {
- [Option('v', "verbose", DefaultValue = false, HelpText = "Show more of what's going on when running.")]
+ [Option('v', "verbose", HelpText = "Show more of what's going on when running")]
public bool Verbose { get; set; }
- [Option('d', "debug", DefaultValue = false, HelpText = "Show debugging level messages. Implies verbose")]
+ [Option('d', "debug", HelpText = "Show debugging level messages. Implies verbose")]
public bool Debug { get; set; }
- [Option("debugger", DefaultValue = false, HelpText = "Launch debugger at start")]
+ [Option("debugger", HelpText = "Launch debugger at start")]
public bool Debugger { get; set; }
[Option("net-useragent", HelpText = "Set the default user-agent string for HTTP requests")]
public string NetUserAgent { get; set; }
- [Option("headless", DefaultValue = false, HelpText = "Set to disable all prompts")]
+ [Option("headless", HelpText = "Set to disable all prompts")]
public bool Headless { get; set; }
- [Option("asroot", DefaultValue = false, HelpText = "Allows CKAN to run as root on Linux-based systems")]
+ [Option("asroot", HelpText = "Allows CKAN to run as root on Linux-based systems")]
public bool AsRoot { get; set; }
- [HelpVerbOption]
- public string GetUsage(string verb)
- {
- return HelpText.AutoBuild(this, verb);
- }
-
+ ///
+ /// Handle the common options.
+ ///
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ /// An code.
public virtual int Handle(GameInstanceManager manager, IUser user)
{
CheckMonoVersion(user, 3, 1, 0);
@@ -223,84 +47,65 @@ public virtual int Handle(GameInstanceManager manager, IUser user)
// Processes in Docker containers normally run as root.
// If we are running in a Docker container, do not require --asroot.
// Docker creates a .dockerenv file in the root of each container.
- if ((Platform.IsUnix || Platform.IsMac) && CmdLineUtil.GetUID() == 0 && !File.Exists("/.dockerenv"))
+ if ((Platform.IsUnix || Platform.IsMac) && GetUid() == 0 && !File.Exists("/.dockerenv"))
{
if (!AsRoot)
{
- user.RaiseError("You are trying to run CKAN as root.\r\nThis is a bad idea and there is absolutely no good reason to do it. Please run CKAN from a user account (or use --asroot if you are feeling brave).");
- return Exit.ERROR;
- }
- else
- {
- user.RaiseMessage("Warning: Running CKAN as root!");
+ user.RaiseError("You are trying to run CKAN as root.\r\nThis is a bad idea and there is absolutely no good reason to do it. Please run CKAN from an user account (or use --asroot if you are feeling brave).");
+ return Exit.Error;
}
+
+ user.RaiseMessage("Warning: Running CKAN as root!");
}
if (Debug)
{
LogManager.GetRepository().Threshold = Level.Debug;
- log.Info("Debug logging enabled");
+ Log.Info("Debug logging enabled");
}
else if (Verbose)
{
LogManager.GetRepository().Threshold = Level.Info;
- log.Info("Verbose logging enabled");
+ Log.Info("Verbose logging enabled");
}
// Assign user-agent string if user has given us one
- if (NetUserAgent != null)
+ if (!string.IsNullOrWhiteSpace(NetUserAgent))
{
Net.UserAgentString = NetUserAgent;
}
- return Exit.OK;
+ return Exit.Ok;
}
- ///
- /// Combine two options objects.
- /// This is mainly to ensure that --headless carries through for prompt.
- ///
- /// Options object to merge into this one
- public void Merge(CommonOptions otherOpts)
- {
- if (otherOpts != null)
- {
- Verbose = Verbose || otherOpts.Verbose;
- Debug = Debug || otherOpts.Debug;
- Debugger = Debugger || otherOpts.Debugger;
- NetUserAgent = NetUserAgent ?? otherOpts.NetUserAgent;
- Headless = Headless || otherOpts.Headless;
- AsRoot = AsRoot || otherOpts.AsRoot;
- }
- }
-
- private static void CheckMonoVersion(IUser user, int rec_major, int rec_minor, int rec_patch)
+ private static void CheckMonoVersion(IUser user, int recMajor, int recMinor, int recPatch)
{
try
{
- Type type = Type.GetType("Mono.Runtime");
- if (type == null) return;
+ var type = Type.GetType("Mono.Runtime");
+ if (type == null)
+ return;
- MethodInfo display_name = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
- if (display_name != null)
+ var displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
+ if (displayName != null)
{
- var version_string = (string) display_name.Invoke(null, null);
- var match = Regex.Match(version_string, @"^\D*(?[\d]+)\.(?\d+)\.(?\d+).*$");
+ var versionString = (string)displayName.Invoke(null, null);
+ var match = Regex.Match(versionString, @"^\D*(?[\d]+)\.(?\d+)\.(?\d+).*$");
if (match.Success)
{
- int major = Int32.Parse(match.Groups["major"].Value);
- int minor = Int32.Parse(match.Groups["minor"].Value);
- int patch = Int32.Parse(match.Groups["revision"].Value);
+ var major = int.Parse(match.Groups["major"].Value);
+ var minor = int.Parse(match.Groups["minor"].Value);
+ var patch = int.Parse(match.Groups["revision"].Value);
- if (major < rec_major || (major == rec_major && minor < rec_minor))
+ if (major < recMajor || major == recMajor && minor < recMinor)
{
user.RaiseMessage(
- "Warning. Detected mono runtime of {0} is less than the recommended version of {1}\r\n",
- String.Join(".", major, minor, patch),
- String.Join(".", rec_major, rec_minor, rec_patch)
- );
- user.RaiseMessage("Update recommend\r\n");
+ "Warning. Detected Mono version {0}, which is less than the recommended version of {1}.",
+ string.Join(".", major, minor, patch),
+ string.Join(".", recMajor, recMinor, recPatch)
+ );
+ user.RaiseMessage("Please update Mono via https://www.mono-project.com/download/stable/");
}
}
}
@@ -311,235 +116,86 @@ private static void CheckMonoVersion(IUser user, int rec_major, int rec_minor, i
}
}
- protected static readonly ILog log = LogManager.GetLogger(typeof(CommonOptions));
+ private static uint GetUid()
+ {
+ if (Platform.IsUnix || Platform.IsMac)
+ {
+ return getuid();
+ }
+
+ return 1;
+ }
+
+ [DllImport("libc")]
+ private static extern uint getuid();
}
- public class InstanceSpecificOptions : CommonOptions
+ ///
+ /// Instance specific options for commands dealing with mods.
+ ///
+ internal class InstanceSpecificOptions : CommonOptions
{
- [Option("instance", HelpText = "Game instance to use")]
- public string Instance { get; set; }
+ // Different 'SetName' properties make the options mutually exclusive
+ [Option("ksp", HelpText = "KSP install to use", SetName = "ksp")]
+ public string KSP { get; set; }
- [Option("gamedir", HelpText = "Game dir to use")]
- public string Gamedir { get; set; }
+ [Option("kspdir", HelpText = "KSP directory to use", SetName = "dir")]
+ public string KSPdir { get; set; }
+ ///
+ /// Handle the instance specific options. This also handles the common options.
+ ///
+ /// The manager to provide game instances.
+ /// The current to raise messages to the user.
+ /// An code.
public override int Handle(GameInstanceManager manager, IUser user)
{
- int exitCode = base.Handle(manager, user);
- if (exitCode == Exit.OK)
- {
- // User provided game instance
- if (Gamedir != null && Instance != null)
- {
- user.RaiseMessage("--instance and --gamedir can't be specified at the same time");
- return Exit.BADOPT;
- }
+ var exitCode = base.Handle(manager, user);
+ if (exitCode != Exit.Ok)
+ return exitCode;
- try
- {
- if (!string.IsNullOrEmpty(Instance))
- {
- // Set a game directory by its alias.
- manager.SetCurrentInstance(Instance);
- }
- else if (!string.IsNullOrEmpty(Gamedir))
- {
- // Set a game directory by its path
- manager.SetCurrentInstanceByPath(Gamedir);
- }
- }
- catch (NotKSPDirKraken k)
+ try
+ {
+ if (!string.IsNullOrWhiteSpace(KSP))
{
- user.RaiseMessage("Sorry, {0} does not appear to be a game instance", k.path);
- return Exit.BADOPT;
+ // Set a KSP directory by its alias.
+ manager.SetCurrentInstance(KSP);
}
- catch (InvalidKSPInstanceKraken k)
+ else if (!string.IsNullOrWhiteSpace(KSPdir))
{
- user.RaiseMessage("Invalid game instance specified \"{0}\", use '--gamedir' to specify by path, or 'instance list' to see known game instances", k.instance);
- return Exit.BADOPT;
+ // Set a KSP directory by its path
+ manager.SetCurrentInstanceByPath(KSPdir);
}
}
+ catch (NotKSPDirKraken kraken)
+ {
+ user.RaiseMessage("Sorry, \"{0}\" does not appear to be a KSP directory.", kraken.path);
+ return Exit.BadOpt;
+ }
+ catch (InvalidKSPInstanceKraken kraken)
+ {
+ user.RaiseMessage("Invalid KSP installation specified \"{0}\", use '--kspdir' to specify by path, or use 'ckan ksp list' to see known KSP installations.", kraken.instance);
+ return Exit.BadOpt;
+ }
+
return exitCode;
}
}
- ///
- /// For things which are subcommands ('instance', 'repair' etc), we just grab a list
- /// we can pass on.
- ///
- public class SubCommandOptions : CommonOptions
+ [Verb("consoleui", HelpText = "Start the CKAN console UI")]
+ internal class ConsoleUiOptions : InstanceSpecificOptions
{
- [ValueList(typeof(List))]
- public List options { get; set; }
-
- public SubCommandOptions() { }
-
- public SubCommandOptions(string[] args)
- {
- options = new List(args).GetRange(1, args.Length - 1);
- }
- }
-
- // 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 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))]
- 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; }
-
- [ValueList(typeof (List))]
- public List