Skip to content

Commit

Permalink
Verb attribute help text (#47)
Browse files Browse the repository at this point in the history
* Add helpText to VerbOptionAttribute

* Add WriteVerbsUsage and fix error with certain properties

* Modify tests

* Code Review
  • Loading branch information
geoperez committed Nov 28, 2017
1 parent 891073b commit 48c1a82
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 77 deletions.
28 changes: 25 additions & 3 deletions src/Unosquare.Swan/Attributes/VerbOptionAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
using System;

namespace Unosquare.Swan.Attributes
namespace Unosquare.Swan.Attributes
{
using System;

/// <summary>
/// Models a verb option
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class VerbOptionAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="VerbOptionAttribute" /> class.
/// </summary>
/// <param name="name">The name.</param>
/// <exception cref="ArgumentNullException">name</exception>
public VerbOptionAttribute(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}

/// <summary>
/// Gets the name of the verb option.
/// </summary>
/// <value>
/// Name.
/// </value>
public string Name { get; }

/// <summary>
/// Gets or sets a short description of this command line verb. Usually a sentence summary.
/// </summary>
/// <value>
/// The help text.
/// </value>
public string HelpText { get; set; }
}
}
145 changes: 88 additions & 57 deletions src/Unosquare.Swan/Components/ArgumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public bool ParseArguments<T>(IEnumerable<string> args, T instance)
x.GetCustomAttribute<VerbOptionAttribute>().Name.Equals(args.ToArray()[0]));
if (selectedVerb == null)
{
"No verb was specified".WriteLine();
WriteVerbsUsage(properties);
return false;
}

Expand All @@ -91,64 +91,9 @@ public bool ParseArguments<T>(IEnumerable<string> args, T instance)
if (properties.Any() == false)
throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");

var unknownList = new List<string>();
var requiredList = new List<string>();
var updatedList = new List<PropertyInfo>();
var propertyName = string.Empty;

foreach (var arg in args)
{
if (string.IsNullOrWhiteSpace(propertyName) == false)
{
var targetProperty = TryGetProperty(properties, propertyName);

// Skip if the property is not found
if (targetProperty == null)
{
unknownList.Add(propertyName);
propertyName = string.Empty;
continue;
}

if (SetPropertyValue(targetProperty, arg, instance))
updatedList.Add(targetProperty);

propertyName = string.Empty;
}
else
{
if (string.IsNullOrWhiteSpace(arg) || arg[0] != Dash) continue;

propertyName = arg.Substring(1);
if (propertyName[0] == Dash) propertyName = propertyName.Substring(1);

var targetProperty = TryGetProperty(properties, propertyName);

// If the arg is a boolean property set it to true.
if (targetProperty == null || targetProperty.PropertyType != typeof(bool)) continue;

if (string.IsNullOrEmpty(verbName))
{
if (SetPropertyValue(targetProperty, true.ToString(), instance))
updatedList.Add(targetProperty);
}
else
{
var property = instance.GetType().GetProperty(verbName);
instance.GetType().GetProperty(verbName);

if (SetPropertyValue(targetProperty, true.ToString(), property.GetValue(instance, null)))
updatedList.Add(targetProperty);
}

propertyName = string.Empty;
}
}

if (string.IsNullOrEmpty(propertyName) == false)
{
unknownList.Add(propertyName);
}
var unknownList = PopulateInstance(args, instance, properties, verbName, updatedList);

foreach (var targetProperty in properties.Except(updatedList))
{
Expand Down Expand Up @@ -212,9 +157,95 @@ public bool ParseArguments<T>(IEnumerable<string> args, T instance)
return false;
}

private List<string> PopulateInstance<T>(IEnumerable<string> args, T instance, PropertyInfo[] properties, string verbName, List<PropertyInfo> updatedList)
{
var unknownList = new List<string>();
var propertyName = string.Empty;

foreach (var arg in args)
{
if (string.IsNullOrWhiteSpace(propertyName) == false)
{
var targetProperty = TryGetProperty(properties, propertyName);

// Skip if the property is not found
if (targetProperty == null)
{
unknownList.Add(propertyName);
propertyName = string.Empty;
continue;
}

if (string.IsNullOrEmpty(verbName))
{
if (SetPropertyValue(targetProperty, arg, instance))
updatedList.Add(targetProperty);
}
else
{
var property = instance.GetType().GetProperty(verbName);
instance.GetType().GetProperty(verbName);

if (SetPropertyValue(targetProperty, arg, property.GetValue(instance, null)))
updatedList.Add(targetProperty);
}

propertyName = string.Empty;
}
else
{
if (string.IsNullOrWhiteSpace(arg) || arg[0] != Dash) continue;

propertyName = arg.Substring(1);
if (propertyName[0] == Dash) propertyName = propertyName.Substring(1);

var targetProperty = TryGetProperty(properties, propertyName);

// If the arg is a boolean property set it to true.
if (targetProperty == null || targetProperty.PropertyType != typeof(bool)) continue;

if (string.IsNullOrEmpty(verbName))
{
if (SetPropertyValue(targetProperty, true.ToString(), instance))
updatedList.Add(targetProperty);
}
else
{
var property = instance.GetType().GetProperty(verbName);
instance.GetType().GetProperty(verbName);

if (SetPropertyValue(targetProperty, true.ToString(), property.GetValue(instance, null)))
updatedList.Add(targetProperty);
}

propertyName = string.Empty;
}
}

if (string.IsNullOrEmpty(propertyName) == false)
{
unknownList.Add(propertyName);
}

return unknownList;
}

