Skip to content

Commit

Permalink
almost got everything working now with help texts and whatnot
Browse files Browse the repository at this point in the history
  • Loading branch information
mookid8000 committed Oct 4, 2015
1 parent e3a431f commit bf31e1b
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 15 deletions.
19 changes: 19 additions & 0 deletions GoCommando/DescriptionAttribute.cs
@@ -0,0 +1,19 @@
using System;

namespace GoCommando
{
/// <summary>
/// Apply this attribute to a property of a command class (which is also decorated with <see cref="ParameterAttribute"/>) in
/// order to provide a description of the parameter
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class DescriptionAttribute : Attribute
{
public string DescriptionText { get; }

public DescriptionAttribute(string descriptionText)
{
DescriptionText = descriptionText;
}
}
}
18 changes: 18 additions & 0 deletions GoCommando/ExampleAttribute.cs
@@ -0,0 +1,18 @@
using System;

namespace GoCommando
{
/// <summary>
/// Apply one or more of these to a command property to show examples on how this particular parameter can be used
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class ExampleAttribute : Attribute
{
public string ExampleValue { get; }

public ExampleAttribute(string exampleValue)
{
ExampleValue = exampleValue;
}
}
}
23 changes: 23 additions & 0 deletions GoCommando/ExitCodeException.cs
@@ -0,0 +1,23 @@
using System;
using System.Runtime.Serialization;

namespace GoCommando
{
/// <summary>
/// Exception that can be used to exit the program with a custom exit code
/// </summary>
[Serializable]
public class CustomExitCodeException : Exception
{
protected CustomExitCodeException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}

public CustomExitCodeException(int exitCode, string message) : base(message)
{
ExitCode = exitCode;
}

public int ExitCode { get; }
}
}
146 changes: 140 additions & 6 deletions GoCommando/Go.cs
Expand Up @@ -32,21 +32,97 @@ public static void Run()
{
Environment.ExitCode = -1;
Console.WriteLine(friendlyException.Message);
Console.WriteLine();
Console.WriteLine("Invoke with -? or -help to get help.");
Console.WriteLine();
Console.WriteLine("Exit code: -1");
Console.WriteLine();
}
catch (CustomExitCodeException customExitCodeException)
{
FailAndExit(customExitCodeException, customExitCodeException.ExitCode);
}
catch (Exception exception)
{
Environment.ExitCode = -2;
Console.Error.WriteLine(exception);
FailAndExit(exception, -2);
}
}

static void FailAndExit(Exception customExitCodeException, int exitCode)
{
Console.WriteLine();
Console.Error.WriteLine(customExitCodeException);
Console.WriteLine();
Console.WriteLine("Exit code: {0}", exitCode);
Console.WriteLine();

Environment.ExitCode = exitCode;
}

