From fc92b28f87599ac25d35399dc2df2f356a285960 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 12 Jul 2018 22:27:09 -0700 Subject: Refactor command line parsing to enable extensions there in light.exe Fixes wixtoolset/issues#5845 --- .../CommandLine/ParseCommandLine.cs | 257 +++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 src/WixToolset.Core/CommandLine/ParseCommandLine.cs (limited to 'src/WixToolset.Core/CommandLine/ParseCommandLine.cs') diff --git a/src/WixToolset.Core/CommandLine/ParseCommandLine.cs b/src/WixToolset.Core/CommandLine/ParseCommandLine.cs new file mode 100644 index 00000000..7d0dcbd8 --- /dev/null +++ b/src/WixToolset.Core/CommandLine/ParseCommandLine.cs @@ -0,0 +1,257 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.CommandLine +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + internal class ParseCommandLine : IParseCommandLine + { + private const string ExpectedArgument = "expected argument"; + + public string ErrorArgument { get; set; } + + private Queue RemainingArguments { get; } + + private IMessaging Messaging { get; } + + public ParseCommandLine(IMessaging messaging, string[] arguments, string errorArgument) + { + this.Messaging = messaging; + this.RemainingArguments = new Queue(arguments); + this.ErrorArgument = errorArgument; + } + + public bool IsSwitch(string arg) => !String.IsNullOrEmpty(arg) && ('/' == arg[0] || '-' == arg[0]); + + public void GetArgumentAsFilePathOrError(string argument, string fileType, IList paths) + { + foreach (var path in GetFiles(argument, fileType)) + { + paths.Add(path); + } + } + + public string GetNextArgumentOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var argument)) + { + return argument; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } + + public bool GetNextArgumentOrError(string commandLineSwitch, IList args) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) + { + args.Add(arg); + return true; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory)) + { + return directory; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } + + public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList directories) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory)) + { + directories.Add(directory); + return true; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public string GetNextArgumentAsFilePathOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path)) + { + return path; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } + + public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList paths) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) + { + foreach (var path in GetFiles(arg, fileType)) + { + paths.Add(path); + } + + return true; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public bool TryGetNextSwitchOrArgument(out string arg) + { + return TryDequeue(this.RemainingArguments, out arg); + } + + private bool TryGetNextNonSwitchArgumentOrError(out string arg) + { + var result = this.TryGetNextSwitchOrArgument(out arg); + + if (!result && !this.IsSwitch(arg)) + { + this.ErrorArgument = arg ?? ParseCommandLine.ExpectedArgument; + } + + return result; + } + + private static bool IsValidArg(string arg) => !(String.IsNullOrEmpty(arg) || '/' == arg[0] || '-' == arg[0]); + + private static bool TryDequeue(Queue q, out string arg) + { + if (q.Count > 0) + { + arg = q.Dequeue(); + return true; + } + + arg = null; + return false; + } + + private bool TryGetDirectory(string commandlineSwitch, IMessaging messageHandler, string arg, out string directory) + { + directory = null; + + if (File.Exists(arg)) + { + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg)); + return false; + } + + directory = this.VerifyPath(arg); + return directory != null; + } + + private bool TryGetFile(string commandlineSwitch, string arg, out string path) + { + path = null; + + if (!IsValidArg(arg)) + { + this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch)); + } + else if (Directory.Exists(arg)) + { + this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg)); + } + else + { + path = this.VerifyPath(arg); + } + + return path != null; + } + + /// + /// Get a set of files that possibly have a search pattern in the path (such as '*'). + /// + /// Search path to find files in. + /// Type of file; typically "Source". + /// An array of files matching the search path. + /// + /// This method is written in this verbose way because it needs to support ".." in the path. + /// It needs the directory path isolated from the file name in order to use Directory.GetFiles + /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since + /// Path.GetDirectoryName does not support ".." in the path. + /// + /// Throws WixFileNotFoundException if no file matching the pattern can be found. + private string[] GetFiles(string searchPath, string fileType) + { + if (null == searchPath) + { + throw new ArgumentNullException(nameof(searchPath)); + } + + // Convert alternate directory separators to the standard one. + string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); + var files = new string[0]; + + try + { + if (0 > lastSeparator) + { + files = Directory.GetFiles(".", filePath); + } + else // found directory separator + { + files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1)); + } + } + catch (DirectoryNotFoundException) + { + // Don't let this function throw the DirectoryNotFoundException. This exception + // occurs for non-existant directories and invalid characters in the searchPattern. + } + catch (ArgumentException) + { + // Don't let this function throw the ArgumentException. This exception + // occurs in certain situations such as when passing a malformed UNC path. + } + catch (IOException) + { + } + + if (0 == files.Length) + { + this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType)); + } + + return files; + } + + private string VerifyPath(string path) + { + string fullPath; + + if (0 <= path.IndexOf('\"')) + { + this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path)); + return null; + } + + try + { + fullPath = Path.GetFullPath(path); + } + catch (Exception e) + { + this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message)); + return null; + } + + return fullPath; + } + } +} -- cgit v1.2.3-55-g6feb