main
cs 292 lines 9.67 KB
Raw
1 // 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.
2
3 namespace WixToolset.Core.CommandLine
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Extensibility.Services;
11
12 internal class CommandLineParser : ICommandLineParser
13 {
14 private const string ExpectedArgument = "expected argument";
15
16 public CommandLineParser(IMessaging messaging, string[] arguments, string errorArgument)
17 {
18 this.Messaging = messaging;
19 this.RemainingArguments = new Queue<string>(arguments.Where(a => !String.IsNullOrWhiteSpace(a))); // skip blank arguments.
20 this.ErrorArgument = errorArgument;
21 }
22
23 public string ErrorArgument { get; private set; }
24
25 private Queue<string> RemainingArguments { get; }
26
27 private IMessaging Messaging { get; }
28
29 public bool IsSwitch(string arg)
30 {
31 return !String.IsNullOrEmpty(arg) && '-' == arg[0];
32 }
33
34 public string GetArgumentAsFilePathOrError(string argument, string filePurpose)
35 {
36 if (!File.Exists(argument))
37 {
38 this.Messaging.Write(ErrorMessages.FileNotFound(null, argument, filePurpose));
39 return null;
40 }
41
42 return argument;
43 }
44
45 public bool GetArgumentAsFilePathOrError(string argument, string filePurpose, IList<string> paths)
46 {
47 var files = this.GetFiles(argument, filePurpose);
48
49 foreach (var path in files)
50 {
51 paths.Add(path);
52 }
53
54 return files.Length > 0;
55 }
56
57 public string GetNextArgumentOrError(string commandLineSwitch)
58 {
59 if (this.TryGetNextNonSwitchArgumentOrError(out var argument))
60 {
61 return argument;
62 }
63
64 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
65 return null;
66 }
67
68 public bool GetNextArgumentOrError(string commandLineSwitch, IList<string> args)
69 {
70 if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
71 {
72 args.Add(arg);
73 return true;
74 }
75
76 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
77 return false;
78 }
79
80 public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch)
81 {
82 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory))
83 {
84 return directory;
85 }
86
87 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
88 return null;
89 }
90
91 public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList<string> directories)
92 {
93 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory))
94 {
95 directories.Add(directory);
96 return true;
97 }
98
99 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
100 return false;
101 }
102
103 public string GetNextArgumentAsFilePathOrError(string commandLineSwitch, string filePurpose)
104 {
105 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, filePurpose, out var path))
106 {
107 return path;
108 }
109
110 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
111 return null;
112 }
113
114 public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList<string> paths)
115 {
116 if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
117 {
118 foreach (var path in this.GetFiles(arg, fileType))
119 {
120 paths.Add(path);
121 }
122
123 return true;
124 }
125
126 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
127 return false;
128 }
129
130 public void ReportErrorArgument(string argument, Message message = null)
131 {
132 this.Messaging.Write(message ?? ErrorMessages.AdditionalArgumentUnexpected(argument));
133 this.ErrorArgument = argument;
134 }
135
136 public bool TryGetNextSwitchOrArgument(out string argument)
137 {
138 if (this.RemainingArguments.Count > 0)
139 {
140 argument = this.RemainingArguments.Dequeue();
141 return true;
142 }
143
144 argument = null;
145 return false;
146 }
147
148 public string PeekNextArgument()
149 {
150 return this.TryPeekNextArgument(out var argument) ? argument : null;
151 }
152
153 public bool TryPeekNextArgument(out string argument)
154 {
155 if (this.RemainingArguments.Count > 0)
156 {
157 argument = this.RemainingArguments.Peek();
158 return true;
159 }
160
161 argument = null;
162 return false;
163 }
164
165 private bool TryGetNextNonSwitchArgumentOrError(out string arg)
166 {
167 var result = this.TryGetNextSwitchOrArgument(out arg);
168
169 if (!result || this.IsSwitch(arg))
170 {
171 this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument;
172 return false;
173 }
174
175 return result;
176 }
177
178 private bool TryGetDirectory(string commandlineSwitch, string arg, out string directory)
179 {
180 directory = null;
181
182 if (File.Exists(arg))
183 {
184 this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg));
185 return false;
186 }
187
188 directory = this.VerifyPath(arg);
189 return directory != null;
190 }
191
192 private bool TryGetFile(string commandlineSwitch, string arg, string purpose, out string path)
193 {
194 path = null;
195
196 if (String.IsNullOrEmpty(arg) || '-' == arg[0])
197 {
198 this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch, purpose));
199 }
200 else if (Directory.Exists(arg))
201 {
202 this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg));
203 }
204 else
205 {
206 path = this.VerifyPath(arg);
207 }
208
209 return path != null;
210 }
211
212 /// <summary>
213 /// Get a set of files that possibly have a search pattern in the path (such as '*').
214 /// </summary>
215 /// <param name="searchPath">Search path to find files in.</param>
216 /// <param name="fileType">Type of file; typically "Source".</param>
217 /// <returns>An array of files matching the search path.</returns>
218 /// <remarks>
219 /// This method is written in this verbose way because it needs to support ".." in the path.
220 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
221 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
222 /// Path.GetDirectoryName does not support ".." in the path.
223 /// </remarks>
224 private string[] GetFiles(string searchPath, string fileType)
225 {
226 if (null == searchPath)
227 {
228 throw new ArgumentNullException(nameof(searchPath));
229 }
230
231 // Convert alternate directory separators to the standard one.
232 var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
233 var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
234 var files = new string[0];
235
236 try
237 {
238 if (0 > lastSeparator)
239 {
240 files = Directory.GetFiles(".", filePath);
241 }
242 else // found directory separator
243 {
244 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
245 }
246 }
247 catch (DirectoryNotFoundException)
248 {
249 // Don't let this function throw the DirectoryNotFoundException. This exception
250 // occurs for non-existant directories and invalid characters in the searchPattern.
251 }
252 catch (ArgumentException)
253 {
254 // Don't let this function throw the ArgumentException. This exception
255 // occurs in certain situations such as when passing a malformed UNC path.
256 }
257 catch (IOException)
258 {
259 }
260
261 if (0 == files.Length)
262 {
263 this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType));
264 }
265
266 return files;
267 }
268
269 private string VerifyPath(string path)
270 {
271 string fullPath;
272
273 if (0 <= path.IndexOf('\"'))
274 {
275 this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path));
276 return null;
277 }
278
279 try
280 {
281 fullPath = Path.GetFullPath(path);
282 }
283 catch (Exception e)
284 {
285 this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message));
286 return null;
287 }
288
289 return fullPath;
290 }
291 }
292 }