diff --git a/Cmdline/Action/Prompt.cs b/Cmdline/Action/Prompt.cs
index b9cd803636..d3f287cb6f 100644
--- a/Cmdline/Action/Prompt.cs
+++ b/Cmdline/Action/Prompt.cs
@@ -2,6 +2,7 @@
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
+using System.Text.RegularExpressions;
using CommandLine;
using CommandLine.Text;
@@ -52,7 +53,8 @@ public int RunCommand(object raw_options)
{
// Parse input as if it was a normal command line,
// but with a persistent GameInstanceManager object.
- int cmdExitCode = MainClass.Execute(manager, opts, command.Split(' '));
+ int cmdExitCode = MainClass.Execute(manager, opts,
+ ParseTextField(command));
// Clear the command if no exception was thrown
if (headless && cmdExitCode != Exit.OK)
{
@@ -70,7 +72,27 @@ public int RunCommand(object raw_options)
return Exit.OK;
}
- private string ReadLineWithCompletion(bool headless)
+ ///
+ /// Split string on spaces, unless they are between quotes.
+ /// Inspired by https://stackoverflow.com/a/14655145/2422988
+ ///
+ /// The string to parse
+ /// Array split by strings, with quoted parts joined together
+ private static string[] ParseTextField(string input)
+ => quotePattern.Matches(input)
+ .Cast()
+ .Select(m => m.Value)
+ .ToArray();
+
+ ///
+ /// Look for non-quotes surrounded by quotes, or non-space-or-quotes, or end preceded by space.
+ /// No attempt to allow escaped quotes within quotes.
+ /// Inspired by https://stackoverflow.com/a/14655145/2422988
+ ///
+ private static readonly Regex quotePattern = new Regex(
+ @"(?<="")[^""]*(?="")|[^ ""]+|(?<= )$", RegexOptions.Compiled);
+
+ private static string ReadLineWithCompletion(bool headless)
{
try
{
@@ -87,7 +109,7 @@ private string ReadLineWithCompletion(bool headless)
private string[] GetSuggestions(string text, int index)
{
- string[] pieces = text.Split(new char[] { ' ' });
+ string[] pieces = ParseTextField(text);
TypeInfo ti = typeof(Actions).GetTypeInfo();
List extras = new List { exitCommand, "help" };
foreach (string piece in pieces.Take(pieces.Length - 1))
@@ -103,88 +125,99 @@ private string[] GetSuggestions(string text, int index)
extras.Clear();
}
var lastPiece = pieces.LastOrDefault() ?? "";
- return lastPiece.StartsWith("--") ? GetOptions(ti, lastPiece.Substring(2))
- : HasVerbs(ti) ? GetVerbs(ti, lastPiece, extras)
- : WantsAvailIdentifiers(ti) ? GetAvailIdentifiers(lastPiece)
- : WantsInstIdentifiers(ti) ? GetInstIdentifiers(lastPiece)
- : WantsGameInstances(ti) ? GetGameInstances(lastPiece)
- : null;
+ return lastPiece.StartsWith("--") ? GetLongOptions(ti, lastPiece.Substring(2))
+ : lastPiece.StartsWith("-") ? GetShortOptions(ti, lastPiece.Substring(1))
+ : HasVerbs(ti) ? GetVerbs(ti, lastPiece, extras)
+ : WantsAvailIdentifiers(ti) ? GetAvailIdentifiers(lastPiece)
+ : WantsInstIdentifiers(ti) ? GetInstIdentifiers(lastPiece)
+ : WantsGameInstances(ti) ? GetGameInstances(lastPiece)
+ : null;
}
- private string[] GetOptions(TypeInfo ti, string prefix)
- {
- return ti.DeclaredProperties
- .Select(p => p.GetCustomAttribute()?.LongName)
+ private static string[] GetLongOptions(TypeInfo ti, string prefix)
+ => AllBaseTypes(ti.AsType())
+ .SelectMany(t => t.GetTypeInfo().DeclaredProperties)
+ .Select(p => p.GetCustomAttribute()?.LongName
+ ?? p.GetCustomAttribute()?.LongName
+ ?? p.GetCustomAttribute()?.LongName)
.Where(o => o != null && o.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase))
.OrderBy(o => o)
.Select(o => $"--{o}")
.ToArray();
- }
-
- private bool HasVerbs(TypeInfo ti)
- {
- return ti.DeclaredProperties
- .Any(p => p.GetCustomAttribute() != null);
- }
- private string[] GetVerbs(TypeInfo ti, string prefix, IEnumerable extras)
- {
- return ti.DeclaredProperties
- .Select(p => p.GetCustomAttribute()?.LongName)
- .Where(v => v != null)
- .Concat(extras)
- .Where(v => v.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase))
- .OrderBy(v => v)
+ private static string[] GetShortOptions(TypeInfo ti, string prefix)
+ => AllBaseTypes(ti.AsType())
+ .SelectMany(t => t.GetTypeInfo().DeclaredProperties)
+ .Select(p => p.GetCustomAttribute()?.ShortName
+ ?? p.GetCustomAttribute()?.ShortName
+ ?? p.GetCustomAttribute()?.ShortName)
+ .Where(o => o != null && $"{o}".StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase))
+ .OrderBy(o => o)
+ .Select(o => $"-{o}")
.ToArray();
- }
- private bool WantsAvailIdentifiers(TypeInfo ti)
+ private static IEnumerable AllBaseTypes(Type start)
{
- return ti.DeclaredProperties
- .Any(p => p.GetCustomAttribute() != null);
+ for (Type t = start; t != null; t = t.BaseType)
+ {
+ yield return t;
+ }
}
+ private static bool HasVerbs(TypeInfo ti)
+ => ti.DeclaredProperties
+ .Any(p => p.GetCustomAttribute() != null);
+
+ private static string[] GetVerbs(TypeInfo ti, string prefix, IEnumerable extras)
+ => ti.DeclaredProperties
+ .Select(p => p.GetCustomAttribute()?.LongName)
+ .Where(v => v != null)
+ .Concat(extras)
+ .Where(v => v.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase))
+ .OrderBy(v => v)
+ .ToArray();
+
+ private static bool WantsAvailIdentifiers(TypeInfo ti)
+ => ti.DeclaredProperties
+ .Any(p => p.GetCustomAttribute() != null);
+
private string[] GetAvailIdentifiers(string prefix)
{
CKAN.GameInstance inst = MainClass.GetGameInstance(manager);
- return RegistryManager.Instance(inst).registry
- .CompatibleModules(inst.VersionCriteria())
- .Where(m => !m.IsDLC)
- .Select(m => m.identifier)
- .Where(ident => ident.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase))
- .ToArray();
+ return RegistryManager.Instance(inst)
+ .registry
+ .CompatibleModules(inst.VersionCriteria())
+ .Where(m => !m.IsDLC)
+ .Select(m => m.identifier)
+ .Where(ident => ident.StartsWith(prefix,
+ StringComparison.InvariantCultureIgnoreCase))
+ .ToArray();
}
- private bool WantsInstIdentifiers(TypeInfo ti)
- {
- return ti.DeclaredProperties
- .Any(p => p.GetCustomAttribute() != null);
- }
+ private static bool WantsInstIdentifiers(TypeInfo ti)
+ => ti.DeclaredProperties
+ .Any(p => p.GetCustomAttribute() != null);
private string[] GetInstIdentifiers(string prefix)
{
CKAN.GameInstance inst = MainClass.GetGameInstance(manager);
var registry = RegistryManager.Instance(inst).registry;
return registry.Installed(false, false)
- .Select(kvp => kvp.Key)
- .Where(ident => ident.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)
- && !registry.GetInstalledVersion(ident).IsDLC)
- .ToArray();
+ .Select(kvp => kvp.Key)
+ .Where(ident => ident.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)
+ && !registry.GetInstalledVersion(ident).IsDLC)
+ .ToArray();
}
- private bool WantsGameInstances(TypeInfo ti)
- {
- return ti.DeclaredProperties
- .Any(p => p.GetCustomAttribute() != null);
- }
+ private static bool WantsGameInstances(TypeInfo ti)
+ => ti.DeclaredProperties
+ .Any(p => p.GetCustomAttribute() != null);
private string[] GetGameInstances(string prefix)
- {
- return manager.Instances
- .Select(kvp => kvp.Key)
- .Where(ident => ident.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase))
- .ToArray();
- }
+ => manager.Instances
+ .Select(kvp => kvp.Key)
+ .Where(ident => ident.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase))
+ .ToArray();
private readonly GameInstanceManager manager;
private const string exitCommand = "exit";
diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs
index a7b3164e2a..11e89f0a52 100644
--- a/Cmdline/Options.cs
+++ b/Cmdline/Options.cs
@@ -4,6 +4,7 @@
using System.Reflection;
using System.Collections.Generic;
using System.Text.RegularExpressions;
+
using log4net;
using log4net.Core;
using CommandLine;
diff --git a/Core/Meta.cs b/Core/Meta.cs
index c4d8a620d8..478d655b39 100644
--- a/Core/Meta.cs
+++ b/Core/Meta.cs
@@ -23,32 +23,34 @@ public static string GetVersion(VersionFormat format = VersionFormat.Normal)
.GetAssemblyAttribute()
.InformationalVersion;
- var dashIndex = version.IndexOf('-');
- var plusIndex = version.IndexOf('+');
-
switch (format)
{
case VersionFormat.Short:
- if (dashIndex >= 0)
- version = version.Substring(0, dashIndex);
- else if (plusIndex >= 0)
- version = version.Substring(0, plusIndex);
-
+ return $"v{version.UpToCharacters(shortDelimiters)}";
break;
case VersionFormat.Normal:
- if (plusIndex >= 0)
- version = version.Substring(0, plusIndex);
-
+ return $"v{version.UpToCharacter('+')}";
break;
case VersionFormat.Full:
+ return $"v{version}";
break;
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
-
- return "v" + version;
}
+ private static readonly char[] shortDelimiters = new char[] { '-', '+' };
+
+ private static string UpToCharacter(this string orig, char what)
+ => orig.UpToIndex(orig.IndexOf(what));
+
+ private static string UpToCharacters(this string orig, char[] what)
+ => orig.UpToIndex(orig.IndexOfAny(what));
+
+ private static string UpToIndex(this string orig, int index)
+ => index == -1 ? orig
+ : orig.Substring(0, index);
+
private static T GetAssemblyAttribute(this Assembly assembly)
=> (T)assembly.GetCustomAttributes(typeof(T), false)
.First();
diff --git a/Core/Meta.cs.in b/Core/Meta.cs.in
deleted file mode 100644
index 26a4da002a..0000000000
--- a/Core/Meta.cs.in
+++ /dev/null
@@ -1,70 +0,0 @@
-using System.Text.RegularExpressions;
-
-namespace CKAN
-{
- public static class Meta
- {
- public readonly static string Development = "development";
-
- // Do *not* change the following line, BUILD_VERSION is
- // replaced by our build system with our actual version.
-
- private readonly static string BUILD_VERSION = <%version%>;
-
- ///
- /// Returns the version of the CKAN.dll used, complete with git info
- /// and other decorations as filled in by our build system.
- /// Eg: v1.3.5-12-g055d7c3 (beta) or "development (unstable)"
- ///
- public static string Version()
- {
- string version = BuildVersion();
-
- #if (STABLE)
- version += " (stable)";
- #else
- version += " (beta)";
- #endif
-
- return version;
- }
-
- ///
- /// Returns only the build info, with no decorations, or "development" if
- /// unknown.
- ///
- public static string BuildVersion()
- {
- return BUILD_VERSION ?? Development;
- }
-
- ///
- /// Returns just our release number (eg: 1.0.3), or null for a dev build.
- ///
- public static Version ReleaseNumber()
- {
- string build_version = BuildVersion();
-
- if (build_version == Development)
- {
- return null;
- }
-
- string short_version = Regex.Match(build_version, @"^(.*)-\d+-.*$").Result("$1");
-
- return new Version(short_version);
- }
-
- ///
- /// Returns true if this is a 'stable' build, false otherwise.
- ///
- public static bool IsStable()
- {
- #if (STABLE)
- return true;
- #else
- return false;
- #endif
- }
- }
-}