#region License // // Copyright 2015-2013 Giacomo Stelluti Scala // // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #endregion #region Using Directives using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using CommandLine.Infrastructure; using CommandLine.Parsing; using CommandLine.Text; #endregion namespace CommandLine { /// /// Provides methods to parse command line arguments. /// public sealed class Parser : IDisposable { /// /// Default exit code (1) used by /// and overloads. /// public const int DefaultExitCodeFail = 1; private static readonly Parser DefaultParser = new Parser(true); private readonly ParserSettings _settings; private bool _disposed; /// /// Initializes a new instance of the class. /// public Parser() { _settings = new ParserSettings { Consumed = true }; } /// /// Initializes a new instance of the class, /// configurable with a object. /// /// The object is used to configure /// aspects and behaviors of the parser. [Obsolete("Use constructor that accepts Action.")] public Parser(ParserSettings settings) { Assumes.NotNull(settings, "settings", SR.ArgumentNullException_ParserSettingsInstanceCannotBeNull); if (settings.Consumed) { throw new InvalidOperationException(SR.InvalidOperationException_ParserSettingsInstanceCanBeUsedOnce); } _settings = settings; _settings.Consumed = true; } /// /// Initializes a new instance of the class, /// configurable with using a delegate. /// /// The delegate used to configure /// aspects and behaviors of the parser. public Parser(Action configuration) { Assumes.NotNull(configuration, "configuration", SR.ArgumentNullException_ParserSettingsDelegateCannotBeNull); _settings = new ParserSettings(); configuration.Invoke(Settings); _settings.Consumed = true; } [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "singleton", Justification = "The constructor that accepts a boolean is designed to support default singleton, the parameter is ignored")] private Parser(bool singleton) : this(with => { with.CaseSensitive = false; with.MutuallyExclusive = false; with.HelpWriter = Console.Error; with.ParsingCulture = CultureInfo.InvariantCulture; }) { } /// /// Finalizes an instance of the class. /// ~Parser() { Dispose(false); } /// /// Gets the singleton instance created with basic defaults. /// public static Parser Default { get { return DefaultParser; } } /// /// Gets the instance that implements in use. /// public ParserSettings Settings { get { return _settings; } } /// /// Parses a array of command line arguments, setting values in /// parameter instance's public fields decorated with appropriate attributes. /// /// A array of command line arguments. /// An instance used to receive values. /// Parsing rules are defined using derived types. /// True if parsing process succeed. /// Thrown if is null. /// Thrown if is null. public bool ParseArguments(string[] args, object options) { Assumes.NotNull(args, "args", SR.ArgumentNullException_ArgsStringArrayCannotBeNull); Assumes.NotNull(options, "options", SR.ArgumentNullException_OptionsInstanceCannotBeNull); return DoParseArguments(args, options); } /// /// Parses a array of command line arguments with verb commands, setting values in /// parameter instance's public fields decorated with appropriate attributes. /// This overload supports verb commands. /// /// A array of command line arguments. /// An instance used to receive values. /// Parsing rules are defined using derived types. /// Delegate executed to capture verb command name and instance. /// True if parsing process succeed. /// Thrown if is null. /// Thrown if is null. /// Thrown if is null. public bool ParseArguments(string[] args, object options, Action onVerbCommand) { Assumes.NotNull(args, "args", SR.ArgumentNullException_ArgsStringArrayCannotBeNull); Assumes.NotNull(options, "options", SR.ArgumentNullException_OptionsInstanceCannotBeNull); Assumes.NotNull(options, "onVerbCommand", SR.ArgumentNullException_OnVerbDelegateCannotBeNull); object verbInstance = null; var result = DoParseArgumentsVerbs(args, options, ref verbInstance); onVerbCommand(args.FirstOrDefault() ?? string.Empty, result ? verbInstance : null); return result; } /// /// Parses a array of command line arguments, setting values in /// parameter instance's public fields decorated with appropriate attributes. If parsing fails, the method invokes /// the delegate, if null exits with . /// /// A array of command line arguments. /// An object's instance used to receive values. /// Parsing rules are defined using derived types. /// The delegate executed when parsing fails. /// True if parsing process succeed. /// Thrown if is null. /// Thrown if is null. public bool ParseArgumentsStrict(string[] args, object options, Action onFail = null) { Assumes.NotNull(args, "args", SR.ArgumentNullException_ArgsStringArrayCannotBeNull); Assumes.NotNull(options, "options", SR.ArgumentNullException_OptionsInstanceCannotBeNull); if (!DoParseArguments(args, options)) { InvokeAutoBuildIfNeeded(options); if (onFail == null) { Environment.Exit(DefaultExitCodeFail); } else { onFail(); } return false; } return true; } /// /// Parses a array of command line arguments with verb commands, setting values in /// parameter instance's public fields decorated with appropriate attributes. If parsing fails, the method invokes /// the delegate, if null exits with . /// This overload supports verb commands. /// /// A array of command line arguments. /// An instance used to receive values. /// Parsing rules are defined using derived types. /// Delegate executed to capture verb command name and instance. /// The delegate executed when parsing fails. /// True if parsing process succeed. /// Thrown if is null. /// Thrown if is null. /// Thrown if is null. public bool ParseArgumentsStrict(string[] args, object options, Action onVerbCommand, Action onFail = null) { Assumes.NotNull(args, "args", SR.ArgumentNullException_ArgsStringArrayCannotBeNull); Assumes.NotNull(options, "options", SR.ArgumentNullException_OptionsInstanceCannotBeNull); Assumes.NotNull(options, "onVerbCommand", SR.ArgumentNullException_OnVerbDelegateCannotBeNull); object verbInstance = null; if (!DoParseArgumentsVerbs(args, options, ref verbInstance)) { onVerbCommand(args.FirstOrDefault() ?? string.Empty, null); InvokeAutoBuildIfNeeded(options); if (onFail == null) { Environment.Exit(DefaultExitCodeFail); } else { onFail(); } return false; } onVerbCommand(args.FirstOrDefault() ?? string.Empty, verbInstance); return true; } /// /// Frees resources owned by the instance. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By design")] internal static object InternalGetVerbOptionsInstanceByName(string verb, object target, out bool found) { found = false; if (string.IsNullOrEmpty(verb)) { return target; } var pair = ReflectionHelper.RetrieveOptionProperty(target, verb); found = pair != null; return found ? pair.Left.GetValue(target, null) : target; } private static void SetParserStateIfNeeded(object options, IEnumerable errors) { if (!options.CanReceiveParserState()) { return; } var property = ReflectionHelper.RetrievePropertyList(options)[0].Left; var parserState = property.GetValue(options, null); if (parserState != null) { if (!(parserState is IParserState)) { throw new InvalidOperationException(SR.InvalidOperationException_ParserStateInstanceBadApplied); } if (!(parserState is ParserState)) { throw new InvalidOperationException(SR.InvalidOperationException_ParserStateInstanceCannotBeNotNull); } } else { try { property.SetValue(options, new ParserState(), null); } catch (Exception ex) { throw new InvalidOperationException(SR.InvalidOperationException_ParserStateInstanceBadApplied, ex); } } var state = (IParserState)property.GetValue(options, null); foreach (var error in errors) { state.Errors.Add(error); } } private static StringComparison GetStringComparison(ParserSettings settings) { return settings.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; } private bool DoParseArguments(string[] args, object options) { var pair = ReflectionHelper.RetrieveMethod(options); var helpWriter = _settings.HelpWriter; if (pair != null && helpWriter != null) { // If help can be handled is displayed if is requested or if parsing fails if (ParseHelp(args, pair.Right) || !DoParseArgumentsCore(args, options)) { string helpText; HelpOptionAttribute.InvokeMethod(options, pair, out helpText); helpWriter.Write(helpText); return false; } return true; } return DoParseArgumentsCore(args, options); } private bool DoParseArgumentsCore(string[] args, object options) { var hadError = false; var optionMap = OptionMap.Create(options, _settings); optionMap.SetDefaults(); var valueMapper = new ValueMapper(options, _settings.ParsingCulture); var arguments = new StringArrayEnumerator(args); while (arguments.MoveNext()) { var argument = arguments.Current; if (string.IsNullOrEmpty(argument)) { continue; } var parser = ArgumentParser.Create(argument, _settings.IgnoreUnknownArguments); if (parser != null) { var result = parser.Parse(arguments, optionMap, options); if ((result & PresentParserState.Failure) == PresentParserState.Failure) { SetParserStateIfNeeded(options, parser.PostParsingState); hadError = true; continue; } if ((result & PresentParserState.MoveOnNextElement) == PresentParserState.MoveOnNextElement) { arguments.MoveNext(); } } else if (valueMapper.CanReceiveValues) { if (!valueMapper.MapValueItem(argument)) { hadError = true; } } } hadError |= !optionMap.EnforceRules(); return !hadError; } private bool DoParseArgumentsVerbs(string[] args, object options, ref object verbInstance) { var verbs = ReflectionHelper.RetrievePropertyList(options); var helpInfo = ReflectionHelper.RetrieveMethod(options); if (args.Length == 0) { if (helpInfo != null || _settings.HelpWriter != null) { DisplayHelpVerbText(options, helpInfo, null); } return false; } var optionMap = OptionMap.Create(options, verbs, _settings); if (TryParseHelpVerb(args, options, helpInfo, optionMap)) { return false; } var verbOption = optionMap[args.First()]; // User invoked a bad verb name if (verbOption == null) { if (helpInfo != null) { DisplayHelpVerbText(options, helpInfo, null); } return false; } verbInstance = verbOption.GetValue(options); if (verbInstance == null) { // Developer has not provided a default value and did not assign an instance verbInstance = verbOption.CreateInstance(options); } var verbResult = DoParseArgumentsCore(args.Skip(1).ToArray(), verbInstance); if (!verbResult && helpInfo != null) { // Particular verb parsing failed, we try to print its help DisplayHelpVerbText(options, helpInfo, args.First()); } return verbResult; } private bool ParseHelp(string[] args, HelpOptionAttribute helpOption) { var caseSensitive = _settings.CaseSensitive; foreach (var arg in args) { if (helpOption.ShortName != null) { if (ArgumentParser.CompareShort(arg, helpOption.ShortName, caseSensitive)) { return true; } } if (string.IsNullOrEmpty(helpOption.LongName)) { continue; } if (ArgumentParser.CompareLong(arg, helpOption.LongName, caseSensitive)) { return true; } } return false; } private bool TryParseHelpVerb(string[] args, object options, Pair helpInfo, OptionMap optionMap) { var helpWriter = _settings.HelpWriter; if (helpInfo != null && helpWriter != null) { if (string.Compare(args[0], helpInfo.Right.LongName, GetStringComparison(_settings)) == 0) { // User explicitly requested help var verb = args.FirstOrDefault(); if (verb != null) { var verbOption = optionMap[verb]; if (verbOption != null) { if (verbOption.GetValue(options) == null) { // We need to create an instance also to render help verbOption.CreateInstance(options); } } } DisplayHelpVerbText(options, helpInfo, verb); return true; } } return false; } private void DisplayHelpVerbText(object options, Pair helpInfo, string verb) { string helpText; if (verb == null) { HelpVerbOptionAttribute.InvokeMethod(options, helpInfo, null, out helpText); } else { HelpVerbOptionAttribute.InvokeMethod(options, helpInfo, verb, out helpText); } if (_settings.HelpWriter != null) { _settings.HelpWriter.Write(helpText); } } private void InvokeAutoBuildIfNeeded(object options) { if (_settings.HelpWriter == null || options.HasHelp() || options.HasVerbHelp()) { return; } // We print help text for the user _settings.HelpWriter.Write( HelpText.AutoBuild( options, current => HelpText.DefaultParsingErrorsHandler(options, current), options.HasVerbs())); } private void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { if (_settings != null) { _settings.Dispose(); } _disposed = true; } } } }