Skip to content

Commit

Permalink
Merge #2709 Ignore spaces and special chars in mod search
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Apr 25, 2019
2 parents 16eeaf1 + df698b1 commit 44d44ea
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 51 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- [GUI] Right click copy option for mod info links (#2699 by: HebaruSan; reviewed: DasSkelett)
- [GUI] Relabel Yes/No buttons for some questions (#2737 by: HebaruSan; reviewed: DasSkelett)
- [Netkan] Handle non-raw GitHub URLs for metanetkans and avc krefs (#2696 by: HebaruSan; reviewed: DasSkelett)
- [GUI] Ignore spaces and special chars in mod search (#2709 by: DasSkelett; reviewed: HebaruSan)

## Bugfixes

Expand Down
135 changes: 111 additions & 24 deletions Cmdline/Action/Search.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,60 +16,147 @@ public Search(IUser user)

public int RunCommand(CKAN.KSP ksp, object raw_options)
{
SearchOptions options = (SearchOptions) raw_options;
SearchOptions options = (SearchOptions)raw_options;

// Check the input.
if (String.IsNullOrWhiteSpace(options.search_term))
if (String.IsNullOrWhiteSpace(options.search_term) && String.IsNullOrWhiteSpace(options.author_term))
{
user.RaiseError("No search term?");

return Exit.BADOPT;
}

var matching_mods = PerformSearch(ksp, options.search_term);
List<CkanModule> matching_compatible = PerformSearch(ksp, options.search_term, options.author_term, false);
List<CkanModule> matching_incompatible = new List<CkanModule>();
if (options.all)
{
matching_incompatible = PerformSearch(ksp, options.search_term, options.author_term, true);
}

// Show how many matches we have.
user.RaiseMessage("Found " + matching_mods.Count().ToString() + " mods matching \"" + options.search_term + "\".");
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))
{
user.RaiseMessage("Found {0} compatible and {1} incompatible mods matching \"{2}\".",
matching_compatible.Count().ToString(),
matching_incompatible.Count().ToString(),
options.search_term);
}
else if (!options.all && !String.IsNullOrWhiteSpace(options.author_term))
{
user.RaiseMessage("Found {0} compatible mods matching \"{1}\" by \"{2}\".",
matching_compatible.Count().ToString(),
options.search_term,
options.author_term);
}
else if (!options.all && String.IsNullOrWhiteSpace(options.author_term))
{
user.RaiseMessage("Found {0} compatible mods matching \"{1}\".",
matching_compatible.Count().ToString(),
options.search_term);
}

// Present the results.
if (!matching_mods.Any())
if (!matching_compatible.Any() && (!options.all || !matching_incompatible.Any()))
{
return Exit.OK;
}

// Print each mod on a separate line.
foreach (CkanModule mod in matching_mods)
if (options.detail)
{
user.RaiseMessage(mod.identifier);
user.RaiseMessage("Matching compatible mods:");
foreach (CkanModule mod in matching_compatible)
{
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())
{
user.RaiseMessage("Matching incompatible mods:");
foreach (CkanModule mod in matching_incompatible)
{
Registry.GetMinMaxVersions(new List<CkanModule> { mod } , out _, out _, out var minKsp, out var maxKsp);
string kspVersion = Versioning.KspVersionRange.VersionSpan(minKsp, maxKsp).ToString();

user.RaiseMessage("* {0} ({1} - {2}) - {3} by {4} - {5}",
mod.identifier,
mod.version,
kspVersion,
mod.name,
mod.author == null ? "N/A" : String.Join(", ", mod.author),
mod.@abstract);
}
}
}
else
{
List<CkanModule> matching = matching_compatible.Concat(matching_incompatible).ToList();
matching.Sort((x, y) => string.Compare(x.identifier, y.identifier, StringComparison.Ordinal));

foreach (CkanModule mod in matching)
{
user.RaiseMessage(mod.identifier);
}
}

return Exit.OK;
}

/// <summary>
/// Searches for the term in the list of available modules for the ksp instance. Looks in name, identifier and description fields.
/// 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.
/// </summary>
/// <returns>List of mathcing modules.</returns>
/// <returns>List of matching modules.</returns>
/// <param name="ksp">The KSP instance to perform the search for.</param>
/// <param name="term">The search term. Case insensitive.</param>
public List<CkanModule> PerformSearch(CKAN.KSP ksp, string term)
public List<CkanModule> PerformSearch(CKAN.KSP ksp, 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, "");

var registry = RegistryManager.Instance(ksp).registry;
return registry
.Available(ksp.VersionCriteria())
.Where((module) =>
{
// Extract the description. This is an optional field and may be null.
string modDesc = string.Empty;

if (!string.IsNullOrEmpty(module.description))
if (!searchIncompatible)
{
return registry
.Available(ksp.VersionCriteria())
.Where((module) =>
{
modDesc = module.description;
}
// Look for a match in each string.
return module.name.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1 || module.identifier.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1 || modDesc.IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1;
}).ToList();
// 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
.Incompatible(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();
}
}

/// <summary>
Expand Down
12 changes: 11 additions & 1 deletion Cmdline/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,17 @@ internal class ShowOptions : InstanceSpecificOptions

internal class SearchOptions : InstanceSpecificOptions
{
[ValueOption(0)] public string search_term { get; set; }
[Option("detail", HelpText = "Show full name, latest compatible version and short description of each module")]
public bool detail { get; set; }

[Option("all", HelpText = "Show incompatible mods too")]
public bool all { get; set; }

[Option("author", HelpText = "Limit search results to mods by matching authors")]
public string author_term { get; set; }

[ValueOption(0)]
public string search_term { get; set; }
}

internal class CompareOptions : CommonOptions
Expand Down
23 changes: 13 additions & 10 deletions ConsoleUI/ModListScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using CKAN.ConsoleUI.Toolkit;
using System.Linq;

namespace CKAN.ConsoleUI {

Expand Down Expand Up @@ -48,18 +49,17 @@ public ModListScreen(KSPManager mgr, bool dbg)
},
1, 0, ListSortDirection.Descending,
(CkanModule m, string filter) => {
// Search for author
if (filter.StartsWith("@")) {
string authorFilt = filter.Substring(1);
if (string.IsNullOrEmpty(authorFilt)) {
return true;
} else if (m.author != null) {
foreach (string auth in m.author) {
if (auth.IndexOf(authorFilt, StringComparison.CurrentCultureIgnoreCase) == 0) {
return true;
}
}
} else {
// Remove special characters from search term
authorFilt = CkanModule.nonAlphaNums.Replace(authorFilt, "");
return m.SearchableAuthors.Any((author) => author.IndexOf(authorFilt, StringComparison.CurrentCultureIgnoreCase) == 0);
}
return false;
// Other special search params: installed, updatable, new, conflicting and dependends
} else if (filter.StartsWith("~")) {
if (filter.Length <= 1) {
// Don't blank the list for just "~" by itself
Expand Down Expand Up @@ -97,9 +97,12 @@ public ModListScreen(KSPManager mgr, bool dbg)
}
return false;
} else {
return m.identifier.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.name.IndexOf( filter, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.@abstract.IndexOf( filter, StringComparison.CurrentCultureIgnoreCase) >= 0;
filter = CkanModule.nonAlphaNums.Replace(filter, "");
return m.SearchableIdentifier.IndexOf( filter, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.SearchableName.IndexOf( filter, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.SearchableAbstract.IndexOf( filter, StringComparison.CurrentCultureIgnoreCase) >= 0
|| m.SearchableDescription.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase) >= 0;
}
}
);
Expand Down
3 changes: 2 additions & 1 deletion Core/Registry/AvailableModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ private AvailableModule()
}

[OnDeserialized]
internal void SetIdentifier(StreamingContext context)
internal void DeserialisationFixes(StreamingContext context)
{
// Set identifier
var mod = module_version.Values.LastOrDefault();
identifier = mod.identifier;
Debug.Assert(module_version.Values.All(m=>identifier.Equals(m.identifier)));
Expand Down
45 changes: 44 additions & 1 deletion Core/Types/CkanModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,21 @@ public List<string> ProvidesList
}
}

// These are used to simplify the search by dropping special chars.
[JsonIgnore]
public string SearchableName;
[JsonIgnore]
public string SearchableIdentifier;
[JsonIgnore]
public string SearchableAbstract;
[JsonIgnore]
public string SearchableDescription;
[JsonIgnore]
public List<string> SearchableAuthors;
// This regex finds all those special chars.
[JsonIgnore]
public static readonly Regex nonAlphaNums = new Regex("[^a-zA-Z0-9]", RegexOptions.Compiled);

#endregion

#region Constructors
Expand Down Expand Up @@ -527,7 +542,6 @@ public CkanModule(string json, IGameComparator comparator)

// Check everything in the spec is defined.
// TODO: This *can* and *should* be done with JSON attributes!

foreach (string field in required_fields)
{
object value = null;
Expand All @@ -552,6 +566,33 @@ public CkanModule(string json, IGameComparator comparator)
throw new BadMetadataKraken(null, error);
}
}

// Calculate the Searchables.
CalculateSearchables();
}

