using System; using System.Collections.Generic; using GitHub.DistributedTask.Logging; using GitHub.Runner.Sdk; // // Pattern: // cmd1 cmd2 --arg1 arg1val --aflag --arg2 arg2val // namespace GitHub.Runner.Common { public sealed class CommandLineParser { private ISecretMasker _secretMasker; private Tracing _trace; public List Commands { get; } public HashSet Flags { get; } public Dictionary Args { get; } public HashSet SecretArgNames { get; } private bool HasArgs { get; set; } public CommandLineParser(IHostContext hostContext, string[] secretArgNames) { _secretMasker = hostContext.SecretMasker; _trace = hostContext.GetTrace(nameof(CommandLineParser)); Commands = new List(); Flags = new HashSet(StringComparer.OrdinalIgnoreCase); Args = new Dictionary(StringComparer.OrdinalIgnoreCase); SecretArgNames = new HashSet(secretArgNames ?? new string[0], StringComparer.OrdinalIgnoreCase); } public bool IsCommand(string name) { bool result = false; if (Commands.Count > 0) { result = String.Equals(name, Commands[0], StringComparison.CurrentCultureIgnoreCase); } return result; } public void Parse(string[] args) { _trace.Info(nameof(Parse)); ArgUtil.NotNull(args, nameof(args)); _trace.Info("Parsing {0} args", args.Length); string argScope = null; foreach (string arg in args) { _trace.Info("parsing argument"); HasArgs = HasArgs || arg.StartsWith("--"); _trace.Info("HasArgs: {0}", HasArgs); if (string.Equals(arg, "/?", StringComparison.Ordinal)) { Flags.Add("help"); } else if (!HasArgs) { _trace.Info("Adding Command: {0}", arg); Commands.Add(arg.Trim()); } else { // it's either an arg, an arg value or a flag if (arg.StartsWith("--") && arg.Length > 2) { string argVal = arg.Substring(2); _trace.Info("arg: {0}", argVal); // this means two --args in a row which means previous was a flag if (argScope != null) { _trace.Info("Adding flag: {0}", argScope); Flags.Add(argScope.Trim()); } argScope = argVal; } else if (!arg.StartsWith("-")) { // we found a value - check if we're in scope of an arg if (argScope != null && !Args.ContainsKey(argScope = argScope.Trim())) { if (SecretArgNames.Contains(argScope)) { _secretMasker.AddValue(arg); } _trace.Info("Adding option '{0}': '{1}'", argScope, arg); // ignore duplicates - first wins - below will be val1 // --arg1 val1 --arg1 val1 Args.Add(argScope, arg); argScope = null; } } else { // // ignoring the second value for an arg (val2 below) // --arg val1 val2 // ignoring invalid things like empty - and -- // --arg val1 -- --flag _trace.Info("Ignoring arg"); } } } _trace.Verbose("done parsing arguments"); // handle last arg being a flag if (argScope != null) { Flags.Add(argScope); } _trace.Verbose("Exiting parse"); } } }