private static IEnumerable<PropertyInfo> GetTypeProperties(Type type)
=> Runtime.PropertyTypeCache.Value.Retrieve(type, PropertyTypeCache.GetAllPublicPropertiesFunc(type));

private static void WriteVerbsUsage( IEnumerable<PropertyInfo> verbs)
{
var options = verbs.Select(p => p.GetCustomAttribute<VerbOptionAttribute>())
.Where(x => x != null);

foreach (var option in options)
{
string.Empty.WriteLine();

$" {option.Name}\t\t{option.HelpText}".WriteLine(ConsoleColor.Cyan);
}
}

private static void WriteUsage(IEnumerable<PropertyInfo> properties)
{
var options = properties.Select(p => p.GetCustomAttribute<ArgumentOptionAttribute>())
Expand Down
2 changes: 1 addition & 1 deletion src/Unosquare.Swan/Unosquare.Swan.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PackageId>Unosquare.Swan</PackageId>
<CodeAnalysisRuleSet>..\..\StyleCop.Analyzers.ruleset</CodeAnalysisRuleSet>
<DebugType>Full</DebugType>
<Version>0.20.0</Version>
<Version>0.21.0</Version>
<Authors>Unosquare</Authors>
<PackageIconUrl>https://github.com/unosquare/swan/raw/master/swan-logo-32.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/unosquare/swan</PackageProjectUrl>
Expand Down
7 changes: 3 additions & 4 deletions test/Unosquare.Swan.Test/ArgumentParserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ public void InstanceNull_ThrowsArgumentNullException()
Assert.Throws<ArgumentNullException>(() =>
Runtime.ArgumentParser.ParseArguments<OptionMock>(DefaultStringList, null));
}

}

[TestFixture]
Expand All @@ -157,12 +156,12 @@ public void BasicVerbParsing_ReturnsTrue()
public void BasicVerbParsing_InstantiatesSelectedVerbOptionProperty()
{
var verbOptions = new CliVerbs();
var arguments = new string[] { "monitor", "-v" };
var arguments = new string[] { "verb", "-u", "user", "--host", "129.168.1.1", "-p", "5556" };
var expected = Runtime.ArgumentParser.ParseArguments(arguments, verbOptions);

Assert.AreEqual(expected, true);
Assert.IsNotNull(verbOptions.MonitorVerboptions);
Assert.IsNull(verbOptions.PushVerbOptions);
Assert.IsNull(verbOptions.MonitorVerboptions);
Assert.IsNotNull(verbOptions.PushVerbOptions);
}

[Test]
Expand Down
49 changes: 37 additions & 12 deletions test/Unosquare.Swan.Test/Mocks/VerbOptionMock.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unosquare.Swan.Attributes;

namespace Unosquare.Swan.Test.Mocks
namespace Unosquare.Swan.Test.Mocks
{
using System;
using Unosquare.Swan.Attributes;

public class CliVerbs
{
[VerbOption("verb")]
Expand All @@ -17,14 +13,43 @@ public class CliVerbs

public class VerbOptions
{
[ArgumentOption('v', "verbose", HelpText = "Set verbose mode.")]
[ArgumentOption('v', "verbose", DefaultValue = true, HelpText = "Add this option to print messages to standard error and standard output streams. 0 to disable, any other number to enable.", Required = false)]
public bool Verbose { get; set; }

[ArgumentOption("color", DefaultValue = ConsoleColor.Red, HelpText = "Set background color.")]
public ConsoleColor BgColor { get; set; }
[ArgumentOption('h', "host", DefaultValue = 1, HelpText = "Hostname or IP Address of the target. -- Must be running an SSH server.", Required = true)]
public string Host { get; set; }

[ArgumentOption('p', "port", DefaultValue = 22, HelpText = "Port on which SSH is running..")]
public int Port { get; set; }

[ArgumentOption('n', HelpText = "Set user name.")]
[ArgumentOption('u', "username", DefaultValue = "pi", HelpText = "The username under which the connection will be established.")]
public string Username { get; set; }

[ArgumentOption('w', "password", DefaultValue = "raspberry", HelpText = "The password for the given username.", Required = false)]
public string Password { get; set; }
[ArgumentOption("pre", HelpText = "Command to execute prior file transfer to target", Required = false)]
public string PreCommand { get; set; }

[ArgumentOption("post", HelpText = "Command to execute after file transfer to target", Required = false)]
public string PostCommand { get; set; }

[ArgumentOption("clean", DefaultValue = false, HelpText = "Deletes all files and folders on the target before pushing the new files. 0 to disable, any other number to enable.", Required = false)]
public bool CleanTarget { get; set; }

[ArgumentOption("exclude", DefaultValue = ".ready|.vshost.exe|.vshost.exe.config", HelpText = "a pipe (|) separated list of file suffixes to ignore while deploying.", Required = false)]
public string ExcludeFileSuffixes { get; set; }

public string[] ExcludeFileSuffixList
{
get
{
var ignoreFileSuffixes = string.IsNullOrWhiteSpace(ExcludeFileSuffixes) ?
new string[] { } :
ExcludeFileSuffixes.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);

return ignoreFileSuffixes;
}
}
}

public class MonitorOptions
Expand Down

0 comments on commit 48c1a82

Please sign in to comment.