From 80bc0a9df896ce4f33fd8302963ed4216426c3b5 Mon Sep 17 00:00:00 2001 From: Suha Can Date: Thu, 17 Feb 2011 12:32:41 -0800 Subject: [PATCH] Implementation of the command handler for recipes. Recipes now can execute commands. --HG-- branch : recipe --- .../RecipeHandlers/CommandRecipeHandler.cs | 162 +++++++++++++++++- .../Orchard.Setup/Services/SetupService.cs | 8 +- 2 files changed, 161 insertions(+), 9 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Recipes/RecipeHandlers/CommandRecipeHandler.cs b/src/Orchard.Web/Modules/Orchard.Recipes/RecipeHandlers/CommandRecipeHandler.cs index a86a2e73f..d1d722b68 100644 --- a/src/Orchard.Web/Modules/Orchard.Recipes/RecipeHandlers/CommandRecipeHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.Recipes/RecipeHandlers/CommandRecipeHandler.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; +using Orchard.Commands; using Orchard.Localization; using Orchard.Logging; using Orchard.Recipes.Models; @@ -8,7 +11,12 @@ using Orchard.Recipes.Services; namespace Orchard.Recipes.RecipeHandlers { public class CommandRecipeHandler : IRecipeHandler { - public CommandRecipeHandler () { + private readonly ICommandManager _commandManager; + private readonly CommandParser _commandParser; + + public CommandRecipeHandler (ICommandManager commandManager) { + _commandManager = commandManager; + _commandParser = new CommandParser(); Logger = NullLogger.Instance; T = NullLocalizer.Instance; } @@ -34,8 +42,158 @@ namespace Orchard.Recipes.RecipeHandlers { .Split(new[] {"\r\n", "\n"}, StringSplitOptions.RemoveEmptyEntries) .Select(commandEntry => commandEntry.Trim()); - // run commands. + foreach (var command in commands) { + if (!String.IsNullOrEmpty(command)) { + var commandParameters = _commandParser.ParseCommandParameters(command); + var input = new StringReader(""); + var output = new StringWriter(); + _commandManager.Execute(new CommandParameters { Arguments = commandParameters.Arguments, Input = input, Output = output, Switches = commandParameters.Switches }); + } + } + recipeContext.Executed = true; } } + + // Utility class for parsing lines of commands. + class CommandParser { + public CommandParameters ParseCommandParameters(string command) { + var args = SplitArgs(command); + var arguments = new List(); + var result = new CommandParameters { + Switches = new Dictionary() + }; + + foreach (var arg in args) { + // Switch? + if (arg[0] == '/') { + int index = arg.IndexOf(':'); + var switchName = (index < 0 ? arg.Substring(1) : arg.Substring(1, index - 1)); + var switchValue = (index < 0 || index >= arg.Length ? string.Empty : arg.Substring(index + 1)); + + if (string.IsNullOrEmpty(switchName)) + { + throw new ArgumentException(string.Format("Invalid switch syntax: \"{0}\". Valid syntax is /[:].", arg)); + } + + result.Switches.Add(switchName, switchValue); + } + else { + arguments.Add(arg); + } + } + + result.Arguments = arguments; + return result; + } + + class State { + private readonly string _commandLine; + private readonly StringBuilder _stringBuilder; + private readonly List _arguments; + private int _index; + + public State(string commandLine) { + _commandLine = commandLine; + _stringBuilder = new StringBuilder(); + _arguments = new List(); + } + + public StringBuilder StringBuilder { get { return _stringBuilder; } } + public bool EOF { get { return _index >= _commandLine.Length; } } + public char Current { get { return _commandLine[_index]; } } + public IEnumerable Arguments { get { return _arguments; } } + + public void AddArgument() { + _arguments.Add(StringBuilder.ToString()); + StringBuilder.Clear(); + } + + public void AppendCurrent() { + StringBuilder.Append(Current); + } + + public void Append(char ch) { + StringBuilder.Append(ch); + } + + public void MoveNext() { + if (!EOF) + _index++; + } + } + + /// + /// Implement the same logic as found at + /// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx + /// The 3 special characters are quote, backslash and whitespaces, in order + /// of priority. + /// The semantics of a quote is: whatever the state of the lexer, copy + /// all characters verbatim until the next quote or EOF. + /// The semantics of backslash is: If the next character is a backslash or a quote, + /// copy the next character. Otherwise, copy the backslash and the next character. + /// The semantics of whitespace is: end the current argument and move on to the next one. + /// + private static IEnumerable SplitArgs(string commandLine) { + var state = new State(commandLine); + while (!state.EOF) { + switch (state.Current) { + case '"': + ProcessQuote(state); + break; + + case '\\': + ProcessBackslash(state); + break; + + case ' ': + case '\t': + if (state.StringBuilder.Length > 0) + state.AddArgument(); + state.MoveNext(); + break; + + default: + state.AppendCurrent(); + state.MoveNext(); + break; + } + } + if (state.StringBuilder.Length > 0) + state.AddArgument(); + return state.Arguments; + } + + private static void ProcessQuote(State state) { + state.MoveNext(); + while (!state.EOF) { + if (state.Current == '"') { + state.MoveNext(); + break; + } + state.AppendCurrent(); + state.MoveNext(); + } + + state.AddArgument(); + } + + private static void ProcessBackslash(State state) { + state.MoveNext(); + if (state.EOF) { + state.Append('\\'); + return; + } + + if (state.Current == '"') { + state.Append('"'); + state.MoveNext(); + } + else { + state.Append('\\'); + state.AppendCurrent(); + state.MoveNext(); + } + } + } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs index be08206c2..b7544687c 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs @@ -240,18 +240,12 @@ namespace Orchard.Setup.Services { var cultureManager = environment.Resolve(); cultureManager.AddCulture("en-US"); - var contentManager = environment.Resolve(); - - // this needs to exit the standalone environment? rework this process entirely? - // simulate installation-time module activation events - //var hackInstallationGenerator = environment.Resolve(); - //hackInstallationGenerator.GenerateInstallEvents(); - var recipeManager = environment.Resolve(); if (context.Recipe != null) { recipeManager.Execute(Recipes().Where(r => r.Name == context.Recipe).FirstOrDefault()); } + var contentManager = environment.Resolve(); // If "Orchard.Widgets" is enabled, setup default layers and widgets var extensionManager = environment.Resolve(); var shellDescriptor = environment.Resolve();