| File: CommandLineParser.cs | Web Access |
| Project: src\src\dotnet-svcutil\lib\src\dotnet-svcutil-lib.csproj (dotnet-svcutil-lib) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; namespace Microsoft.Tools.ServiceModel.Svcutil { internal enum SwitchType { Flag, SingletonValue, ValueList, FlagOrSingletonValue } internal class CommandSwitch { public const string AbbreviationSwitchIndicator = "-"; public const string FullSwitchIndicator = "--"; public const OperationalContext DefaultSwitchLevel = OperationalContext.Project; private static List<CommandSwitch> s_allSwitches = new List<CommandSwitch>(); public static IEnumerable<CommandSwitch> All { get { return s_allSwitches; } } public string Name { get; private set; } public string Abbreviation { get; private set; } public SwitchType SwitchType { get; private set; } public OperationalContext SwitchLevel { get; private set; } internal CommandSwitch(string name, string abbreviation, SwitchType switchType, OperationalContext switchLevel = DefaultSwitchLevel) { // ensure that name doesn't start with the switch indicators. // also convert to lower-case if (name.StartsWith(FullSwitchIndicator)) { this.Name = name.Substring(FullSwitchIndicator.Length); } else { this.Name = name; } if (abbreviation.StartsWith(AbbreviationSwitchIndicator)) { this.Abbreviation = abbreviation.Substring(AbbreviationSwitchIndicator.Length); } else { this.Abbreviation = abbreviation; } this.SwitchType = switchType; this.SwitchLevel = switchLevel; System.Diagnostics.Debug.Assert(!s_allSwitches.Any(s => s.Equals(this)), $"A switch with name or abbreviation '{name}+{abbreviation}' has already been crated!"); s_allSwitches.Add(this); } internal static CommandSwitch FindSwitch(string name) { return CommandSwitch.All.FirstOrDefault(s => s.Equals(name)); } public override string ToString() { return string.Format(CultureInfo.InvariantCulture, "{0} {{{1}, {2}, Type={3}}}", this.GetType().Name, Name, Abbreviation, SwitchType); } internal bool Equals(string other) { string otherName; bool isAbbreviation = true; if (string.IsNullOrWhiteSpace(other)) { return false; } // If other starts with a switch indicator, we want to enforce the correct indicator (e.g. "-directory" and "--d" will return false, but "--directory" and "directory" will return true). bool enforceSwitchType = other.StartsWith(FullSwitchIndicator) || other.StartsWith(AbbreviationSwitchIndicator); // Take off the switch indicators and figure out if this is an abbreviation or not. // also convert to lower-case if (other.StartsWith(FullSwitchIndicator)) { isAbbreviation = false; otherName = other.Substring(FullSwitchIndicator.Length).ToLowerInvariant(); } else if (other.StartsWith(AbbreviationSwitchIndicator)) { isAbbreviation = true; otherName = other.Substring(AbbreviationSwitchIndicator.Length).ToLowerInvariant(); } else { otherName = other.ToLowerInvariant(); } var thisAbbr = Abbreviation.ToLowerInvariant(); var thisName = Name.ToLowerInvariant(); if (enforceSwitchType) { return isAbbreviation ? thisAbbr == otherName : thisName == otherName; } else { return thisAbbr == otherName || thisName == otherName; } } } internal static class CommandParser { // Instead of throwing exceptions we pass the first exception that happens as an out parameter. This lets us parse as many // arguments as we can correctly so we can respect options like noLogo and verbosity when showing errors. internal static CommandProcessorOptions ParseCommand(string[] cmd) { var options = new CommandProcessorOptions(); // work around https://github.com/dotnet/core-setup/issues/619 to ignore additionalprobingpath cmd = cmd.Where(c => !c.ToLowerInvariant().Contains("additionalprobingpath")).ToArray(); for (int i = 0; i < cmd.Length; i++) { string arg = cmd[i]?.Trim('\"').Trim(); if (string.IsNullOrWhiteSpace(arg)) { continue; } // if argument does not start with switch indicator, place into "default" arguments if (IsArgumentValue(arg)) { SetValue(options, CommandProcessorOptions.InputsKey, Environment.ExpandEnvironmentVariables(arg)); continue; } // check if this switch exists in the list of possible switches var argSwitch = CommandSwitch.FindSwitch(arg); var argValue = cmd.Length > i + 1 ? cmd[i + 1] : null; if (argSwitch == null) { options.AddError(string.Format(SR.ErrUnknownSwitchFormat, arg)); continue; } // we have a valid switch, now validate value. // make sure there's a value for the parameter. switch (argSwitch.SwitchType) { case SwitchType.Flag: argValue = "true"; break; case SwitchType.FlagOrSingletonValue: argValue = (argValue == null || !IsArgumentValue(argValue)) ? string.Empty : argValue; break; case SwitchType.SingletonValue: case SwitchType.ValueList: if (string.IsNullOrWhiteSpace(argValue) || !IsArgumentValue(argValue)) { options.AddError(string.Format(SR.ErrArgumentWithoutValue, argSwitch.Name)); continue; } break; } // check if switch is allowed to be specified multiple times // if not and it has already been specified and a new value has been parsed. if (argSwitch.SwitchType != SwitchType.ValueList && options.GetValue<object>(argSwitch.Name) != null) { options.AddError(string.Format(SR.ErrSingleUseSwitchFormat, argSwitch.Name)); continue; } SetValue(options, argSwitch.Name, argValue.Trim('\"').Trim()); if (argSwitch.SwitchType != SwitchType.Flag && argValue != string.Empty && IsArgumentValue(argValue)) { i++; // move to next input. } } return options; } private static void SetValue(CommandProcessorOptions options, string optionName, object value) { try { options.SetValue(optionName, value); } catch (Exception ex) { if (Utils.IsFatalOrUnexpected(ex)) throw; options.AddError(ex); } } private static bool IsArgumentValue(string arg) { return !(arg.StartsWith(CommandSwitch.FullSwitchIndicator) || arg.StartsWith(CommandSwitch.AbbreviationSwitchIndicator)); } } }