public string GetAnyArgName_OrDefault(string defaultRet, params string[] args) { string ret = GetAnyArgName(args); if (StringSupportMethods.IsStringNullOrWhitespace(ret)) { return(defaultRet); } else { return(ret); } }
/// <summary> /// For argument aliases (like -n or -num) /// </summary> public string GetArgValue(params string[] argAliases) { foreach (string s in argAliases) { CmdArg argInfo; string value = GetArgValue(s, out argInfo); if (!StringSupportMethods.IsStringNullOrWhitespace(value)) { return(value); } } return(""); }
public static bool EndsWithAny_NoCase(string input, string[] startsWithStrings) { input = input.Trim(); foreach (string s in startsWithStrings) { if (!StringSupportMethods.IsStringNullOrWhitespace(s)) // EndsWith returns true for ending with "" blank strings when the string has data. { if (input.EndsWith(s, StringComparison.CurrentCultureIgnoreCase)) { return(true); } } } return(false); }
/// <summary> /// Returns the specific string that the supplied string starts with /// </summary> public static string FindStartingString(string input, string[] startingPossibilites) { List <string> strings = new List <string>(startingPossibilites); List <string> stringsByLength = strings.OrderBy(x => x.Length).Reverse().ToList(); foreach (string s in stringsByLength) { if (!StringSupportMethods.IsStringNullOrWhitespace(s)) // EndsWith returns true for ending with "" blank strings when the string has data. Doing this check just in case StartsWith does too { if (input.StartsWith(s, StringComparison.CurrentCultureIgnoreCase)) { return(s); } } } return(""); }
/// <summary> /// Splits out the combined File Name + Command Line into their own string /// </summary> public static void SplitCommandLineInto(string cmdLineAndFile, out string fileName, out string commandLine, bool expandEnvVar = true) { // Output params fileName = ""; commandLine = ""; // 1. To be nice, if the user didn't quote the path to the file with spaces in it, then // Systematically search for a file in existence with spaces, until either 1 is found, or none is found and the best case scenario is selected // int startingArrayIndexForCmdLine = 1; string fileNameChecking = ""; string[] splitAppArgs = CmdLineHelper.SupportMethods.CommandLineToArgs(cmdLineAndFile, true, false); if (splitAppArgs.Length == 0) { // Only a file Name was supplied if there are no spaces fileName = cmdLineAndFile; } else { for (int i = 0; i < splitAppArgs.Length; i++) { fileNameChecking += ExpandEnvVar(splitAppArgs[i].Replace("\"", ""), expandEnvVar); if (File.Exists(fileNameChecking)) { startingArrayIndexForCmdLine = i + 1; fileName = fileNameChecking; break; } fileNameChecking += " "; } } // Set the output parameter if (File.Exists(fileNameChecking)) { fileName = fileNameChecking; } // // Otherwise, if the file name wasn't obtained, go select the first non-blank filename: // if (StringSupportMethods.IsStringNullOrWhitespace(fileName)) { // Build up all the rest of the items in the array specified within the -a "cmd.exe /s" switch for (int i = 0; i < splitAppArgs.Length; i++) { if (StringSupportMethods.IsStringNullOrWhitespace(fileName)) { // skip to the first token and treat it as the file name if (StringSupportMethods.IsStringNullOrWhitespace(splitAppArgs[i])) { continue; } else { startingArrayIndexForCmdLine = i + 1; fileName = splitAppArgs[i]; } } } } // // Build the command line manually // for (int i = startingArrayIndexForCmdLine; i < splitAppArgs.Length; i++) { // Added support for environment variables (such as for 7z which wont support them) commandLine += ExpandEnvVar(splitAppArgs[i], expandEnvVar) + " "; } fileName = fileName.Trim(); // Necessary or else a space will be at the end of the CmdLine when specifying [a] [a] for example. Can cause issues with some applications like Tweaking Registry Backup commandLine = commandLine.Trim(); }
/// <summary> /// Core method - Initalizes an argument list (which will be returned) given a string array of command line arguments /// </summary> private List <CmdArg> ParseArgs(string[] argArray, bool includeSpecialCommands = false) { // Note: // 'argArray' needs to be a linear list of Arg Name (ex: "-Say") followed next by its Arg Value (ex: "Hello"), Name, Value, ... List <CmdArg> retList = new List <CmdArg>(); CmdArg currArg = null; bool currentArgAdded = true; // (true: start out like a previous argument was successfully added) // Flag used to prevent checking for "-" or "/" in parameters that are nested and meant to be passed on to the next application's command line, // which will also use the same special characters. bool passEverything = false; bool startingPassAllIndicator = false; bool startedPassAllRightNow = false; // Now adding support to [A] nesting like [A1] [A1], [A2] [A2] (possibly add support for any number later)... // See ParseArgs_NestingCheck // Arguments here within the argArray will be separated out by spaces (like a split on " ") // So the logic in this loop determines which arguments to combine, and which ones are separate based on special argument characters encountered: // Those are "-" or "/". bool startOfNewArgument = true; bool turnOffSlashSwitch = false; bool turnOffDashSwitch = false; for (int i = 0; i < argArray.Length; i++) { var argStr = argArray[i].Trim(); // 8-9-17 // Always reset -- only set on first encountered [a] if (startingPassAllIndicator) { // Reset this flag startingPassAllIndicator = false; string startingString = StringSupportMethods.FindStartingString(argStr, PASS_ALL_INDICATORS); if (!StringSupportMethods.IsStringNullOrWhitespace(startingString) && startingString.ToLower().Trim() == argStr.ToLower().Trim()) { // In this case, this will be the ending [a], so just stop passEverything, and this loop execution can be completly skipped passEverything = false; continue; } } // Always reset startedPassAllRightNow = false; // Always recheck to see if the current argument begins with the "include-everything", which is a single double-quote (but the argName / caller has to escape it via 3 double quotes: """) // This is re-evaluated below if (passEverything == false) { // Special args: // -disableSlash // -disableDash // If found, then "-" or "/" will not be considered the start of a new argument // This is an alternative to specifying [a] or [all] around a string such as: -a [a] cmd.exe /k ... [a] --> -noSlash -a cmd.exe /k ... if (!includeSpecialCommands && (DisableSlash || argStr.ToLower() == "-disableslash")) { turnOffSlashSwitch = true; WasDashOrSlashDisabled = true; continue; } else if (!includeSpecialCommands && (DisableDash || argStr.ToLower() == "-disabledash")) { turnOffDashSwitch = true; WasDashOrSlashDisabled = true; continue; } // If a quote is encountered, it means that the user escaped it in the command line by passing it as """ // When this occurs it means to pass everything through in this current parameter (meaning I can nest in other single "-" or "/" as special chars to a sub-application that also uses these, like cmd.exe, or another application that I created which uses this parser) passEverything = StringSupportMethods.StartsWithAny_NoCase(argStr, PASS_ALL_INDICATORS); if (passEverything && currArg != null) { currArg.FullArgSupplied = true; startingPassAllIndicator = true; // Prevention of setting disablePassEverything to true below when checking to see if the string is the ending [A] startedPassAllRightNow = true; } //startOfNewArgument = true; } else { // Always disable startingPassAllIndicator unless it was just enabled for the first [A] if (!startedPassAllRightNow) { startingPassAllIndicator = false; } } // // New arg or not? // // Start a new arg if the current string begins with a "-" or "/" if (passEverything == false && ((!turnOffDashSwitch && argStr.StartsWith("-")) || (!turnOffSlashSwitch && argStr.StartsWith("/")))) { // // New argument to add // startOfNewArgument = true; // If a new arg was encountered before the previous argument's value, then simply add in the previous argument if (currentArgAdded == false) { if (currArg != null) { retList.Add(currArg); currentArgAdded = true; } } // Start the new arg currArg = new CmdArg(); if (argStr.Length > 1) { // Store the argument Name currArg.ArgDelimiterUsed = argStr.Substring(0, 1); // the "-" or "/" char currArg.Name = argStr.Substring(1); // Remove the "-" or "/" start of the argument Name currentArgAdded = false; } } else { // // Not a new argument (so append this onto the current one in progress) // // If this is not a new arg Name, then this string is added to the current argument's value if (currArg != null) { // Note: // If the current argument has data in it from the previous array value, then that means there was a space in the cmdline parsed from CommandLineToArgvW // and hence can be preserved by adding 1 space // HOPEFULLY only is one space in the Name, if 2 spaces back-to-back, then this could be problematic, as we wouldn't know, unless implementing our own CommandLineToArgvW parser bool disablePassEverything = false; if ( !startOfNewArgument && // Prevent the starting [a] from being looked at as the ending [a] (not sufficient, need the next check too: !startedPassAllRightNow) !startedPassAllRightNow && // Set to true if this is the starting [a] Added - 8-9-17 passEverything && StringSupportMethods.EndsWithAny_NoCase(argStr, PASS_ALL_INDICATORS) ) { // Stop the PassEverything sequence if this argument ends with the quote disablePassEverything = true; argStr = StringSupportMethods.ReplaceAny_NoCase(argStr, PASS_ALL_INDICATORS, ""); } else if (currArg.FullArgSupplied) { // 8-9-17 // Passing something like this: -u0 -s [a]-1[a] -a [a] C:\Program Files (x86)\Tweaking.com\Registry Backup\TweakingRegistryBackup.exe /silent [a] // Causes the pass-all to occur all the way to the end after the first set of [a] [a], since there is no space in that value (between the [a]s). // Hence its all grouped under -s, and no -a is seen as another argument // Therefore a check must be done to see if the string ends in [a] (excluding if its the starting [a] in the event that the user did pass in spaces) string startingString = StringSupportMethods.FindStartingString(argStr, PASS_ALL_INDICATORS); if (!StringSupportMethods.IsStringNullOrWhitespace(startingString) && // MUST check for IsNullOrWhitespace, otherwise EndsWith will return true for ending with "" (a blank string)... probably the same for starts with argStr.Length > startingString.Length) { // This shows that this scenario is possibly like this: [a]-1[a] That is, [a]-1[a] length is greater than [a] // Must check the length for it being grater, otherwise having the argStr just be [a] (that is a space was added between this and the actual full string), would incorrectly satisfy StartsWith and EndsWith // However its possible that a space was added, so therefore we now must check to see if the string ends with this indicator if (argStr.EndsWith(startingString, StringComparison.CurrentCultureIgnoreCase)) { // Go ahead and just disable Pass Everything now (we could just set startingPassAllIndicator = false to satisfy the condition below, but we truly just want disablePassEverything to disable it at the end of this method) disablePassEverything = true; } } // Fix / Check - 11-13-16 // Before replacing [A] or [ALL], need to check if the string already ends with [A] or [ALL] in the event that no space was provided in the trailing [a] or [all] // Ex: -s [a]-2[a] -d hello -s would incorrectly include everything else // -s [a]-a [a] -d hello This would work, but we don't want to rely on the user having to remember that a space in the trailing [a] is required if ( passEverything && !startingPassAllIndicator && // Ensures that this is not turned off on the first encountered [A] // Using EndsWith in the event that no space was provided at the end and the [A] is right next to the last char and is included in this token StringSupportMethods.EndsWithAny_NoCase(argStr, PASS_ALL_INDICATORS) ) { disablePassEverything = true; } argStr = StringSupportMethods.ReplaceAny_NoCase(argStr, PASS_ALL_INDICATORS, ""); startOfNewArgument = false; // Toggle off now, since this is going to be a string of arguments until an ending [a] is found } // // Update the arg value // // Always add a space between the args, which is possible until a new switch is encountered - 11-1-16 if (!StringSupportMethods.IsStringNullOrWhitespace(currArg.Value)) // Will be blank if this was a [a] { argStr = " " + argStr; } // Need to trim off any quotes, which may have been supplied in order to pass in a negative value, like "-1", which will be undesirably returned when called GetArgValue (since it cannot be parsed into an int) if (!passEverything) { argStr = StringSupportMethods.TrimQuotes(argStr); } // Store the Argument Value currArg.Value += argStr; // Optionally add it in the list -- Only add in this argument if its a brand new one if (startOfNewArgument && !StringSupportMethods.IsStringNullOrWhitespace(currArg.Value.Trim())) { retList.Add(currArg); // [Re]set flags: currentArgAdded = true; startOfNewArgument = false; } // Lastly, toggle off the special case flag once complete if (disablePassEverything) { passEverything = false; } } } } // foreach arg // If this loop finished (no more arguments), and the last argument wasn't added (it didn't have a value) then add it in if (currentArgAdded == false) { if (currArg != null) { retList.Add(currArg); } } // Ensure everything is tidy for (int i = 0; i < retList.Count; i++) { retList[i].Value = retList[i].Value.Trim(); } return(retList); }
/// <summary> /// Initializes an argument list (which will be returned) given a single linear string as the command line /// </summary> private List <CmdArg> ParseArgs_NestingCheck(string commandLine, bool includeSpecialCommands = false) { // TO TEST FURTHER bool isAll = StringSupportMethods.ContainsNoCase(commandLine, "[ALL1]"); bool nestingSupplied = isAll || StringSupportMethods.ContainsNoCase(commandLine, "[A1]"); //bool all2 = ContainsNoCase(commandLine, "[ALL2]") || ContainsNoCase(commandLine, "[A2]"); if (nestingSupplied) { string toFind = ""; if (isAll) { toFind = "[ALL1]"; } else { toFind = "[A1]"; } int startPos = commandLine.IndexOf(toFind, StringComparison.CurrentCultureIgnoreCase); if (startPos >= 0) { startPos += toFind.Length; // Skip over this int endPos = commandLine.LastIndexOf(toFind, StringComparison.CurrentCultureIgnoreCase); if (endPos > startPos) { int posOfSwitchStart = commandLine.LastIndexOf("-", startPos, StringComparison.CurrentCultureIgnoreCase); int posOfWhiteSpace = commandLine.IndexOf(" ", posOfSwitchStart); if (posOfSwitchStart >= 0 && posOfWhiteSpace >= 0) { string[] switchExtracts = StringSupportMethods.ExtractAround(commandLine, posOfSwitchStart, posOfWhiteSpace, 1); if (switchExtracts.Length == 3) { string[] extracts = StringSupportMethods.ExtractAround(commandLine, startPos, endPos, toFind.Length); string before = StringSupportMethods.ReplaceNoCase(extracts[0], toFind, ""); string nestedCommandLine = extracts[1]; string after = StringSupportMethods.ReplaceNoCase(extracts[2], toFind, ""); // Remove the switch, which will be stored manually below (and should be kept out of what will be parsed next) before = StringSupportMethods.SubstringIndex(before, 0, posOfSwitchStart - 1); // Remove this process as part of the command line before = SupportMethods.SplitOutCmdLine(before); List <CmdArg> list = ParseArgs(SupportMethods.CommandLineToArgs(before.Trim() + " " + after.Trim()), includeSpecialCommands); string argName = switchExtracts[1].Replace("-", ""); list.Add(new CmdArg { FullArgSupplied = true, // Set indicator Name = argName.Substring(1).Trim(), Value = nestedCommandLine }); return(list); } } } } } string[] args = SupportMethods.CommandLineToArgs(commandLine); WasAnyArgSupplied = (args.Length >= 1); return(ParseArgs(args, includeSpecialCommands)); }