Adding support for "CommandHelp" attribute

Also changed "Output" and "Input" to be TextReader instead of string

Fix unit tests to work with new command descriptor semantics

--HG--
branch : dev
rename : src/Orchard/Commands/CommandManager.cs => src/Orchard/Commands/DefaultCommandManager.cs
This commit is contained in:
Renaud Paquay
2010-04-12 15:28:40 -07:00
parent ba7e5613f8
commit aab562be03
16 changed files with 188 additions and 93 deletions

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using NUnit.Framework;
using Orchard.Commands;
using System;
using System.Linq;
namespace Orchard.Tests.Commands {
[TestFixture]
@@ -14,141 +16,152 @@ namespace Orchard.Tests.Commands {
_handler = new StubCommandHandler();
}
private CommandContext CreateCommandContext(string commandName) {
return CreateCommandContext(commandName, new Dictionary<string, string>(), new string[]{});
}
private CommandContext CreateCommandContext(string commandName, IDictionary<string, string> switches) {
return CreateCommandContext(commandName, switches, new string[]{});
}
private CommandContext CreateCommandContext(string commandName, IDictionary<string, string> switches, string[] args) {
var builder = new CommandHandlerDescriptorBuilder();
var descriptor = builder.Build(typeof(StubCommandHandler));
var commandDescriptor = descriptor.Commands.Single(d => string.Equals(d.Name, commandName, StringComparison.OrdinalIgnoreCase));
return new CommandContext {
Command = commandName,
Switches = switches,
CommandDescriptor = commandDescriptor,
Arguments = args,
Input = new StringReader(string.Empty),
Output = new StringWriter()
};
}
[Test]
public void TestFooCommand() {
CommandContext commandContext = new CommandContext { Command = "Foo" };
var commandContext = CreateCommandContext("Foo");
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("Command Foo Executed"));
Assert.That(commandContext.Output.ToString(), Is.EqualTo("Command Foo Executed"));
}
[Test]
public void TestNotExistingCommand() {
CommandContext commandContext = new CommandContext { Command = "NoSuchCommand" };
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
Assert.Throws<InvalidOperationException>(() => {
var commandContext = CreateCommandContext("NoSuchCommand");
_handler.Execute(commandContext);
});
}
[Test]
public void TestCommandWithCustomAlias() {
CommandContext commandContext = new CommandContext { Command = "Bar" };
var commandContext = CreateCommandContext("Bar");
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("Hello World!"));
Assert.That(commandContext.Output.ToString(), Is.EqualTo("Hello World!"));
}
[Test]
public void TestHelpText() {
var commandContext = CreateCommandContext("Baz");
Assert.That(commandContext.CommandDescriptor.HelpText, Is.EqualTo("Baz help"));
}
[Test]
public void TestEmptyHelpText() {
var commandContext = CreateCommandContext("Foo");
Assert.That(commandContext.CommandDescriptor.HelpText, Is.EqualTo(string.Empty));
}
[Test]
public void TestBooleanSwitchForCommand() {
CommandContext commandContext = new CommandContext { Command = "Baz", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("Verbose", "true");
var commandContext = CreateCommandContext("Baz", new Dictionary<string, string> {{"Verbose", "true"}});
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("Command Baz Called : This was a test"));
Assert.That(commandContext.Output.ToString(), Is.EqualTo("Command Baz Called : This was a test"));
}
[Test]
public void TestIntSwitchForCommand() {
CommandContext commandContext = new CommandContext { Command = "Baz", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("Level", "2");
var commandContext = CreateCommandContext("Baz", new Dictionary<string, string> {{"Level", "2"}});
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("Command Baz Called : Entering Level 2"));
Assert.That(commandContext.Output.ToString(), Is.EqualTo("Command Baz Called : Entering Level 2"));
}
[Test]
public void TestStringSwitchForCommand() {
CommandContext commandContext = new CommandContext { Command = "Baz", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("User", "OrchardUser");
var commandContext = CreateCommandContext("Baz", new Dictionary<string, string> {{"User", "OrchardUser"}});
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("Command Baz Called : current user is OrchardUser"));
Assert.That(commandContext.Output.ToString(), Is.EqualTo("Command Baz Called : current user is OrchardUser"));
}
[Test]
public void TestSwitchForCommandWithoutSupportForIt() {
CommandContext commandContext = new CommandContext { Command = "Foo", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("User", "OrchardUser");
var switches = new Dictionary<string, string> {{"User", "OrchardUser"}};
var commandContext = CreateCommandContext("Foo", switches);
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
}
[Test]
public void TestCommandThatDoesNotReturnAValue() {
CommandContext commandContext = new CommandContext { Command = "Log" };
var commandContext = CreateCommandContext("Log");
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.Null);
Assert.That(commandContext.Output.ToString(), Is.EqualTo(""));
}
[Test]
public void TestNotExistingSwitch() {
CommandContext commandContext = new CommandContext { Command = "Foo", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("ThisSwitchDoesNotExist", "Insignificant");
var switches = new Dictionary<string, string> {{"ThisSwitchDoesNotExist", "Insignificant"}};
var commandContext = CreateCommandContext("Foo", switches);
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
}
[Test]
public void TestCommandArgumentsArePassedCorrectly() {
CommandContext commandContext = new CommandContext {
Command = "Concat",
Switches = new Dictionary<string, string>(),
Arguments = new[] {"left to ", "right"}
};
var commandContext = CreateCommandContext("Concat", new Dictionary<string, string>(), new[] {"left to ", "right"});
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("left to right"));
Assert.That(commandContext.Output.ToString(), Is.EqualTo("left to right"));
}
[Test]
public void TestCommandArgumentsArePassedCorrectlyWithAParamsParameters() {
CommandContext commandContext = new CommandContext {
Command = "ConcatParams",
Switches = new Dictionary<string, string>(),
Arguments = new[] {"left to ", "right"}
};
var commandContext = CreateCommandContext("ConcatParams", new Dictionary<string, string>(), new[] {"left to ", "right"});
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("left to right"));
Assert.That(commandContext.Output.ToString(), Is.EqualTo("left to right"));
}
[Test]
public void TestCommandArgumentsArePassedCorrectlyWithAParamsParameterAndNoArguments() {
CommandContext commandContext = new CommandContext {
Command = "ConcatParams",
Switches = new Dictionary<string, string>()
};
var commandContext = CreateCommandContext("ConcatParams", new Dictionary<string, string>());
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo(""));
Assert.That(commandContext.Output.ToString(), Is.EqualTo(""));
}
[Test]
public void TestCommandArgumentsArePassedCorrectlyWithNormalParametersAndAParamsParameters() {
CommandContext commandContext = new CommandContext {
Command = "ConcatAllParams",
Switches = new Dictionary<string, string>(),
Arguments = new[] { "left-", "center-", "right" }
};
var commandContext = CreateCommandContext("ConcatAllParams",
new Dictionary<string, string>(),
new[] { "left-", "center-", "right"});
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("left-center-right"));
Assert.That(commandContext.Output.ToString(), Is.EqualTo("left-center-right"));
}
[Test]
public void TestCommandParamsMismatchWithoutParamsNotEnoughArguments() {
CommandContext commandContext = new CommandContext {
Command = "Concat",
Switches = new Dictionary<string, string>(),
Arguments = new[] { "left to "}
};
var commandContext = CreateCommandContext("Concat", new Dictionary<string, string>(), new[] { "left to " });
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
}
[Test]
public void TestCommandParamsMismatchWithoutParamsTooManyArguments() {
CommandContext commandContext = new CommandContext {
Command = "Foo",
Switches = new Dictionary<string, string>(),
Arguments = new[] { "left to " }
};
var commandContext = CreateCommandContext("Foo", new Dictionary<string, string>(), new[] { "left to " });
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
}
[Test]
public void TestCommandParamsMismatchWithParamsButNotEnoughArguments() {
CommandContext commandContext = new CommandContext {
Command = "ConcatAllParams",
Switches = new Dictionary<string, string>(),
};
var commandContext = CreateCommandContext("ConcatAllParams", new Dictionary<string, string>());
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
}
}
@@ -173,6 +186,7 @@ namespace Orchard.Tests.Commands {
}
[OrchardSwitches("Verbose, Level, User")]
[CommandHelp("Baz help")]
public string Baz() {
string trace = "Command Baz Called";

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Autofac;
@@ -14,7 +15,7 @@ namespace Orchard.Tests.Commands {
[SetUp]
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<CommandManager>().As<ICommandManager>();
builder.RegisterType<DefaultCommandManager>().As<ICommandManager>();
builder.RegisterType<MyCommand>().As<ICommandHandler>();
builder.RegisterModule(new CommandModule());
var container = builder.Build();
@@ -24,16 +25,16 @@ namespace Orchard.Tests.Commands {
[Test]
public void ManagerCanRunACommand() {
var context = new CommandParameters { Arguments = new string[] { "FooBar" } };
var context = new CommandParameters { Arguments = new string[] { "FooBar" }, Output = new StringWriter()};
_manager.Execute(context);
Assert.That(context.Output, Is.EqualTo("success!"));
Assert.That(context.Output.ToString(), Is.EqualTo("success!"));
}
[Test]
public void ManagerCanRunACompositeCommand() {
var context = new CommandParameters { Arguments = ("Foo Bar Bleah").Split(' ') };
var context = new CommandParameters { Arguments = ("Foo Bar Bleah").Split(' '), Output = new StringWriter() };
_manager.Execute(context);
Assert.That(context.Output, Is.EqualTo("Bleah"));
Assert.That(context.Output.ToString(), Is.EqualTo("Bleah"));
}
public class MyCommand : DefaultOrchardCommandHandler {

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac.Features.Metadata;
namespace Orchard.Commands.Builtin {
public class HelpCommand : DefaultOrchardCommandHandler {
private readonly ICommandManager _commandManager;
public HelpCommand(ICommandManager commandManager) {
_commandManager = commandManager;
}
[OrchardCommand("help commands")]
[CommandHelp("List all available commands")]
public void Commands() {
var descriptors = _commandManager.GetCommandDescriptors();
foreach (var descriptor in descriptors) {
var helpText = string.IsNullOrEmpty(descriptor.HelpText) ? T("[no help text]") : T(descriptor.HelpText);
Context.Output.WriteLine("{0}: {1}", descriptor.Name, helpText);
}
}
}
}

View File

@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
namespace Orchard.Commands {
public class CommandContext {
public string Input { get; set; }
public string Output { get; set; }
public TextReader Input { get; set; }
public TextWriter Output { get; set; }
public string Command { get; set; }
public IEnumerable<string> Arguments { get; set; }
public IDictionary<string,string> Switches { get; set; }

View File

@@ -5,5 +5,6 @@ namespace Orchard.Commands {
public class CommandDescriptor {
public string Name { get; set; }
public MethodInfo MethodInfo { get; set; }
public string HelpText { get; set; }
}
}

View File

@@ -2,22 +2,35 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Orchard.Commands {
public class CommandHandlerDescriptorBuilder {
public CommandHandlerDescriptor Build(Type type) {
var methods = CollectMethods(type);
return new CommandHandlerDescriptor { Commands = methods };
return new CommandHandlerDescriptor { Commands = CollectMethods(type) };
}
private IEnumerable<CommandDescriptor> CollectMethods(Type type) {
foreach (var methodInfo in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)) {
var name = GetCommandName(methodInfo);
yield return new CommandDescriptor { Name = name, MethodInfo = methodInfo };
yield return BuildMethod(methodInfo);
}
}
private CommandDescriptor BuildMethod(MethodInfo methodInfo) {
return new CommandDescriptor {
Name = GetCommandName(methodInfo),
MethodInfo = methodInfo,
HelpText = GetCommandHelpText(methodInfo)
};
}
private string GetCommandHelpText(MethodInfo methodInfo) {
var attributes = methodInfo.GetCustomAttributes(typeof(CommandHelpAttribute), false/*inherit*/);
if (attributes != null && attributes.Any()) {
return attributes.Cast<CommandHelpAttribute>().Single().HelpText;
}
return string.Empty;
}
private string GetCommandName(MethodInfo methodInfo) {
var attributes = methodInfo.GetCustomAttributes(typeof(OrchardCommandAttribute), false/*inherit*/);
if (attributes != null && attributes.Any()) {

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Mvc;
@@ -15,7 +16,7 @@ namespace Orchard.Commands {
/// executing a single command.
/// </summary>
public class CommandHostAgent {
public void RunSingleCommand(string tenant, string[] args, Dictionary<string,string> switches) {
public void RunSingleCommand(TextReader input, TextWriter output, string tenant, string[] args, Dictionary<string,string> switches) {
try {
var hostContainer = OrchardStarter.CreateHostContainer(MvcSingletons);
var host = hostContainer.Resolve<IOrchardHost>();
@@ -28,7 +29,13 @@ namespace Orchard.Commands {
// Disptach command execution to ICommandManager
using (var env = host.CreateStandaloneEnvironment(tenantSettings)) {
env.Resolve<ICommandManager>().Execute(new CommandParameters {Arguments = args, Switches = switches});
var parameters = new CommandParameters {
Arguments = args,
Switches = switches,
Input = input,
Output = output
};
env.Resolve<ICommandManager>().Execute(parameters);
}
}
catch (Exception e) {

View File

@@ -12,9 +12,12 @@ namespace Orchard.Commands {
if (!registration.Services.Contains(new TypedService(typeof(ICommandHandler))))
return;
// Workaround autofac integration: module registration is currently run twice...
if (!registration.Metadata.ContainsKey(typeof(CommandHandlerDescriptor).FullName)) {
var builder = new CommandHandlerDescriptorBuilder();
var descriptor = builder.Build(registration.Activator.LimitType);
registration.Metadata.Add(typeof (CommandHandlerDescriptor).FullName, descriptor);
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@@ -8,7 +9,7 @@ namespace Orchard.Commands {
public IEnumerable<string> Arguments { get; set; }
public IDictionary<string, string> Switches { get; set; }
public string Input { get; set; }
public string Output { get; set; }
public TextReader Input { get; set; }
public TextWriter Output { get; set; }
}
}

View File

@@ -5,14 +5,10 @@ using System.Text;
using Autofac.Features.Metadata;
namespace Orchard.Commands {
public interface ICommandManager : IDependency {
void Execute(CommandParameters parameters);
}
public class CommandManager : ICommandManager {
public class DefaultCommandManager : ICommandManager {
private readonly IEnumerable<Meta<Func<ICommandHandler>>> _handlers;
public CommandManager(IEnumerable<Meta<Func<ICommandHandler>>> handlers) {
public DefaultCommandManager(IEnumerable<Meta<Func<ICommandHandler>>> handlers) {
_handlers = handlers;
}
@@ -32,6 +28,9 @@ namespace Orchard.Commands {
}
}
public IEnumerable<CommandDescriptor> GetCommandDescriptors() {
return _handlers.SelectMany(h => GetDescriptor(h.Metadata).Commands);
}
private class Match {
public CommandContext Context { get; set; }

View File

@@ -12,11 +12,17 @@ namespace Orchard.Commands {
}
public Localizer T { get; set; }
public CommandContext Context { get; set; }
#region Implementation of ICommandHandler
public void Execute(CommandContext context) {
if (context.Switches != null && context.Switches.Count > 0) {
SetSwitchValues(context);
Invoke(context);
}
private void SetSwitchValues(CommandContext context) {
if (context.Switches != null && context.Switches.Any()) {
foreach (var commandSwitch in context.Switches.Keys) {
PropertyInfo propertyInfo = GetType().GetProperty(commandSwitch);
if (propertyInfo == null) {
@@ -45,17 +51,19 @@ namespace Orchard.Commands {
}
}
}
InvokeCommand(context);
}
private void InvokeCommand(CommandContext context) {
private void Invoke(CommandContext context) {
CheckMethodForSwitches(context.CommandDescriptor.MethodInfo, context.Switches);
object[] invokeParameters = GetInvokeParametersForMethod(context.CommandDescriptor.MethodInfo, context.Arguments.ToArray() ?? new string[] { });
object[] invokeParameters = GetInvokeParametersForMethod(context.CommandDescriptor.MethodInfo, (context.Arguments ?? Enumerable.Empty<string>()).ToArray());
if (invokeParameters == null) {
throw new ArgumentException(T("Command arguments don't match").ToString());
throw new InvalidOperationException(T("Command arguments don't match").ToString());
}
context.Output = (string)context.CommandDescriptor.MethodInfo.Invoke(this, invokeParameters);
this.Context = context;
var result = context.CommandDescriptor.MethodInfo.Invoke(this, invokeParameters);
if (result is string)
context.Output.Write(result);
}
private static object[] GetInvokeParametersForMethod(MethodInfo methodInfo, IList<string> arguments) {

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Orchard.Commands {
public interface ICommandManager : IDependency {
void Execute(CommandParameters parameters);
IEnumerable<CommandDescriptor> GetCommandDescriptors();
}
}

View File

@@ -12,6 +12,14 @@ namespace Orchard.Commands {
public string Command {
get { return _commandAlias; }
}
}
[AttributeUsage(AttributeTargets.Method)]
public class CommandHelpAttribute : Attribute {
public CommandHelpAttribute(string text) {
this.HelpText = text;
}
public string HelpText { get; set; }
}
}

View File

@@ -142,8 +142,10 @@
<Compile Include="Commands\CommandHandlerDescriptor.cs" />
<Compile Include="Commands\CommandHandlerDescriptorBuilder.cs" />
<Compile Include="Commands\CommandHostAgent.cs" />
<Compile Include="Commands\CommandManager.cs" />
<Compile Include="Commands\DefaultCommandManager.cs" />
<Compile Include="Commands\CommandModule.cs" />
<Compile Include="Commands\Builtin\HelpCommand.cs" />
<Compile Include="Commands\ICommandManager.cs" />
<Compile Include="Commands\OrchardCommandAttribute.cs" />
<Compile Include="Commands\CommandContext.cs" />
<Compile Include="Commands\DefaultOrchardCommandHandler.cs" />

View File

@@ -22,6 +22,8 @@ namespace Orchard.Host {
public void RunCommand(OrchardParameters args) {
var agent = Activator.CreateInstance("Orchard.Framework", "Orchard.Commands.CommandHostAgent").Unwrap();
agent.GetType().GetMethod("RunSingleCommand").Invoke(agent, new object[] {
Console.In,
Console.Out,
args.Tenant,
args.Arguments.ToArray(),
args.Switches});

View File

@@ -20,7 +20,10 @@ namespace Orchard {
case "v":
case "verbose":
result.Verbose = bool.Parse(sw.Value);
bool verbose;
if (!bool.TryParse(sw.Value, out verbose))
verbose = true;
result.Verbose = verbose;
break;
case "vp":