diff --git a/CHANGELOG.md b/CHANGELOG.md
index 198565b9f9..7b583fbd58 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- [GUI] ru-RU translation (#3383 by: nt0g; reviewed: HebaruSan)
- [GUI] Japanese Localization (#3394 by: utah239; reviewed: HebaruSan)
- [Multiple] Match underscore in DLL to dash in identifier (#3412 by: HebaruSan; reviewed: DasSkelett)
+- [CLI] Add versions table option to ckan show command (#3414 by: HebaruSan; reviewed: DasSkelett)
### Bugfixes
diff --git a/Cmdline/Action/Search.cs b/Cmdline/Action/Search.cs
index 4064480f01..5fc4d40c95 100644
--- a/Cmdline/Action/Search.cs
+++ b/Cmdline/Action/Search.cs
@@ -122,6 +122,8 @@ public int RunCommand(CKAN.GameInstance ksp, object raw_options)
/// List of matching modules.
/// The KSP instance to perform the search for.
/// The search term. Case insensitive.
+ /// Name of author to find
+ /// True to look for incompatible modules, false (default) to look for compatible
public List PerformSearch(CKAN.GameInstance ksp, string term, string author = null, bool searchIncompatible = false)
{
// Remove spaces and special characters from the search term.
@@ -134,29 +136,25 @@ public List PerformSearch(CKAN.GameInstance ksp, string term, string
{
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();
+ .Where(module => (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) =>
- {
// 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();
+ .Where(module => (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();
}
}
diff --git a/Cmdline/Action/Show.cs b/Cmdline/Action/Show.cs
index e7c55d6ec9..635bfcf12a 100644
--- a/Cmdline/Action/Show.cs
+++ b/Cmdline/Action/Show.cs
@@ -3,12 +3,12 @@
using System.Linq;
using System.Text;
+using CKAN.Versioning;
+
namespace CKAN.CmdLine
{
public class Show : ICommand
{
- public IUser user { get; set; }
-
public Show(IUser user)
{
this.user = user;
@@ -17,80 +17,103 @@ public Show(IUser user)
public int RunCommand(CKAN.GameInstance ksp, object raw_options)
{
ShowOptions options = (ShowOptions) raw_options;
-
- if (options.Modname == null)
+ if (options.modules == null || options.modules.Count < 1)
{
// empty argument
user.RaiseMessage("show - module name argument missing, perhaps you forgot it?");
return Exit.BADOPT;
}
+ int combined_exit_code = Exit.OK;
// Check installed modules for an exact match.
var registry = RegistryManager.Instance(ksp).registry;
- var installedModuleToShow = registry.InstalledModule(options.Modname);
-
- if (installedModuleToShow != null)
- {
- // 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
- );
-
- if (moduleToShow == null)
+ foreach (string modName in options.modules)
{
- // 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());
-
- Search search = new Search(user);
- var matches = search.PerformSearch(ksp, options.Modname);
-
- // Display the results of the search.
- if (!matches.Any())
- {
- // No matches found.
- user.RaiseMessage("No close matches found.");
- return Exit.BADOPT;
- }
- else if (matches.Count() == 1)
+ var installedModuleToShow = registry.InstalledModule(modName);
+ if (installedModuleToShow != null)
{
- // If there is only 1 match, display it.
- user.RaiseMessage("Found 1 close match: {0}", matches[0].name);
+ // Show the installed module.
+ combined_exit_code = CombineExitCodes(
+ combined_exit_code,
+ ShowMod(installedModuleToShow)
+ );
+ if (options.with_versions)
+ {
+ ShowVersionTable(ksp, registry.AvailableByIdentifier(installedModuleToShow.identifier).ToList());
+ }
user.RaiseMessage("");
-
- moduleToShow = matches[0];
+ continue;
}
- else
- {
- // Display the found close matches.
- string[] strings_matches = new string[matches.Count];
- for (int i = 0; i < matches.Count; i++)
+ // 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 == modName
+ || mod.identifier == modName
+ );
+ if (moduleToShow == null)
+ {
+ // No exact match found. Try to look for a close match for this KSP version.
+ user.RaiseMessage(
+ "{0} not installed or compatible with {1} {2}.",
+ modName,
+ ksp.game.ShortName,
+ string.Join(", ", ksp.VersionCriteria().Versions.Select(v => v.ToString()))
+ );
+ user.RaiseMessage("Looking for close matches in compatible mods...");
+
+ Search search = new Search(user);
+ var matches = search.PerformSearch(ksp, modName);
+
+ // Display the results of the search.
+ if (!matches.Any())
{
- strings_matches[i] = matches[i].name;
+ // No matches found.
+ user.RaiseMessage("No close matches found.");
+ combined_exit_code = CombineExitCodes(combined_exit_code, Exit.BADOPT);
+ user.RaiseMessage("");
+ continue;
}
+ 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("");
- int selection = user.RaiseSelectionDialog("Close matches", strings_matches);
-
- if (selection < 0)
+ moduleToShow = matches[0];
+ }
+ else
{
- return Exit.BADOPT;
+ // Display the found close matches.
+ int selection = user.RaiseSelectionDialog(
+ "Close matches:",
+ matches.Select(m => m.name).ToArray()
+ );
+ user.RaiseMessage("");
+ if (selection < 0)
+ {
+ combined_exit_code = CombineExitCodes(combined_exit_code, Exit.BADOPT);
+ continue;
+ }
+
+ // Mark the selection as the one to show.
+ moduleToShow = matches[selection];
}
+ }
- // Mark the selection as the one to show.
- moduleToShow = matches[selection];
+ combined_exit_code = CombineExitCodes(
+ combined_exit_code,
+ ShowMod(moduleToShow)
+ );
+ if (options.with_versions)
+ {
+ ShowVersionTable(ksp, registry.AvailableByIdentifier(moduleToShow.identifier).ToList());
}
+ user.RaiseMessage("");
}
-
- return ShowMod(moduleToShow);
+ return combined_exit_code;
}
///
@@ -103,16 +126,20 @@ public int ShowMod(InstalledModule module)
// Display the basic info.
int return_value = ShowMod(module.Module);
- // Display InstalledModule specific information.
- ICollection files = module.Files as ICollection;
- if (files == null) throw new InvalidCastException();
-
if (!module.Module.IsDLC)
{
- user.RaiseMessage("\r\nShowing {0} installed files:", files.Count);
+ // Display InstalledModule specific information.
+ ICollection files = module.Files as ICollection;
+ if (files == null)
+ {
+ throw new InvalidCastException();
+ }
+
+ user.RaiseMessage("");
+ user.RaiseMessage("Showing {0} installed files:", files.Count);
foreach (string file in files)
{
- user.RaiseMessage("- {0}", file);
+ user.RaiseMessage(" - {0}", file);
}
}
@@ -138,89 +165,124 @@ public int ShowMod(CkanModule module)
if (!string.IsNullOrEmpty(module.description))
{
- user.RaiseMessage("\r\n{0}\r\n", module.description);
+ user.RaiseMessage("");
+ user.RaiseMessage("{0}", module.description);
}
#endregion
#region General info (author, version...)
- user.RaiseMessage("\r\nModule info:");
- user.RaiseMessage("- version:\t{0}", module.version);
+ user.RaiseMessage("");
+ user.RaiseMessage("Module info:");
+ user.RaiseMessage(" 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");
+ user.RaiseMessage(" Authors:\tUNKNOWN");
}
- user.RaiseMessage("- status:\t{0}", module.release_status);
- user.RaiseMessage("- license:\t{0}", string.Join(", ", module.license));
+ if (module.release_status != null)
+ {
+ user.RaiseMessage(" Status:\t{0}", module.release_status);
+ }
+ user.RaiseMessage(" License:\t{0}", string.Join(", ", module.license));
+ if (module.Tags != null && module.Tags.Count > 0)
+ {
+ // Need an extra space before the tab to line up with other fields
+ user.RaiseMessage(" Tags: \t{0}", string.Join(", ", module.Tags));
+ }
+ if (module.localizations != null && module.localizations.Length > 0)
+ {
+ user.RaiseMessage(" Languages:\t{0}", string.Join(", ", module.localizations.OrderBy(l => l)));
+ }
#endregion
#region Relationships
if (module.depends != null && module.depends.Count > 0)
{
- user.RaiseMessage("\r\nDepends:");
+ user.RaiseMessage("");
+ user.RaiseMessage("Depends:");
foreach (RelationshipDescriptor dep in module.depends)
- user.RaiseMessage("- {0}", RelationshipToPrintableString(dep));
+ {
+ user.RaiseMessage(" - {0}", RelationshipToPrintableString(dep));
+ }
}
if (module.recommends != null && module.recommends.Count > 0)
{
- user.RaiseMessage("\r\nRecommends:");
+ user.RaiseMessage("");
+ user.RaiseMessage("Recommends:");
foreach (RelationshipDescriptor dep in module.recommends)
- user.RaiseMessage("- {0}", RelationshipToPrintableString(dep));
+ {
+ user.RaiseMessage(" - {0}", RelationshipToPrintableString(dep));
+ }
}
if (module.suggests != null && module.suggests.Count > 0)
{
- user.RaiseMessage("\r\nSuggests:");
+ user.RaiseMessage("");
+ user.RaiseMessage("Suggests:");
foreach (RelationshipDescriptor dep in module.suggests)
- user.RaiseMessage("- {0}", RelationshipToPrintableString(dep));
+ {
+ user.RaiseMessage(" - {0}", RelationshipToPrintableString(dep));
+ }
}
- if (module.ProvidesList != null && module.ProvidesList.Count > 0)
+ if (module.provides != null && module.provides.Count > 0)
{
- user.RaiseMessage("\r\nProvides:");
- foreach (string prov in module.ProvidesList)
- user.RaiseMessage("- {0}", prov);
+ user.RaiseMessage("");
+ user.RaiseMessage("Provides:");
+ foreach (string prov in module.provides)
+ {
+ user.RaiseMessage(" - {0}", prov);
+ }
}
#endregion
- user.RaiseMessage("\r\nResources:");
if (module.resources != null)
{
- if (module.resources.bugtracker != null)
+ user.RaiseMessage("");
+ user.RaiseMessage("Resources:");
+ if (module.resources.homepage != null)
{
- user.RaiseMessage("- bugtracker: {0}", Uri.EscapeUriString(module.resources.bugtracker.ToString()));
+ user.RaiseMessage(" Home page:\t{0}", Uri.EscapeUriString(module.resources.homepage.ToString()));
}
- if (module.resources.homepage != null)
+ if (module.resources.manual != null)
{
- user.RaiseMessage("- homepage: {0}", Uri.EscapeUriString(module.resources.homepage.ToString()));
+ user.RaiseMessage(" Manual: {0}", Uri.EscapeUriString(module.resources.manual.ToString()));
}
if (module.resources.spacedock != null)
{
- user.RaiseMessage("- spacedock: {0}", Uri.EscapeUriString(module.resources.spacedock.ToString()));
+ user.RaiseMessage(" SpaceDock:\t{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:\t{0}", Uri.EscapeUriString(module.resources.repository.ToString()));
+ }
+ if (module.resources.bugtracker != null)
+ {
+ user.RaiseMessage(" Bug tracker:\t{0}", Uri.EscapeUriString(module.resources.bugtracker.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:\t{0}", Uri.EscapeUriString(module.resources.store.ToString()));
}
if (module.resources.steamstore != null)
{
- user.RaiseMessage("- steamstore: {0}", Uri.EscapeUriString(module.resources.steamstore.ToString()));
+ user.RaiseMessage(" Steam store:\t{0}", Uri.EscapeUriString(module.resources.steamstore.ToString()));
+ }
+ if (module.resources.remoteAvc != null)
+ {
+ user.RaiseMessage(" Version file: {0}", Uri.EscapeUriString(module.resources.remoteAvc.ToString()));
}
}
@@ -230,12 +292,52 @@ public int ShowMod(CkanModule module)
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);
+ user.RaiseMessage("");
+ user.RaiseMessage("Filename: {0}", file_uri_hash + "-" + file_name);
}
return Exit.OK;
}
+ private static int CombineExitCodes(int a, int b)
+ {
+ // Failures should dominate, keep whichever one isn't OK
+ return a == Exit.OK ? b : a;
+ }
+
+ private void ShowVersionTable(CKAN.GameInstance inst, List modules)
+ {
+ var versions = modules.Select(m => m.version.ToString()).ToList();
+ var gameVersions = modules.Select(m =>
+ {
+ GameVersion minKsp = null, maxKsp = null;
+ Registry.GetMinMaxVersions(new List() { m }, out _, out _, out minKsp, out maxKsp);
+ return GameVersionRange.VersionSpan(inst.game, minKsp, maxKsp);
+ }).ToList();
+ string[] headers = new string[] { "Version", "Game Versions" };
+ int versionLength = Math.Max(headers[0].Length, versions.Max(v => v.Length));
+ int gameVersionLength = Math.Max(headers[1].Length, gameVersions.Max(v => v.Length));
+ user.RaiseMessage("");
+ user.RaiseMessage(
+ "{0} {1}",
+ headers[0].PadRight(versionLength),
+ headers[1].PadRight(gameVersionLength)
+ );
+ user.RaiseMessage(
+ "{0} {1}",
+ new string('-', versionLength),
+ new string('-', gameVersionLength)
+ );
+ for (int row = 0; row < versions.Count; ++row)
+ {
+ user.RaiseMessage(
+ "{0} {1}",
+ versions[row].PadRight(versionLength),
+ gameVersions[row].PadRight(gameVersionLength)
+ );
+ }
+ }
+
///
/// Formats a RelationshipDescriptor into a user-readable string:
/// Name, version: x, min: x, max: x
@@ -246,5 +348,7 @@ private static string RelationshipToPrintableString(RelationshipDescriptor dep)
sb.Append(dep.ToString());
return sb.ToString();
}
+
+ private IUser user { get; set; }
}
}
diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs
index 0e88b1a902..1ce6a70238 100644
--- a/Cmdline/Options.cs
+++ b/Cmdline/Options.cs
@@ -516,7 +516,11 @@ internal class ImportOptions : InstanceSpecificOptions
internal class ShowOptions : InstanceSpecificOptions
{
- [ValueOption(0)] public string Modname { get; set; }
+ [Option("with-versions")]
+ public bool with_versions { get; set; }
+
+ [ValueList(typeof(List))]
+ public List modules { get; set; }
}
internal class SearchOptions : InstanceSpecificOptions