#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; using System.IO; namespace Nuclex.Support.Parsing { partial class CommandLine { /// Parses command line strings private class Parser { /// Initializes a new command line parser /// Whether the / character initiates an argument private Parser(bool windowsMode) { this.windowsMode = windowsMode; this.commandLine = new CommandLine(); } /// Parses a string containing command line arguments /// String that will be parsed /// Whether the / character initiates an argument /// The parsed command line arguments from the string public static CommandLine Parse(string commandLineString, bool windowsMode) { Console.WriteLine("Parsing '" + commandLineString + "'"); Parser theParser = new Parser(windowsMode); theParser.parseFullCommandLine(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 parseFullCommandLine(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 parseChunk(commandLineString, ref 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 void parseChunk(string commandLineString, ref 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) { this.commandLine.addValue(new StringSegment(commandLineString, startIndex, 1)); break; } // Does another '-' follow? Might be a unix style option or a loose "--" if(commandLineString[index] == '-') { ++index; } parsePotentialOption(commandLineString, startIndex, ref index); break; } // Windows style argument using '/' as its initiator case '/': { // The '/ character is only used to initiate argument on windows and can be // toggled off. The application decides, whether this is done depending on the // operating system or whether uniform behavior across platforms is desired. if(!this.windowsMode) { goto default; } ++index; parsePotentialOption(commandLineString, startIndex, ref index); break; } // Quoted loose value case '"': { parseQuotedValue(commandLineString, ref index); break; } // Unquoted loose value default: { parseNakedValue(commandLineString, ref index); break; } } } /// 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 void parsePotentialOption( string commandLineString, int initiatorStartIndex, ref int index ) { // If the string ends here this can only be considered as a loose value if(index == commandLineString.Length) { this.commandLine.addValue( new StringSegment( commandLineString, initiatorStartIndex, commandLineString.Length - initiatorStartIndex ) ); return; } int nameStartIndex = index; // Look for the first character that ends the option. If it is not an actual option, // the very first character might be the end index = commandLineString.IndexOfAny(OptionNameEndingCharacters, nameStartIndex); if(index == -1) { index = commandLineString.Length; } // If the first character of the supposed option is not valid for an option name, // we have to consider this to be a loose value if(index == nameStartIndex) { index = commandLineString.IndexOfAny(WhitespaceCharacters, index); if(index == -1) { index = commandLineString.Length; } commandLine.addValue( new StringSegment( commandLineString, initiatorStartIndex, index - initiatorStartIndex ) ); return; } parseOptionAssignment( commandLineString, initiatorStartIndex, nameStartIndex, ref index ); } /// Parses the value assignment in a command line option /// String containing the command line arguments /// /// Position of the character that started the option /// /// /// Position of the first character in the option's name /// /// Index at which the option name ended private void parseOptionAssignment( string commandLineString, int initiatorStartIndex, int nameStartIndex, ref int index ) { int nameEndIndex = index; int valueStartIndex; int valueEndIndex; if(index == commandLineString.Length) { valueStartIndex = -1; valueEndIndex = -1; } else { char currentCharacter = commandLineString[index]; bool isAssignment = (currentCharacter == ':') || (currentCharacter == '='); // Does the string end after the suspected assignment character? bool argumentEndReached = ((index + 1) == commandLineString.Length); if(isAssignment) { parseOptionValue(commandLineString, initiatorStartIndex, nameStartIndex, ref index); return; } else { bool isModifier = (currentCharacter == '+') || (currentCharacter == '-'); if(isModifier) { valueStartIndex = index; ++index; valueEndIndex = index; } else { valueStartIndex = -1; valueEndIndex = -1; } } } int argumentLength = index - initiatorStartIndex; this.commandLine.addOption( new Option( new StringSegment(commandLineString, initiatorStartIndex, argumentLength), nameStartIndex, nameEndIndex - nameStartIndex, valueStartIndex, valueEndIndex - valueStartIndex ) ); } /// Parses the value assignment in a command line option /// String containing the command line arguments /// /// Position of the character that started the option /// /// /// Position of the first character in the option's name /// /// Index at which the option name ended private void parseOptionValue( string commandLineString, int initiatorStartIndex, int nameStartIndex, ref int index ) { int nameEndIndex = index; int valueStartIndex, valueEndIndex; // Does the string end after the suspected assignment character? bool argumentEndReached = ((index + 1) == commandLineString.Length); if(argumentEndReached) { ++index; valueStartIndex = -1; valueEndIndex = -1; } else { char nextCharacter = commandLineString[index + 1]; // Is this a quoted assignment if(nextCharacter == '"') { index += 2; valueStartIndex = index; index = commandLineString.IndexOf('"', index); if(index == -1) { index = commandLineString.Length; valueEndIndex = index; } else { valueEndIndex = index; ++index; } } else { // Nope, assuming unquoted assignment or empty assignment ++index; valueStartIndex = index; index = commandLineString.IndexOfAny(WhitespaceCharacters, index); if(index == -1) { index = commandLineString.Length; valueEndIndex = index; } else { if(index == valueStartIndex) { valueStartIndex = -1; valueEndIndex = -1; } else { valueEndIndex = index; } } } } int argumentLength = index - initiatorStartIndex; this.commandLine.addOption( new Option( new StringSegment(commandLineString, initiatorStartIndex, argumentLength), nameStartIndex, nameEndIndex - nameStartIndex, valueStartIndex, valueEndIndex - valueStartIndex ) ); } /// Parses a quoted value from the input string /// String the quoted value is parsed from /// Index at which the quoted value begins private void parseQuotedValue(string commandLineString, ref int index) { char quoteCharacter = commandLineString[index]; int startIndex = index + 1; // Search for the closing quote index = commandLineString.IndexOf(quoteCharacter, startIndex); if(index == -1) { index = commandLineString.Length; // value ends at string end commandLine.addValue( new StringSegment(commandLineString, startIndex, index - startIndex) ); } else { // A closing quote was found commandLine.addValue( new StringSegment(commandLineString, startIndex, index - startIndex) ); ++index; // Skip the closing quote } } /// Parses a plain, unquoted value from the input string /// String containing the value to be parsed /// Index at which the value begins private void parseNakedValue(string commandLineString, ref int index) { int startIndex = index; index = commandLineString.IndexOfAny(WhitespaceCharacters, index); if(index == -1) { index = commandLineString.Length; } commandLine.addValue( new StringSegment(commandLineString, startIndex, index - startIndex) ); } /// Characters which end an option name when they are encountered private static readonly char[] OptionNameEndingCharacters = new char[] { ' ', '\t', '=', ':', '/', '-', '+', '"' }; /// 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; /// Whether the '/' character initiates an argument private bool windowsMode; } } } // namespace Nuclex.Support.Parsing