#region CPL License /* Nuclex Framework Copyright (C) 2002-2008 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.Diagnostics; namespace Nuclex.Support.Parsing { partial class CommandLine { /// Parses command line strings private class Parser { /// Initializes a new command line parser private Parser() { this.commandLine = new CommandLine(); } /// Parses a string containing command line arguments /// String that will be parsed /// The parsed command line arguments from the string public static CommandLine Parse(string commandLineString) { Parser theParser = new Parser(); theParser.parse(commandLineString); return theParser.commandLine; } /// /// Parses the provided string and adds the parameters found to /// the command line representation /// /// /// String containing the command line arguments that will be parsed /// private void parse(string commandLineString) { if(commandLineString == null) { return; } // Walk through the command line character by character and gather // the parameters and values to build the command line representation from for(int index = 0; index < commandLineString.Length; ) { // Look for the next non-whitespace character index = StringHelper.IndexNotOfAny( commandLineString, WhitespaceCharacters, index ); if(index == -1) { break; } // Parse the chunk of characters at this location and advance the index // to the next location after the chunk of characters index += parseCharacterChunk(commandLineString, index); } } /// /// Parses a chunk of characters and adds it as an option or a loose value to /// the command line representation we're building /// /// /// String containing the chunk of characters that will be parsed /// /// Index in the string at which to begin parsing /// The number of characters that were consumed private int parseCharacterChunk(string commandLineString, int index) { int startIndex = index; char currentCharacter = commandLineString[index]; switch(currentCharacter) { // Unix style argument using either '-' or "--" as its initiator case '-': { ++index; // Does the string end here? Stop parsing. if(index >= commandLineString.Length) { addValue("-"); break; } // Does another '-' follow? Might be a unix style option or a loose "--" if(commandLineString[index] == '-') { ++index; index += parsePotentialOption(commandLineString, startIndex, index); } else { // Nope, it's a normal option or a loose '-' index += parsePotentialOption(commandLineString, startIndex, index); } break; } // Windows style argument using '/' as its initiator case '/': { ++index; index += parsePotentialOption(commandLineString, startIndex, index); break; } // Quoted loose value case '"': { StringSegment value = parseQuotedValue(commandLineString, index); index += value.Count + 1; break; } // Unquoted loose value default: { StringSegment value = parseNakedValue(commandLineString, index); index += value.Count; break; } } return index - startIndex; } /// Parses a potential command line option /// String containing the command line arguments /// /// Index of the option's initiator ('-' or '--' or '/') /// /// /// Index at which the option name is supposed start (if it's an actual option) /// /// The number of characters consumed private int parsePotentialOption( string commandLineString, int initiatorStartIndex, int index ) { // If the string ends here this can only be considered as a loose value if(index >= commandLineString.Length) { addValue(commandLineString.Substring(initiatorStartIndex)); return 0; } // Look for the first character that ends the option. If it is not an actual option, // the very first character might be the end int nameEndIndex = commandLineString.IndexOfAny(OptionNameEndingCharacters, index); if(nameEndIndex == -1) { nameEndIndex = commandLineString.Length; } // If the first character of the supposed option is not valid for an option, // we have to consider this to be a loose value if(nameEndIndex == index) { // Parse normal unquoted value //parseNakedValue(commandLineString, initiatorStartIndex).Count; /* int endIndex = commandLineString.IndexOfAny(WhitespaceCharacters, index); if(endIndex == -1) { addValue(commandLineString.Substring(initiatorStartIndex)); return commandLineString.Length - index; } else { addValue( commandLineString.Substring(initiatorStartIndex, endIndex - initiatorStartIndex) ); return endIndex - index; } */ } Console.WriteLine( "Argument name: " + commandLineString.Substring(index, nameEndIndex - index) ); // TODO: Parse argument value (if provided) here!! return nameEndIndex - index; } static readonly char[] OptionNameEndingCharacters = new char[] { ' ', '\t', '=', ':', '/', '-', '+', '"' }; /// Parses a quoted value from the input string /// String the quoted value is parsed from /// Index at which the quoted value begins /// A string segment containing the parsed quoted value /// /// The returned string segment does not include the quotes. /// private static StringSegment parseQuotedValue(string commandLineString, int index) { char quoteCharacter = commandLineString[index]; ++index; int endIndex = commandLineString.IndexOf(quoteCharacter, index); if(endIndex == -1) { endIndex = commandLineString.Length; } // TODO: We don't skip the closing quote, the callee would have to detect it himself return new StringSegment(commandLineString, index, endIndex - index); } /// Parses a plain, unquoted value from the input string /// String containing the value to be parsed /// Index at which the value begins /// A string segment containing the parsed value private static StringSegment parseNakedValue(string commandLineString, int index) { int endIndex = commandLineString.IndexOfAny(WhitespaceCharacters, index); if(endIndex == -1) { endIndex = commandLineString.Length; } return new StringSegment(commandLineString, index, endIndex - index); } /// /// Determines whether the specified character is valid as the first character /// in an option /// /// Character that will be tested for validity /// True if the character is valid as the first character in an option private static bool isValidFirstCharacterInOption(char character) { const string InvalidCharacters = " \t=:/-+\""; return (InvalidCharacters.IndexOf(character) == -1); } private void addValue(string value) { Console.WriteLine("Added Value: '" + value + "'"); } /// Characters the parser considers to be whitespace private static readonly char[] WhitespaceCharacters = new char[] { ' ', '\t' }; /// Command line currently being built by the parser private CommandLine commandLine; } } } // namespace Nuclex.Support.Parsing