http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/Lucene.Net.Tests.Cli/Properties/AssemblyInfo.cs ---------------------------------------------------------------------- diff --git a/src/tools/Lucene.Net.Tests.Cli/Properties/AssemblyInfo.cs b/src/tools/Lucene.Net.Tests.Cli/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a5b2792 --- /dev/null +++ b/src/tools/Lucene.Net.Tests.Cli/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Lucene.Net.Tests.Cli")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("495b65f0-0b01-40fe-9dc8-5a82c49e07ef")]
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/Lucene.Net.Tests.Cli/SourceCode/SourceCodeParserTest.cs ---------------------------------------------------------------------- diff --git a/src/tools/Lucene.Net.Tests.Cli/SourceCode/SourceCodeParserTest.cs b/src/tools/Lucene.Net.Tests.Cli/SourceCode/SourceCodeParserTest.cs new file mode 100644 index 0000000..23ae032 --- /dev/null +++ b/src/tools/Lucene.Net.Tests.Cli/SourceCode/SourceCodeParserTest.cs @@ -0,0 +1,72 @@ +using Lucene.Net.Support; +using NUnit.Framework; +using System.IO; +using System.Reflection; + +namespace Lucene.Net.Cli.SourceCode +{ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + public class SourceCodeParserTest + { + [Test] + public void TestSourceCodeSectionParser() + { + var parser = new SourceCodeSectionParser(); + var thisAssembly = this.GetType().GetTypeInfo().Assembly; + + using (var output = new MemoryStream()) + { + using (var input = thisAssembly.FindAndGetManifestResourceStream(this.GetType(), "TestInputForParser.cs")) + { + parser.ParseSourceCodeFiles(input, output); + } + + output.Seek(0, SeekOrigin.Begin); + + using (var reader = new StreamReader(output, SourceCodeSectionParser.ENCODING)) + { + Assert.AreEqual("using System;", reader.ReadLine()); + Assert.AreEqual("using System.Collections.Generic;", reader.ReadLine()); + Assert.AreEqual("using System.Linq;", reader.ReadLine()); + Assert.AreEqual("using System.Threading.Tasks;", reader.ReadLine()); + Assert.AreEqual("using System.Reflection;", reader.ReadLine()); + Assert.AreEqual("using System.Xml;", reader.ReadLine()); + Assert.AreEqual("", reader.ReadLine()); + Assert.AreEqual("namespace Lucene.Net.Cli.SourceCode", reader.ReadLine()); + Assert.AreEqual("{", reader.ReadLine()); + Assert.AreEqual(" public class TestInputForParser", reader.ReadLine()); + Assert.AreEqual(" {", reader.ReadLine()); + Assert.AreEqual(" public void Foo()", reader.ReadLine()); + Assert.AreEqual(" {", reader.ReadLine()); + Assert.AreEqual(" Console.WriteLine(\"Foo\");", reader.ReadLine()); + Assert.AreEqual(" }", reader.ReadLine()); + Assert.AreEqual("", reader.ReadLine()); + Assert.AreEqual(" public void Bar()", reader.ReadLine()); + Assert.AreEqual(" {", reader.ReadLine()); + Assert.AreEqual(" Console.WriteLine(\"Bar2\");", reader.ReadLine()); + Assert.AreEqual(" }", reader.ReadLine()); + Assert.AreEqual(" }", reader.ReadLine()); + Assert.AreEqual("}", reader.ReadLine()); + Assert.AreEqual(null, reader.ReadLine()); + Assert.AreEqual(null, reader.ReadLine()); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/Lucene.Net.Tests.Cli/SourceCode/TestInputForParser.cs ---------------------------------------------------------------------- diff --git a/src/tools/Lucene.Net.Tests.Cli/SourceCode/TestInputForParser.cs b/src/tools/Lucene.Net.Tests.Cli/SourceCode/TestInputForParser.cs new file mode 100644 index 0000000..280fc1c --- /dev/null +++ b/src/tools/Lucene.Net.Tests.Cli/SourceCode/TestInputForParser.cs @@ -0,0 +1,53 @@ +// <comment> +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// DO NOT ALTER THIS FILE, it is for testing purposes + +// </comment> +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +// <include> +////using System.Reflection; +////using System.Xml; +// </include> + +namespace Lucene.Net.Cli.SourceCode +{ + public class TestInputForParser + { + public void Foo() + { + Console.WriteLine("Foo"); + } + + // <comment> + public void Bar() + { + Console.WriteLine("Bar1"); + } + // </comment> + // <include> + ////public void Bar() + ////{ + //// Console.WriteLine("Bar2"); + ////} + // </include> + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/Lucene.Net.Tests.Cli/StringExtensions.cs ---------------------------------------------------------------------- diff --git a/src/tools/Lucene.Net.Tests.Cli/StringExtensions.cs b/src/tools/Lucene.Net.Tests.Cli/StringExtensions.cs new file mode 100644 index 0000000..eeb4e80 --- /dev/null +++ b/src/tools/Lucene.Net.Tests.Cli/StringExtensions.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Lucene.Net.Cli +{ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + public static class StringExtensions + { + public static string[] ToArgs(this string input) + { + return Regex.Replace(input.Trim(), @"\s+", " ").Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); + } + + public static string OptionValue(this IEnumerable<string> args, string option) + { + return args.SkipWhile(a => a != option).Skip(1).FirstOrDefault(); + } + + public static IList<string> OptionValues(this IEnumerable<string> args, string option) + { + var argsList = new List<string>(args); + var result = new List<string>(); + for (int i = 0; i < argsList.Count; i++) + { + string current = argsList[i]; + if (current == option) + { + if (i == argsList.Count - 1) + { + result.Add(null); + } + else + { + current = argsList[i + 1]; + if (current != option) + { + result.Add(current); + i++; + } + else + { + result.Add(null); + } + } + } + } + return result; + } + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/Lucene.Net.Tests.Cli/project.json ---------------------------------------------------------------------- diff --git a/src/tools/Lucene.Net.Tests.Cli/project.json b/src/tools/Lucene.Net.Tests.Cli/project.json new file mode 100644 index 0000000..9ff17e3 --- /dev/null +++ b/src/tools/Lucene.Net.Tests.Cli/project.json @@ -0,0 +1,35 @@ +{ + "version": "4.8.0", + "title": "Lucene.Net.Tests.Cli", + "dependencies": { + "dotnet-test-nunit-teamcity": "3.4.0-beta-3", + "lucene-cli": "4.8.0", + "Lucene.Net": "4.8.0", + "Lucene.Net.TestFramework": "4.8.0", + "NUnit": "3.5.0" + }, + "testRunner": "nunit-teamcity", + "frameworks": { + "netcoreapp1.0": { + "imports": "dnxcore50", + "buildOptions": { + "debugType": "portable", + "define": [ "NETSTANDARD" ], + "compile": { + "excludeFiles": [ + "SourceCode/TestInputForParser.cs" + ] + }, + "embed": { + "includeFiles": [ + "SourceCode/TestInputForParser.cs" + ] + } + } + } + }, + "runtimes": { + "win7-x86": {}, + "win7-x64": {} + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/CommandLine/CommandArgument.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/CommandLine/CommandArgument.cs b/src/tools/lucene-cli/CommandLine/CommandArgument.cs new file mode 100644 index 0000000..b7081e0 --- /dev/null +++ b/src/tools/lucene-cli/CommandLine/CommandArgument.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace Lucene.Net.Cli.CommandLine +{ + public class CommandArgument + { + public CommandArgument() + { + Values = new List<string>(); + } + + //public string Id { get; set; } // used to identify a command in the list + public virtual string Name { get; set; } + public bool ShowInHelpText { get; set; } = true; + public virtual string Description { get; set; } + public List<string> Values { get; private set; } + public bool MultipleValues { get; set; } + public virtual string Value + { + get + { + return Values.FirstOrDefault(); + } + } + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/CommandLine/CommandLineApplication.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/CommandLine/CommandLineApplication.cs b/src/tools/lucene-cli/CommandLine/CommandLineApplication.cs new file mode 100644 index 0000000..978edad --- /dev/null +++ b/src/tools/lucene-cli/CommandLine/CommandLineApplication.cs @@ -0,0 +1,563 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lucene.Net.Cli.CommandLine +{ + public class CommandLineApplication + { + // Indicates whether the parser should throw an exception when it runs into an unexpected argument. + // If this field is set to false, the parser will stop parsing when it sees an unexpected argument, and all + // remaining arguments, including the first unexpected argument, will be stored in RemainingArguments property. + private readonly bool _throwOnUnexpectedArg; + + public CommandLineApplication(bool throwOnUnexpectedArg = true) + { + _throwOnUnexpectedArg = throwOnUnexpectedArg; + Options = new List<CommandOption>(); + Arguments = new List<CommandArgument>(); + Commands = new List<CommandLineApplication>(); + RemainingArguments = new List<string>(); + Invoke = () => 0; + } + + public CommandLineApplication Parent { get; set; } + public virtual string Name { get; set; } + public string FullName { get; set; } + public string Syntax { get; set; } + public virtual string Description { get; set; } + public bool ShowInHelpText { get; set; } = true; + public string ExtendedHelpText { get; set; } + public readonly List<CommandOption> Options; + public CommandOption OptionHelp { get; private set; } + public CommandOption OptionVersion { get; private set; } + public readonly List<CommandArgument> Arguments; + public readonly List<string> RemainingArguments; + public bool IsShowingInformation { get; protected set; } // Is showing help or version? + public Func<int> Invoke { get; set; } + public Func<string> LongVersionGetter { get; set; } + public Func<string> ShortVersionGetter { get; set; } + public readonly List<CommandLineApplication> Commands; + public bool AllowArgumentSeparator { get; set; } + public TextWriter Out { get; set; } = Console.Out; + public TextWriter Error { get; set; } = Console.Error; + + public IEnumerable<CommandOption> GetOptions() + { + var expr = Options.AsEnumerable(); + var rootNode = this; + while (rootNode.Parent != null) + { + rootNode = rootNode.Parent; + expr = expr.Concat(rootNode.Options.Where(o => o.Inherited)); + } + + return expr; + } + + public virtual CommandLineApplication Command(string name, Action<CommandLineApplication> configuration, + bool throwOnUnexpectedArg = true) + { + var command = new CommandLineApplication(throwOnUnexpectedArg) { Name = name, Parent = this }; + Commands.Add(command); + configuration(command); + return command; + } + + public virtual CommandOption Option(string template, string description, CommandOptionType optionType) + => Option(template, description, optionType, _ => { }, inherited: false); + + public virtual CommandOption Option(string template, string description, CommandOptionType optionType, bool inherited) + => Option(template, description, optionType, _ => { }, inherited); + + public virtual CommandOption Option(string template, string description, CommandOptionType optionType, Action<CommandOption> configuration) + => Option(template, description, optionType, configuration, inherited: false); + + public virtual CommandOption Option(string template, string description, CommandOptionType optionType, Action<CommandOption> configuration, bool inherited) + { + var option = new CommandOption(template, optionType) + { + Description = description, + Inherited = inherited + }; + Options.Add(option); + configuration(option); + return option; + } + + public virtual CommandArgument Argument(string name, string description, bool multipleValues = false) + { + return Argument(name, description, _ => { }, multipleValues); + } + + public virtual CommandArgument Argument(string name, string description, Action<CommandArgument> configuration, bool multipleValues = false) + { + var lastArg = Arguments.LastOrDefault(); + if (lastArg != null && lastArg.MultipleValues) + { + var message = string.Format("The last argument '{0}' accepts multiple values. No more argument can be added.", + lastArg.Name); + throw new InvalidOperationException(message); + } + + var argument = new CommandArgument { Name = name, Description = description, MultipleValues = multipleValues }; + Arguments.Add(argument); + configuration(argument); + return argument; + } + + public virtual void OnExecute(Func<int> invoke) + { + Invoke = invoke; + } + + public virtual void OnExecute(Func<Task<int>> invoke) + { + Invoke = () => invoke().Result; + } + public virtual int Execute(params string[] args) + { + CommandLineApplication command = this; + CommandOption option = null; + IEnumerator<CommandArgument> arguments = null; + + for (var index = 0; index < args.Length; index++) + { + var arg = args[index]; + var processed = false; + if (!processed && option == null) + { + string[] longOption = null; + string[] shortOption = null; + + if (arg.StartsWith("--")) + { + longOption = arg.Substring(2).Split(new[] { ':', '=' }, 2); + } + else if (arg.StartsWith("-")) + { + shortOption = arg.Substring(1).Split(new[] { ':', '=' }, 2); + } + else // Look for symbols (such as help option) + { + var symbolOption = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.SymbolName, arg, StringComparison.Ordinal)); + if (symbolOption != null) + { + shortOption = new string[] { symbolOption.SymbolName, "" }; + } + } + if (longOption != null) + { + processed = true; + var longOptionName = longOption[0]; + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.LongName, longOptionName, StringComparison.Ordinal)); + + if (option == null) + { + if (string.IsNullOrEmpty(longOptionName) && !command._throwOnUnexpectedArg && AllowArgumentSeparator) + { + // skip over the '--' argument separator + index++; + } + + HandleUnexpectedArg(command, args, index, argTypeName: "option"); + break; + } + + // If we find a help/version option, show information and stop parsing + if (command.OptionHelp == option) + { + command.ShowHelp(); + return 0; + } + else if (command.OptionVersion == option) + { + command.ShowVersion(); + return 0; + } + + if (longOption.Length == 2) + { + if (!option.TryParse(longOption[1])) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{longOption[1]}' for option '{option.LongName}'"); + } + option = null; + } + else if (option.OptionType == CommandOptionType.NoValue) + { + // No value is needed for this option + option.TryParse(null); + option = null; + } + } + if (shortOption != null) + { + processed = true; + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.ShortName, shortOption[0], StringComparison.Ordinal)); + + // If not a short option, try symbol option + if (option == null) + { + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.SymbolName, shortOption[0], StringComparison.Ordinal)); + } + + if (option == null) + { + HandleUnexpectedArg(command, args, index, argTypeName: "option"); + break; + } + + // If we find a help/version option, show information and stop parsing + if (command.OptionHelp == option) + { + command.ShowHelp(); + return 0; + } + else if (command.OptionVersion == option) + { + command.ShowVersion(); + return 0; + } + + if (shortOption.Length == 2) + { + if (!option.TryParse(shortOption[1])) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{shortOption[1]}' for option '{option.LongName}'"); + } + option = null; + } + else if (option.OptionType == CommandOptionType.NoValue) + { + // No value is needed for this option + option.TryParse(null); + option = null; + } + } + } + + if (!processed && option != null) + { + processed = true; + if (!option.TryParse(arg)) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{option.LongName}'"); + } + option = null; + } + + if (!processed && arguments == null) + { + var currentCommand = command; + foreach (var subcommand in command.Commands) + { + if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase)) + { + processed = true; + command = subcommand; + break; + } + } + + // If we detect a subcommand + if (command != currentCommand) + { + processed = true; + } + } + if (!processed) + { + if (arguments == null) + { + arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator()); + } + if (arguments.MoveNext()) + { + processed = true; + arguments.Current.Values.Add(arg); + } + } + if (!processed) + { + HandleUnexpectedArg(command, args, index, argTypeName: "command or argument"); + break; + } + } + + if (option != null) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Missing value for option '{option.LongName}'"); + } + + return command.Invoke(); + } + + // Helper method that adds a help option + public virtual CommandOption HelpOption(string template) + { + // Help option is special because we stop parsing once we see it + // So we store it separately for further use + OptionHelp = Option(template, "Show help information", CommandOptionType.NoValue); + + return OptionHelp; + } + + public virtual CommandOption VersionOption(string template, + string shortFormVersion, + string longFormVersion = null) + { + if (longFormVersion == null) + { + return VersionOption(template, () => shortFormVersion); + } + else + { + return VersionOption(template, () => shortFormVersion, () => longFormVersion); + } + } + + // Helper method that adds a version option + public virtual CommandOption VersionOption(string template, + Func<string> shortFormVersionGetter, + Func<string> longFormVersionGetter = null) + { + // Version option is special because we stop parsing once we see it + // So we store it separately for further use + OptionVersion = Option(template, "Show version information", CommandOptionType.NoValue); + ShortVersionGetter = shortFormVersionGetter; + LongVersionGetter = longFormVersionGetter ?? shortFormVersionGetter; + + return OptionVersion; + } + + // Show short hint that reminds users to use help option + public virtual void ShowHint() + { + if (OptionHelp != null) + { + Out.WriteLine(string.Format("Specify --{0} for a list of available options and commands.", OptionHelp.LongName)); + } + } + + // Show full help + public virtual void ShowHelp(string commandName = null) + { + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + } + + Out.WriteLine(GetHelpText(commandName)); + } + + public virtual string GetHelpText(string commandName = null) + { + var headerBuilder = new StringBuilder("Usage:"); + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + headerBuilder.Insert(6, string.Format(" {0}", cmd.Name)); + } + + CommandLineApplication target; + + if (commandName == null || string.Equals(Name, commandName, StringComparison.OrdinalIgnoreCase)) + { + target = this; + } + else + { + target = Commands.SingleOrDefault(cmd => string.Equals(cmd.Name, commandName, StringComparison.OrdinalIgnoreCase)); + + if (target != null) + { + headerBuilder.AppendFormat(" {0}", commandName); + } + else + { + // The command name is invalid so don't try to show help for something that doesn't exist + target = this; + } + + } + + var optionsBuilder = new StringBuilder(); + var commandsBuilder = new StringBuilder(); + var argumentsBuilder = new StringBuilder(); + + var arguments = target.Arguments.Where(a => a.ShowInHelpText).ToList(); + if (arguments.Any()) + { + headerBuilder.Append(" [arguments]"); + + argumentsBuilder.AppendLine(); + argumentsBuilder.AppendLine("Arguments:"); + var maxArgLen = arguments.Max(a => a.Name.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxArgLen + 2); + foreach (var arg in arguments) + { + argumentsBuilder.AppendFormat(outputFormat, arg.Name, arg.Description); + argumentsBuilder.AppendLine(); + } + } + + var options = target.GetOptions().Where(o => o.ShowInHelpText).ToList(); + if (options.Any()) + { + headerBuilder.Append(" [options]"); + + optionsBuilder.AppendLine(); + optionsBuilder.AppendLine("Options:"); + var maxOptLen = options.Max(o => o.Template.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxOptLen + 2); + foreach (var opt in options) + { + optionsBuilder.AppendFormat(outputFormat, opt.Template, opt.Description); + optionsBuilder.AppendLine(); + } + } + + var commands = target.Commands.Where(c => c.ShowInHelpText).ToList(); + if (commands.Any()) + { + headerBuilder.Append(" [command]"); + + commandsBuilder.AppendLine(); + commandsBuilder.AppendLine("Commands:"); + var maxCmdLen = commands.Max(c => c.Name.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxCmdLen + 2); + foreach (var cmd in commands.OrderBy(c => c.Name)) + { + commandsBuilder.AppendFormat(outputFormat, cmd.Name, cmd.Description); + commandsBuilder.AppendLine(); + } + + if (OptionHelp != null) + { + commandsBuilder.AppendLine(); + commandsBuilder.AppendFormat($"Use \"{target.Name} [command] --{OptionHelp.LongName}\" for more information about a command."); + commandsBuilder.AppendLine(); + } + } + + if (target.AllowArgumentSeparator) + { + headerBuilder.Append(" [[--] <arg>...]"); + } + + headerBuilder.AppendLine(); + + var nameAndVersion = new StringBuilder(); + nameAndVersion.AppendLine(GetFullNameAndVersion()); + nameAndVersion.AppendLine(); + + return nameAndVersion.ToString() + + headerBuilder.ToString() + + argumentsBuilder.ToString() + + optionsBuilder.ToString() + + commandsBuilder.ToString() + + (string.IsNullOrEmpty(target.ExtendedHelpText) ? "" : Environment.NewLine + target.ExtendedHelpText); + } + + public virtual void ShowVersion() + { + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + } + + Out.WriteLine(FullName); + Out.WriteLine(LongVersionGetter()); + } + + public virtual string GetFullNameAndVersion() + { + return ShortVersionGetter == null ? FullName : string.Format("{0} {1}", FullName, ShortVersionGetter()); + } + + public virtual void ShowRootCommandFullNameAndVersion() + { + var rootCmd = this; + while (rootCmd.Parent != null) + { + rootCmd = rootCmd.Parent; + } + + Out.WriteLine(rootCmd.GetFullNameAndVersion()); + Out.WriteLine(); + } + + private void HandleUnexpectedArg(CommandLineApplication command, string[] args, int index, string argTypeName) + { + if (command._throwOnUnexpectedArg) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'"); + } + else + { + // All remaining arguments are stored for further use + command.RemainingArguments.AddRange(new ArraySegment<string>(args, index, args.Length - index)); + } + } + + private class CommandArgumentEnumerator : IEnumerator<CommandArgument> + { + private readonly IEnumerator<CommandArgument> _enumerator; + + public CommandArgumentEnumerator(IEnumerator<CommandArgument> enumerator) + { + _enumerator = enumerator; + } + + public CommandArgument Current + { + get + { + return _enumerator.Current; + } + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + public void Dispose() + { + _enumerator.Dispose(); + } + + public bool MoveNext() + { + if (Current == null || !Current.MultipleValues) + { + return _enumerator.MoveNext(); + } + + // If current argument allows multiple values, we don't move forward and + // all later values will be added to current CommandArgument.Values + return true; + } + + public void Reset() + { + _enumerator.Reset(); + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/CommandLine/CommandOption.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/CommandLine/CommandOption.cs b/src/tools/lucene-cli/CommandLine/CommandOption.cs new file mode 100644 index 0000000..16c1025 --- /dev/null +++ b/src/tools/lucene-cli/CommandLine/CommandOption.cs @@ -0,0 +1,112 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Lucene.Net.Cli.CommandLine +{ + public class CommandOption + { + public CommandOption(string template, CommandOptionType optionType) + { + Template = template; + OptionType = optionType; + Values = new List<string>(); + + foreach (var part in Template.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (part.StartsWith("--")) + { + LongName = part.Substring(2); + } + else if (part.StartsWith("-")) + { + var optName = part.Substring(1); + + // If there is only one char and it is not an English letter, it is a symbol option (e.g. "-?") + if (optName.Length == 1 && !IsEnglishLetter(optName[0])) + { + SymbolName = optName; + } + else + { + ShortName = optName; + } + } + else if (part.StartsWith("<") && part.EndsWith(">")) + { + ValueName = part.Substring(1, part.Length - 2); + } + else + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + if (string.IsNullOrEmpty(LongName) && string.IsNullOrEmpty(ShortName) && string.IsNullOrEmpty(SymbolName)) + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + /// <summary> + /// An ID that can be used to locate this option. + /// </summary> + public string UniqueId { get; set; } + public string Template { get; set; } + public string ShortName { get; set; } + public string LongName { get; set; } + public string SymbolName { get; set; } + public string ValueName { get; set; } + public string Description { get; set; } + public List<string> Values { get; private set; } + public CommandOptionType OptionType { get; private set; } + public bool ShowInHelpText { get; set; } = true; + public bool Inherited { get; set; } + + public bool TryParse(string value) + { + switch (OptionType) + { + case CommandOptionType.MultipleValue: + Values.Add(value); + break; + case CommandOptionType.SingleValue: + if (Values.Any()) + { + return false; + } + Values.Add(value); + break; + case CommandOptionType.NoValue: + if (value != null) + { + return false; + } + // Add a value to indicate that this option was specified + Values.Add("on"); + break; + default: + break; + } + return true; + } + + public bool HasValue() + { + return Values.Any(); + } + + public string Value() + { + return HasValue() ? Values[0] : null; + } + + private bool IsEnglishLetter(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/CommandLine/CommandOptionType.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/CommandLine/CommandOptionType.cs b/src/tools/lucene-cli/CommandLine/CommandOptionType.cs new file mode 100644 index 0000000..ef541e4 --- /dev/null +++ b/src/tools/lucene-cli/CommandLine/CommandOptionType.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Lucene.Net.Cli.CommandLine +{ + public enum CommandOptionType + { + MultipleValue, + SingleValue, + NoValue + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/CommandLine/CommandParsingException.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/CommandLine/CommandParsingException.cs b/src/tools/lucene-cli/CommandLine/CommandParsingException.cs new file mode 100644 index 0000000..bd2446a --- /dev/null +++ b/src/tools/lucene-cli/CommandLine/CommandParsingException.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Lucene.Net.Cli.CommandLine +{ + public class CommandParsingException : Exception + { + public CommandParsingException(CommandLineApplication command, string message) + : base(message) + { + Command = command; + } + + public CommandLineApplication Command { get; } + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/CommandLineOptions.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/CommandLineOptions.cs b/src/tools/lucene-cli/CommandLineOptions.cs new file mode 100644 index 0000000..93c7455 --- /dev/null +++ b/src/tools/lucene-cli/CommandLineOptions.cs @@ -0,0 +1,74 @@ +using System; + +namespace Lucene.Net.Cli +{ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + public class CommandLineOptions + { + public static int Parse(string[] args) + { + var options = new CommandLineOptions(); + + var cmd = new RootCommand.Configuration(options); + + try + { + return cmd.Execute(args); + } + catch (Exception ex) + { + Console.WriteLine(Resources.Strings.GeneralExceptionMessage + Environment.NewLine + ex.ToString()); + cmd.ShowHint(); + return 1; + } + } + + //public static readonly string VERBOSE_OPTION_VALUE_NAME = "verbose"; + //public static readonly string DIRECTORY_TYPE_VALUE_NAME = "directoryType"; + //public static readonly string INDEX_DIRECTORY_ARGUMENT_ID = "indexDirectory"; + + //public CommandOption VerboseOption = new CommandOption("-v|--verbose", CommandOptionType.NoValue) + //{ + // Description = Resources.Strings.VerboseOptionDescription, + // ValueName = VERBOSE_OPTION_VALUE_NAME + //}; + //public CommandArgument IndexDirectoryArgument = new CommandArgument() + //{ + // Name = "[<INDEX-DIRECTORY>]", + // Description = Resources.Strings.IndexDirectoryArgumentDescription, + // Id = INDEX_DIRECTORY_ARGUMENT_ID + //}; + //public string IndexDirectory + //{ + // get + // { + // // Return current directory if index directory not supplied. + // return string.IsNullOrWhiteSpace(IndexDirectoryArgument.Value) ? + // System.AppContext.BaseDirectory : + // IndexDirectoryArgument.Value; + // } + //} + + //public CommandOption DirectoryTypeOption = new CommandOption("-dir|-dir-impl|--dir-impl|--directory-type", CommandOptionType.SingleValue) + //{ + // Description = Resources.Strings.DirectoryTypeOptionDescription, + // ValueName = DIRECTORY_TYPE_VALUE_NAME + //}; + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/ConfigurationBase.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/ConfigurationBase.cs b/src/tools/lucene-cli/ConfigurationBase.cs new file mode 100644 index 0000000..28e8de5 --- /dev/null +++ b/src/tools/lucene-cli/ConfigurationBase.cs @@ -0,0 +1,130 @@ +using Lucene.Net.Cli.CommandLine; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Lucene.Net.Cli +{ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + public abstract class ConfigurationBase : CommandLineApplication + { + private static Assembly thisAssembly = typeof(ConfigurationBase).GetTypeInfo().Assembly; + protected static string HELP_VALUE_NAME = "help"; + + protected ConfigurationBase() + //: base(throwOnUnexpectedArg: false) + { + var help = this.HelpOption("-?|-h|--help"); + help.UniqueId = HELP_VALUE_NAME; + help.ShowInHelpText = false; + + this.ShortVersionGetter = () => + { + return "Lucene.Net Command Line Utility, Version: " + thisAssembly + .GetCustomAttribute<AssemblyInformationalVersionAttribute>() + .InformationalVersion; + }; + + this.LongVersionGetter = () => + { + return ShortVersionGetter(); + }; + } + + public override void OnExecute(Func<int> invoke) + { + base.OnExecute(() => + { + if (this.GetOptionByUniqueId(HELP_VALUE_NAME).HasValue()) + { + this.ShowHelp(); + return 1; + } + try + { + return invoke(); + } + catch (ArgumentException e) + { + // Rather than writing to console, the + // utilities are now throwing ArgumentException + // if the args cannot be parsed. + this.ShowHint(); + this.ShowHelp(); + return 1; + } + }); + } + + public Action<string[]> Main { get; set; } + + public CommandOption GetOptionByUniqueId(string uniqueId) + { + return this.Options.FirstOrDefault(o => o.UniqueId == uniqueId); + } + + public CommandOption GetOption<T>() + { + return this.Options.FirstOrDefault(o => typeof(T).IsAssignableFrom(o.GetType())); + } + + public CommandArgument GetArgument<T>() + { + return this.Arguments.FirstOrDefault(o => typeof(T).IsAssignableFrom(o.GetType())); + } + + /// <summary> + /// Gets the resource with a specific name. It is automatically + /// prefixed by the current command name. + /// </summary> + /// <param name="resourceName"></param> + /// <returns></returns> + protected string FromResource(string resourceName) + { + return Resources.Strings.ResourceManager.GetString(this.GetType().DeclaringType.Name + resourceName); + } + + public void ShowNotEnoughArguments(int minimum) + { + Out.WriteLine(Resources.Strings.NotEnoughArguments, minimum); + } + + public bool ValidateArguments(int minimum) + { + var args = GetNonNullArguments(); + + if (args.Length < minimum) + { + this.ShowNotEnoughArguments(minimum); + this.ShowHelp(); + return false; + } + return true; + } + + public string[] GetNonNullArguments() + { + return this.Arguments + .Where(a => !string.IsNullOrWhiteSpace(a.Value)) + .SelectMany(a => a.MultipleValues ? a.Values : new List<string> { a.Value }) + .ToArray(); + } + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/ICommand.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/ICommand.cs b/src/tools/lucene-cli/ICommand.cs new file mode 100644 index 0000000..b84e8d6 --- /dev/null +++ b/src/tools/lucene-cli/ICommand.cs @@ -0,0 +1,27 @@ +namespace Lucene.Net.Cli +{ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /// <summary> + /// A console command. + /// </summary> + public interface ICommand + { + int Run(ConfigurationBase cmd); + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/Program.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/Program.cs b/src/tools/lucene-cli/Program.cs new file mode 100644 index 0000000..9ef9869 --- /dev/null +++ b/src/tools/lucene-cli/Program.cs @@ -0,0 +1,35 @@ +using System; + +namespace Lucene.Net.Cli +{ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + public class Program + { + public static int Main(string[] args) + { + int result = CommandLineOptions.Parse(args); + +#if DEBUG + Console.ReadKey(); +#endif + + return result; + } + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/Properties/AssemblyInfo.cs ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/Properties/AssemblyInfo.cs b/src/tools/lucene-cli/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..07a71d6 --- /dev/null +++ b/src/tools/lucene-cli/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("lucene-cli")] +[assembly: AssemblyDescription( + "Lucene.Net maintenance utilities and demos.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyDefaultAlias("Lucene.Net.Cli")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d5f3414e-e743-4dca-a50a-da3278a2ba2b")] http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9e389540/src/tools/lucene-cli/Properties/launchSettings.json ---------------------------------------------------------------------- diff --git a/src/tools/lucene-cli/Properties/launchSettings.json b/src/tools/lucene-cli/Properties/launchSettings.json new file mode 100644 index 0000000..1c0ef57 --- /dev/null +++ b/src/tools/lucene-cli/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "lucene-cli": { + "commandName": "Project" + } + } +} \ No newline at end of file
