// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.DotNet.Cli.Utils.CommandParsing { public class CommandGrammar : Grammar { private CommandGrammar(Func variable, bool preserveSurroundingQuotes) { var environmentVariablePiece = Ch('%').And(Rep(Ch().Not(Ch('%')))).And(Ch('%')).Left().Down().Str() .Build(key => variable(key) ?? "%" + key + "%"); var escapeSequencePiece = Ch('%').And(Ch('%')).Build(_=>"%") .Or(Ch('^').And(Ch('^')).Build(_ => "^")) .Or(Ch('\\').And(Ch('\\')).Build(_ => "\\")) .Or(Ch('\\').And(Ch('\"')).Build(_ => "\"")) ; var specialPiece = environmentVariablePiece.Or(escapeSequencePiece); var unquotedPiece = Rep1(Ch().Not(specialPiece).Not(Ch(' '))).Str(); var quotedPiece = Rep1(Ch().Not(specialPiece).Not(Ch('\"'))).Str(); var unquotedTerm = Rep1(unquotedPiece.Or(specialPiece)).Str(); var quotedTerm = Ch('\"').And(Rep(quotedPiece.Or(specialPiece)).Str()).And(Ch('\"')).Left().Down(); if (preserveSurroundingQuotes) { // Str() value assigned to quotedTerm does not include quotation marks surrounding the quoted or // special piece. Add those quotes back if requested. quotedTerm = quotedTerm.Build(str => "\"" + str + "\""); } var whitespace = Rep(Ch(' ')); var term = whitespace.And(quotedTerm.Or(unquotedTerm)).And(whitespace).Left().Down(); Parse = Rep(term); } public readonly Parser> Parse; public static string[] Process(string text, Func variables, bool preserveSurroundingQuotes) { var grammar = new CommandGrammar(variables, preserveSurroundingQuotes); var cursor = new Cursor(text, 0, text.Length); var result = grammar.Parse(cursor); if (!result.Remainder.IsEnd) { throw new ArgumentException($"Malformed command text '{text}'", nameof(text)); } return result.Value.ToArray(); } } }