aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/CommandLine/ParseCommandLine.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2018-07-12 22:27:09 -0700
committerRob Mensching <rob@firegiant.com>2018-07-12 22:38:12 -0700
commitfc92b28f87599ac25d35399dc2df2f356a285960 (patch)
tree0a775850ec5b4ff580b949700b51f5eee3182325 /src/WixToolset.Core/CommandLine/ParseCommandLine.cs
parent1a2d7994764060dc6f8936fab1c03e255f2671c5 (diff)
downloadwix-fc92b28f87599ac25d35399dc2df2f356a285960.tar.gz
wix-fc92b28f87599ac25d35399dc2df2f356a285960.tar.bz2
wix-fc92b28f87599ac25d35399dc2df2f356a285960.zip
Refactor command line parsing to enable extensions there in light.exe
Fixes wixtoolset/issues#5845
Diffstat (limited to 'src/WixToolset.Core/CommandLine/ParseCommandLine.cs')
-rw-r--r--src/WixToolset.Core/CommandLine/ParseCommandLine.cs257
1 files changed, 257 insertions, 0 deletions
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 @@
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
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Data;
9 using WixToolset.Extensibility.Services;
10
11 internal class ParseCommandLine : IParseCommandLine
12 {
13 private const string ExpectedArgument = "expected argument";
14
15 public string ErrorArgument { get; set; }
16
17 private Queue<string> RemainingArguments { get; }
18
19 private IMessaging Messaging { get; }
20
21 public ParseCommandLine(IMessaging messaging, string[] arguments, string errorArgument)
22 {
23 this.Messaging = messaging;
24 this.RemainingArguments = new Queue<string>(arguments);
25 this.ErrorArgument = errorArgument;
26 }
27
28 public bool IsSwitch(string arg) => !String.IsNullOrEmpty(arg) && ('/' == arg[0] || '-' == arg[0]);
29
30 public void GetArgumentAsFilePathOrError(string argument, string fileType, IList<string> paths)
31 {
32 foreach (var path in GetFiles(argument, fileType))
33 {
34 paths.Add(path);
35 }
36 }
37
38 public string GetNextArgumentOrError(string commandLineSwitch)
39 {
40 if (this.TryGetNextNonSwitchArgumentOrError(out var argument))
41 {
42 return argument;
43 }
44
45 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
46 return null;
47 }
48
49 public bool GetNextArgumentOrError(string commandLineSwitch, IList<string> args)
50 {
51 if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
52 {
53 args.Add(arg);
54 return true;
55 }
56
57 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
58 return false;
59 }
60
61 public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch)
62 {
63 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory))
64 {
65 return directory;
66 }
67
68 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
69 return null;
70 }
71
72 public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList<string> directories)
73 {
74 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory))
75 {
76 directories.Add(directory);
77 return true;
78 }
79
80 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
81 return false;
82 }
83
84 public string GetNextArgumentAsFilePathOrError(string commandLineSwitch)
85 {
86 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path))
87 {
88 return path;
89 }
90
91 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
92 return null;
93 }
94
95 public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList<string> paths)
96 {
97 if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
98 {
99 foreach (var path in GetFiles(arg, fileType))
100 {
101 paths.Add(path);
102 }
103
104 return true;
105 }
106
107 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
108 return false;
109 }
110
111 public bool TryGetNextSwitchOrArgument(out string arg)
112 {
113 return TryDequeue(this.RemainingArguments, out arg);
114 }
115
116 private bool TryGetNextNonSwitchArgumentOrError(out string arg)
117 {
118 var result = this.TryGetNextSwitchOrArgument(out arg);
119
120 if (!result && !this.IsSwitch(arg))
121 {
122 this.ErrorArgument = arg ?? ParseCommandLine.ExpectedArgument;
123 }
124
125 return result;
126 }
127
128 private static bool IsValidArg(string arg) => !(String.IsNullOrEmpty(arg) || '/' == arg[0] || '-' == arg[0]);
129
130 private static bool TryDequeue(Queue<string> q, out string arg)
131 {
132 if (q.Count > 0)
133 {
134 arg = q.Dequeue();
135 return true;
136 }
137
138 arg = null;
139 return false;
140 }
141
142 private bool TryGetDirectory(string commandlineSwitch, IMessaging messageHandler, string arg, out string directory)
143 {
144 directory = null;
145
146 if (File.Exists(arg))
147 {
148 this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg));
149 return false;
150 }
151
152 directory = this.VerifyPath(arg);
153 return directory != null;
154 }
155
156 private bool TryGetFile(string commandlineSwitch, string arg, out string path)
157 {
158 path = null;
159
160 if (!IsValidArg(arg))
161 {
162 this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch));
163 }
164 else if (Directory.Exists(arg))
165 {
166 this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg));
167 }
168 else
169 {
170 path = this.VerifyPath(arg);
171 }
172
173 return path != null;
174 }
175
176 /// <summary>
177 /// Get a set of files that possibly have a search pattern in the path (such as '*').
178 /// </summary>
179 /// <param name="searchPath">Search path to find files in.</param>
180 /// <param name="fileType">Type of file; typically "Source".</param>
181 /// <returns>An array of files matching the search path.</returns>
182 /// <remarks>
183 /// This method is written in this verbose way because it needs to support ".." in the path.
184 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
185 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
186 /// Path.GetDirectoryName does not support ".." in the path.
187 /// </remarks>
188 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
189 private string[] GetFiles(string searchPath, string fileType)
190 {
191 if (null == searchPath)
192 {
193 throw new ArgumentNullException(nameof(searchPath));
194 }
195
196 // Convert alternate directory separators to the standard one.
197 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
198 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
199 var files = new string[0];
200
201 try
202 {
203 if (0 > lastSeparator)
204 {
205 files = Directory.GetFiles(".", filePath);
206 }
207 else // found directory separator
208 {
209 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
210 }
211 }
212 catch (DirectoryNotFoundException)
213 {
214 // Don't let this function throw the DirectoryNotFoundException. This exception
215 // occurs for non-existant directories and invalid characters in the searchPattern.
216 }
217 catch (ArgumentException)
218 {
219 // Don't let this function throw the ArgumentException. This exception
220 // occurs in certain situations such as when passing a malformed UNC path.
221 }
222 catch (IOException)
223 {
224 }
225
226 if (0 == files.Length)
227 {
228 this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType));
229 }
230
231 return files;
232 }
233
234 private string VerifyPath(string path)
235 {
236 string fullPath;
237
238 if (0 <= path.IndexOf('\"'))
239 {
240 this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path));
241 return null;
242 }
243
244 try
245 {
246 fullPath = Path.GetFullPath(path);
247 }
248 catch (Exception e)
249 {
250 this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message));
251 return null;
252 }
253
254 return fullPath;
255 }
256 }
257}