From fe1cc0e86920ce4c4f555c48580a7787595f018a Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Fri, 23 Apr 2010 16:30:25 -0700 Subject: [PATCH] Refactoring of orchard.exe Added "Usage" help screen for "orchard.exe /?" Better support for switches parsing Encapsulated Console.In and Console.Out Extracted "OrchardHost" class --HG-- branch : dev --- src/Orchard/Commands/Builtin/HelpCommand.cs | 2 - src/Orchard/Localization/LocalizedString.cs | 6 +- src/Tools/Orchard/Host/CommandHost.cs | 9 +- src/Tools/Orchard/Orchard.csproj | 1 + src/Tools/Orchard/OrchardHost.cs | 176 ++++++++++++++++++ src/Tools/Orchard/OrchardParameters.cs | 3 - .../Parameters/CommandParametersParser.cs | 14 +- src/Tools/Orchard/Program.cs | 103 +--------- 8 files changed, 198 insertions(+), 116 deletions(-) create mode 100644 src/Tools/Orchard/OrchardHost.cs diff --git a/src/Orchard/Commands/Builtin/HelpCommand.cs b/src/Orchard/Commands/Builtin/HelpCommand.cs index 73aced3c8..7558bca52 100644 --- a/src/Orchard/Commands/Builtin/HelpCommand.cs +++ b/src/Orchard/Commands/Builtin/HelpCommand.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using Autofac.Features.Metadata; using Orchard.Localization; namespace Orchard.Commands.Builtin { diff --git a/src/Orchard/Localization/LocalizedString.cs b/src/Orchard/Localization/LocalizedString.cs index 6f1ae781b..bd5f94b1f 100644 --- a/src/Orchard/Localization/LocalizedString.cs +++ b/src/Orchard/Localization/LocalizedString.cs @@ -1,5 +1,7 @@ -namespace Orchard.Localization { - public class LocalizedString { +using System; + +namespace Orchard.Localization { + public class LocalizedString : MarshalByRefObject { private readonly string _localized; public LocalizedString(string localized) { diff --git a/src/Tools/Orchard/Host/CommandHost.cs b/src/Tools/Orchard/Host/CommandHost.cs index 623cf49bb..52ef04877 100644 --- a/src/Tools/Orchard/Host/CommandHost.cs +++ b/src/Tools/Orchard/Host/CommandHost.cs @@ -1,7 +1,6 @@ using System; -using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Text; using System.Web.Hosting; namespace Orchard.Host { @@ -19,11 +18,11 @@ namespace Orchard.Host { //TODO } - public int RunCommand(OrchardParameters args) { + public int RunCommand(TextReader input, TextWriter output, OrchardParameters args) { var agent = Activator.CreateInstance("Orchard.Framework", "Orchard.Commands.CommandHostAgent").Unwrap(); int result = (int)agent.GetType().GetMethod("RunSingleCommand").Invoke(agent, new object[] { - Console.In, - Console.Out, + input, + output, args.Tenant, args.Arguments.ToArray(), args.Switches}); diff --git a/src/Tools/Orchard/Orchard.csproj b/src/Tools/Orchard/Orchard.csproj index 35a7eb075..9a41e2e3b 100644 --- a/src/Tools/Orchard/Orchard.csproj +++ b/src/Tools/Orchard/Orchard.csproj @@ -48,6 +48,7 @@ + diff --git a/src/Tools/Orchard/OrchardHost.cs b/src/Tools/Orchard/OrchardHost.cs new file mode 100644 index 000000000..a4a96a24f --- /dev/null +++ b/src/Tools/Orchard/OrchardHost.cs @@ -0,0 +1,176 @@ +using System; +using System.Linq; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Web; +using System.Web.Hosting; +using Orchard.Host; +using Orchard.Parameters; + +namespace Orchard { + class OrchardHost { + private readonly TextReader _input; + private readonly TextWriter _output; + private readonly string[] _args; + private OrchardParameters _arguments; + + public OrchardHost(TextReader input, TextWriter output, string[] args) { + _input = input; + _output = output; + _args = args; + } + + private bool Verbose { + get { return _arguments != null && _arguments.Verbose; } + } + + public int Run() { + try { + return DoRun(); + } + catch (Exception e) { + _output.WriteLine("Error:"); + for (; e != null; e = e.InnerException) { + _output.WriteLine(" {0}", e.Message); + if (Verbose) { + _output.WriteLine(" Stacktrace:"); + _output.WriteLine("{0}", e.StackTrace); + _output.WriteLine(); + } + } + return -1; + } + } + + private int DoRun() { + _arguments = new OrchardParametersParser().Parse(new CommandParametersParser().Parse(_args)); + if (!_arguments.Arguments.Any() || _arguments.Switches.ContainsKey("?")) { + return GeneralHelp(); + } + + if (string.IsNullOrEmpty(_arguments.VirtualPath)) + _arguments.VirtualPath = "/"; + LogInfo("Virtual path: \"{0}\"", _arguments.VirtualPath); + + if (string.IsNullOrEmpty(_arguments.WorkingDirectory)) + _arguments.WorkingDirectory = Environment.CurrentDirectory; + LogInfo("Working directory: \"{0}\"", _arguments.WorkingDirectory); + + LogInfo("Detecting orchard installation root directory..."); + var orchardDirectory = GetOrchardDirectory(_arguments.WorkingDirectory); + LogInfo("Orchard root directory: \"{0}\"", orchardDirectory.FullName); + + LogInfo("Creating ASP.NET AppDomain for command agent..."); + var host = (CommandHost)CreateWorkerAppDomainWithHost(_arguments.VirtualPath, orchardDirectory.FullName, typeof(CommandHost)); + + LogInfo("Executing command in ASP.NET AppDomain..."); + var result = host.RunCommand(_input, _output, _arguments); + LogInfo("Return code for command: {0}", result); + + return result; + } + + private int GeneralHelp() { + _output.WriteLine("Executes Orchard commands from a Orchard installation directory."); + _output.WriteLine(""); + _output.WriteLine("Usage:"); + _output.WriteLine(" orchard.exe arg1 ... argn /switch1[:value1] ... /switchn[:valuen]"); + _output.WriteLine(""); + _output.WriteLine(" arg1 ... argn"); + _output.WriteLine(" Specify which command to execute and optional arguments"); + _output.WriteLine(""); + _output.WriteLine(" /switch1[:value1] ... /switchn[:valuen]"); + _output.WriteLine(" Specify switches to apply to the command. Available switches generally "); + _output.WriteLine(" depend on the command executed, with the exception of a few built-in ones."); + _output.WriteLine(""); + _output.WriteLine(" Built-in commands"); + _output.WriteLine(" ================="); + _output.WriteLine(""); + _output.WriteLine(" help commands"); + _output.WriteLine(" Display the list of available commands."); + _output.WriteLine(""); + _output.WriteLine(" help "); + _output.WriteLine(" Display help for a given command."); + _output.WriteLine(""); + _output.WriteLine(" Built-in switches"); + _output.WriteLine(" ================="); + _output.WriteLine(""); + _output.WriteLine(" /WorkingDirectory"); + _output.WriteLine(" /wd"); + _output.WriteLine(" Specifies the orchard installation directory. The current directory is the default."); + _output.WriteLine(""); + _output.WriteLine(" /Verbose"); + _output.WriteLine(" /v"); + _output.WriteLine(" Turn on verbose output"); + _output.WriteLine(""); + _output.WriteLine(" /VirtualPath"); + _output.WriteLine(" /vp"); + _output.WriteLine(" Virtual path to pass to the WebHost. Empty (i.e. root path) by default."); + _output.WriteLine(""); + _output.WriteLine(" /Tenant:tenant-name"); + _output.WriteLine(" /t:tenant-name"); + _output.WriteLine(" Specifies which tenant to run the command into. \"Default\" tenant by default."); + _output.WriteLine(""); + return 0; + } + + private void LogInfo(string format, params object[] args) { + if (Verbose) { + _output.Write("{0}: ", DateTime.Now); + _output.WriteLine(format, args); + } + } + + private DirectoryInfo GetOrchardDirectory(string directory) { + for (var directoryInfo = new DirectoryInfo(directory); directoryInfo != null; directoryInfo = directoryInfo.Parent) { + if (!directoryInfo.Exists) { + throw new ApplicationException(string.Format("Directory \"{0}\" does not exist", directoryInfo.FullName)); + } + + // We look for + // 1) .\web.config + // 2) .\bin\Orchard.Framework.dll + var webConfigFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, "web.config")); + if (!webConfigFileInfo.Exists) + continue; + + var binDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName, "bin")); + if (!binDirectoryInfo.Exists) + continue; + + var orchardFrameworkFileInfo = new FileInfo(Path.Combine(binDirectoryInfo.FullName, "Orchard.Framework.dll")); + if (!orchardFrameworkFileInfo.Exists) + continue; + + return directoryInfo; + } + + throw new ApplicationException( + string.Format("Directory \"{0}\" doesn't seem to contain an Orchard installation", new DirectoryInfo(directory).FullName)); + } + + private static object CreateWorkerAppDomainWithHost(string virtualPath, string physicalPath, Type hostType) { + // this creates worker app domain in a way that host doesn't need to be in GAC or bin + // using BuildManagerHost via private reflection + string uniqueAppString = string.Concat(virtualPath, physicalPath).ToLowerInvariant(); + string appId = (uniqueAppString.GetHashCode()).ToString("x", CultureInfo.InvariantCulture); + + // create BuildManagerHost in the worker app domain + var appManager = ApplicationManager.GetApplicationManager(); + var buildManagerHostType = typeof(HttpRuntime).Assembly.GetType("System.Web.Compilation.BuildManagerHost"); + var buildManagerHost = appManager.CreateObject(appId, buildManagerHostType, virtualPath, physicalPath, false); + + // call BuildManagerHost.RegisterAssembly to make Host type loadable in the worker app domain + buildManagerHostType.InvokeMember( + "RegisterAssembly", + BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, + null, + buildManagerHost, + new object[] { hostType.Assembly.FullName, hostType.Assembly.Location }); + + // create Host in the worker app domain + return appManager.CreateObject(appId, hostType, virtualPath, physicalPath, false); + } + } +} diff --git a/src/Tools/Orchard/OrchardParameters.cs b/src/Tools/Orchard/OrchardParameters.cs index d9b8e8f42..4889ac17d 100644 --- a/src/Tools/Orchard/OrchardParameters.cs +++ b/src/Tools/Orchard/OrchardParameters.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Orchard { public class OrchardParameters : MarshalByRefObject { @@ -11,6 +9,5 @@ namespace Orchard { public string Tenant { get; set; } public IEnumerable Arguments { get; set; } public IDictionary Switches { get; set; } - } } \ No newline at end of file diff --git a/src/Tools/Orchard/Parameters/CommandParametersParser.cs b/src/Tools/Orchard/Parameters/CommandParametersParser.cs index 21860ba14..8783bb603 100644 --- a/src/Tools/Orchard/Parameters/CommandParametersParser.cs +++ b/src/Tools/Orchard/Parameters/CommandParametersParser.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Orchard.Parameters { public class CommandParametersParser : ICommandParametersParser { @@ -12,9 +10,17 @@ namespace Orchard.Parameters { }; foreach (var arg in args) { + // Switch? if (arg[0] == '/') { - var switchKeyValue = arg.Substring(1).Split(':'); - result.Switches.Add(switchKeyValue[0], switchKeyValue.Length >= 2 ? switchKeyValue[1] : string.Empty); + 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 { result.Arguments.Add(arg); diff --git a/src/Tools/Orchard/Program.cs b/src/Tools/Orchard/Program.cs index 7837cce6d..e5eba10bb 100644 --- a/src/Tools/Orchard/Program.cs +++ b/src/Tools/Orchard/Program.cs @@ -1,106 +1,9 @@ using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Web; -using System.Web.Hosting; -using Orchard.Host; -using Orchard.Parameters; namespace Orchard { - class Program { - private readonly string[] _args; - - private Program(string[] args) { - _args = args; + public class Program { + public static int Main(string[] args) { + return new OrchardHost(Console.In, Console.Out, args).Run(); } - - static int Main(string[] args) { - return new Program(args).Run(); - } - - public int Run() { - // Parse command line arguments - var arguments = new OrchardParametersParser().Parse(new CommandParametersParser().Parse(_args)); - - if (string.IsNullOrEmpty(arguments.VirtualPath)) - arguments.VirtualPath = "/"; - - if (string.IsNullOrEmpty(arguments.WorkingDirectory)) - arguments.WorkingDirectory = Environment.CurrentDirectory; - - if (arguments.Verbose) { - Console.WriteLine("Virtual path: {0}", arguments.VirtualPath); - Console.WriteLine("Working directory: {0}", arguments.WorkingDirectory); - } - - var orchardDirectory = GetOrchardDirectory(arguments.WorkingDirectory); - if (arguments.Verbose) { - Console.WriteLine("Detected and using orchard directory \"{0}\"", orchardDirectory.FullName); - } - - if (arguments.Verbose) { - Console.WriteLine("Creating ASP.NET AppDomain for command agent"); - } - var host = (CommandHost)CreateWorkerAppDomainWithHost(arguments.VirtualPath, orchardDirectory.FullName, typeof(CommandHost)); - - - if (arguments.Verbose) { - Console.WriteLine("Executing command in ASP.NET AppDomain"); - } - return host.RunCommand(arguments); - } - - private DirectoryInfo GetOrchardDirectory(string directory) { - for (var directoryInfo = new DirectoryInfo(directory); directoryInfo != null; directoryInfo = directoryInfo.Parent) { - if (!directoryInfo.Exists) { - throw new ApplicationException(string.Format("Directory \"{0}\" does not exist", directoryInfo.FullName)); - } - - var webConfigFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, "web.config")); - if (!webConfigFileInfo.Exists) - continue; - - var binDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName, "bin")); - if (!binDirectoryInfo.Exists) - continue; - - var orchardFrameworkFileInfo = new FileInfo(Path.Combine(binDirectoryInfo.FullName, "Orchard.Framework.dll")); - if (!orchardFrameworkFileInfo.Exists) - continue; - - return directoryInfo; - } - - throw new ApplicationException( - string.Format("Directory \"{0}\" doesn't seem to contain an Orchard installation", directory)); - } - - public object CreateWorkerAppDomainWithHost(string virtualPath, string physicalPath, Type hostType) { - // this creates worker app domain in a way that host doesn't need to be in GAC or bin - // using BuildManagerHost via private reflection - string uniqueAppString = string.Concat(virtualPath, physicalPath).ToLowerInvariant(); - string appId = (uniqueAppString.GetHashCode()).ToString("x", CultureInfo.InvariantCulture); - - // create BuildManagerHost in the worker app domain - var appManager = ApplicationManager.GetApplicationManager(); - var buildManagerHostType = typeof(HttpRuntime).Assembly.GetType("System.Web.Compilation.BuildManagerHost"); - var buildManagerHost = appManager.CreateObject(appId, buildManagerHostType, virtualPath, physicalPath, false); - - // call BuildManagerHost.RegisterAssembly to make Host type loadable in the worker app domain - buildManagerHostType.InvokeMember( - "RegisterAssembly", - BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, - null, - buildManagerHost, - new object[2] { hostType.Assembly.FullName, hostType.Assembly.Location }); - - // create Host in the worker app domain - return appManager.CreateObject(appId, hostType, virtualPath, physicalPath, false); - } - } }