static void InnerRun()
{
var args = Environment.GetCommandLineArgs().Skip(1).ToList();
var settings = new Settings();
var arguments = Parse(args, settings);
var commandTypes = GetCommands(settings);

var helpSwitch = arguments.Switches.FirstOrDefault(s => s.Key == "?")
?? arguments.Switches.FirstOrDefault(s => s.Key == "help");

if (helpSwitch != null)
{
var exe = Assembly.GetEntryAssembly().GetName().Name + ".exe";

if (helpSwitch.Value != null)
{
var command = commandTypes.FirstOrDefault(c => c.Command == helpSwitch.Value);

if (command != null)
{
if (command.Parameters.Any())
{
Console.WriteLine(@"Type
{0} {1} <args>
where <args> can consist of the following parameters:
{2}",
exe,
command.Command,
string.Join(Environment.NewLine, command.Parameters.Select(parameter => FormatParameter(parameter, settings))));
}
else
{
Console.WriteLine(@"Type
{0} {1}
", exe, command.Command);
}
return;
}
else
{
Console.WriteLine("Unknown command '{0}'", helpSwitch.Value);
}
}

var availableCommands = string.Join(Environment.NewLine, commandTypes.Select(c => " " + c.Command));

Console.WriteLine($@"The following commands are available:
{availableCommands}
Type
{exe} -help <command>
to get help for a command.
");
return;
}

var commandToRun = commandTypes.FirstOrDefault(c => c.Command == arguments.Command);

if (commandToRun == null)
Expand All @@ -61,6 +137,52 @@ static void InnerRun()
commandToRun.Invoke(arguments.Switches);
}

static string FormatParameter(Parameter parameter, Settings settings)
{
var shorthand = parameter.Shortname != null
? $" / {settings.SwitchPrefix}{parameter.Shortname}"
: "";

var additionalProperties = new List<string>();

var isFlag = parameter.IsFlag;

if (isFlag)
{
additionalProperties.Add("flag");
}

if (parameter.Optional)
{
additionalProperties.Add("optional");
}

var additionalPropertiesText = additionalProperties.Any() ? $" ({string.Join("/", additionalProperties)})" : "";

var helpText = parameter.DescriptionText ?? "";

var examplesText = !parameter.ExampleValues.Any()
? ""
: FormatExamples(parameter, settings);

return $@" {settings.SwitchPrefix}{parameter.Name}{shorthand}{additionalPropertiesText}
{helpText}
{examplesText}
";
}

static string FormatExamples(Parameter parameter, Settings settings)
{
var examples = string.Join(Environment.NewLine, parameter.ExampleValues
.Select(e => $" {settings.SwitchPrefix}{parameter.Name} {e}"));

return $@"
Examples:
{examples}";
}

internal static List<CommandInvoker> GetCommands(Settings settings)
{
return Assembly.GetEntryAssembly().GetTypes()
Expand All @@ -77,18 +199,30 @@ internal static List<CommandInvoker> GetCommands(Settings settings)
internal static Arguments Parse(IEnumerable<string> args, Settings settings)
{
var list = args.ToList();
var command = list.First();

if (command.StartsWith(settings.SwitchPrefix))
if (!list.Any()) return new Arguments(null, Enumerable.Empty<Switch>(), settings);

var first = list.First();

string command;
List<string> switchArgs;

if (first.StartsWith(settings.SwitchPrefix))
{
command = null;
switchArgs = list;
}
else
{
throw new GoCommandoException($"Invalid command: '{command}' - the command must not start with the switch prefix '{settings.SwitchPrefix}'");
command = first;
switchArgs = list.Skip(1).ToList();
}

var switches = new List<Switch>();

string key = null;

foreach (var arg in args.Skip(1))
foreach (var arg in switchArgs)
{
if (arg.StartsWith(settings.SwitchPrefix))
{
Expand Down
3 changes: 3 additions & 0 deletions GoCommando/GoCommando.csproj
Expand Up @@ -40,6 +40,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DescriptionAttribute.cs" />
<Compile Include="ExampleAttribute.cs" />
<Compile Include="ExitCodeException.cs" />
<Compile Include="ICommand.cs" />
<Compile Include="Internals\Arguments.cs" />
<Compile Include="BannerAttribute.cs" />
Expand Down
25 changes: 19 additions & 6 deletions GoCommando/Internals/Command.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace GoCommando.Internals
{
Expand All @@ -12,7 +13,7 @@ public CommandInvoker(string command, Type type, Settings settings)
{
_settings = settings;

if (!typeof (ICommand).IsAssignableFrom(type))
if (!typeof(ICommand).IsAssignableFrom(type))
{
throw new ApplicationException($"Command tyep {type} does not implement {typeof(ICommand)} as it should!");
}
Expand All @@ -29,15 +30,27 @@ IEnumerable<Parameter> GetParameters(Type type)
.Select(p => new
{
Property = p,
Attribute = p.GetCustomAttributes(typeof (ParameterAttribute), false)
.Cast<ParameterAttribute>()
.FirstOrDefault()
ParameterAttribute = GetSingleAttributeOrNull<ParameterAttribute>(p),
DescriptionAttribute = GetSingleAttributeOrNull<DescriptionAttribute>(p),
ExampleAttributes = p.GetCustomAttributes<ExampleAttribute>()
})
.Where(a => a.Attribute != null)
.Select(a => new Parameter(a.Property, a.Attribute.Name, a.Attribute.ShortName, a.Attribute.Optional))
.Where(a => a.ParameterAttribute != null)
.Select(a => new Parameter(a.Property,
a.ParameterAttribute.Name,
a.ParameterAttribute.ShortName,
a.ParameterAttribute.Optional,
a.DescriptionAttribute?.DescriptionText,
a.ExampleAttributes.Select(e => e.ExampleValue)))
.ToList();
}

static TAttribute GetSingleAttributeOrNull<TAttribute>(PropertyInfo p) where TAttribute : Attribute
{
return p.GetCustomAttributes(typeof(TAttribute), false)
.Cast<TAttribute>()
.FirstOrDefault();
}

public string Command { get; }
public Type Type { get; }
public IEnumerable<Parameter> Parameters { get; }
Expand Down
11 changes: 10 additions & 1 deletion GoCommando/Internals/Parameter.cs
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace GoCommando.Internals
Expand All @@ -9,15 +11,22 @@ class Parameter
public string Name { get; }
public string Shortname { get; }
public bool Optional { get; }
public string DescriptionText { get; }
public string[] ExampleValues { get; }

public Parameter(PropertyInfo propertyInfo, string name, string shortname, bool optional)
public bool IsFlag => PropertyInfo.PropertyType == typeof (bool);

public Parameter(PropertyInfo propertyInfo, string name, string shortname, bool optional, string descriptionText, IEnumerable<string> exampleValues)
{
PropertyInfo = propertyInfo;
Name = name;
Shortname = shortname;
Optional = optional;
DescriptionText = descriptionText;
ExampleValues = exampleValues.ToArray();
}


public bool MatchesKey(string key)
{
return key == Name
Expand Down
9 changes: 7 additions & 2 deletions TestApp/Commands/RunCommand.cs
Expand Up @@ -6,16 +6,21 @@ namespace TestApp.Commands
[Command("run")]
public class RunCommand : ICommand
{
[Parameter("path")]
[Description("Specifies the path with which stuff is to be done")]
[Parameter("path", shortName: "p")]
[Example(@"c:\temp\somefile.json")]
public string Path { get; set; }

[Parameter("dir")]
[Example(@"C:\temp")]
[Example(@"C:\windows\microsoft.net")]
[Example(@"C:\windows\system32")]
public string Dir { get; set; }

[Parameter("flag")]
public bool Flag { get; set; }

[Parameter("moreflag")]
[Parameter("moreflag", shortName: "m")]
public bool MoreFlag { get; set; }

[Parameter("optionalFlag", optional: true)]
Expand Down

0 comments on commit bf31e1b

Please sign in to comment.