/// <summary>
/// Calculate the mod properties used for searching via Regex.
/// </summary>
public void CalculateSearchables()
{
SearchableIdentifier = identifier == null ? string.Empty : CkanModule.nonAlphaNums.Replace(identifier, "");
SearchableName = name == null ? string.Empty : CkanModule.nonAlphaNums.Replace(name, "");
SearchableAbstract = @abstract == null ? string.Empty : CkanModule.nonAlphaNums.Replace(@abstract, "");
SearchableDescription = description == null ? string.Empty : CkanModule.nonAlphaNums.Replace(description, "");
SearchableAuthors = new List<string>();

if (author == null)
{
SearchableAuthors.Add(string.Empty);
}
else
{
foreach (string auth in author)
{
SearchableAuthors.Add(CkanModule.nonAlphaNums.Replace(auth, ""));
}
}
}

public string serialise()
Expand All @@ -575,6 +616,8 @@ private void DeSerialisationFixes(StreamingContext like_i_could_care)
license = license ?? new List<License> { License.UnknownLicense };
@abstract = @abstract ?? string.Empty;
name = name ?? string.Empty;

CalculateSearchables();
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion GUI/CKAN-GUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@
<SubType>Component</SubType>
</Compile>
<Compile Include="SelectionDialog.cs">
<Subtype>Form</Subtype>
<SubType>Form</SubType>
</Compile>
<Compile Include="SelectionDialog.Designer.cs">
<DependentUpon>SelectionDialog.cs</DependentUpon>
Expand Down
Loading

0 comments on commit 44d44ea

Please sign in to comment.