diff options
| author | Rob Mensching <rob@firegiant.com> | 2017-12-22 15:53:01 -0800 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2017-12-22 15:53:01 -0800 |
| commit | ecf3a0cca5a424a91ab98557d963d2535963d582 (patch) | |
| tree | 06355e906e5c404480dc6eac342b9b4d2ec9d122 /src/WixToolset.Core/CommandLine/CommandLineParser.cs | |
| parent | dc9f4c329e6f55ce7595970463e0caf148096f4b (diff) | |
| download | wix-ecf3a0cca5a424a91ab98557d963d2535963d582.tar.gz wix-ecf3a0cca5a424a91ab98557d963d2535963d582.tar.bz2 wix-ecf3a0cca5a424a91ab98557d963d2535963d582.zip | |
Reintroduce binder extensions and light.exe for binding .wixouts
Diffstat (limited to 'src/WixToolset.Core/CommandLine/CommandLineParser.cs')
| -rw-r--r-- | src/WixToolset.Core/CommandLine/CommandLineParser.cs | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/src/WixToolset.Core/CommandLine/CommandLineParser.cs b/src/WixToolset.Core/CommandLine/CommandLineParser.cs new file mode 100644 index 00000000..0e7da42a --- /dev/null +++ b/src/WixToolset.Core/CommandLine/CommandLineParser.cs | |||
| @@ -0,0 +1,628 @@ | |||
| 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 System.Text; | ||
| 10 | using System.Text.RegularExpressions; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Extensibility; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | internal enum Commands | ||
| 16 | { | ||
| 17 | Unknown, | ||
| 18 | Build, | ||
| 19 | Preprocess, | ||
| 20 | Compile, | ||
| 21 | Link, | ||
| 22 | Bind, | ||
| 23 | } | ||
| 24 | |||
| 25 | internal class CommandLineParser : ICommandLine, IParseCommandLine | ||
| 26 | { | ||
| 27 | private IServiceProvider ServiceProvider { get; set; } | ||
| 28 | |||
| 29 | private IMessaging Messaging { get; set; } | ||
| 30 | |||
| 31 | public static string ExpectedArgument { get; } = "expected argument"; | ||
| 32 | |||
| 33 | public string ActiveCommand { get; private set; } | ||
| 34 | |||
| 35 | public string[] OriginalArguments { get; private set; } | ||
| 36 | |||
| 37 | public Queue<string> RemainingArguments { get; } = new Queue<string>(); | ||
| 38 | |||
| 39 | public IExtensionManager ExtensionManager { get; private set; } | ||
| 40 | |||
| 41 | public string ErrorArgument { get; set; } | ||
| 42 | |||
| 43 | public bool ShowHelp { get; set; } | ||
| 44 | |||
| 45 | public ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context) | ||
| 46 | { | ||
| 47 | this.ServiceProvider = context.ServiceProvider; | ||
| 48 | |||
| 49 | this.Messaging = context.Messaging ?? this.ServiceProvider.GetService<IMessaging>(); | ||
| 50 | |||
| 51 | this.ExtensionManager = context.ExtensionManager ?? this.ServiceProvider.GetService<IExtensionManager>(); | ||
| 52 | |||
| 53 | var args = context.ParsedArguments ?? Array.Empty<string>(); | ||
| 54 | |||
| 55 | if (!String.IsNullOrEmpty(context.Arguments)) | ||
| 56 | { | ||
| 57 | args = CommandLineParser.ParseArgumentsToArray(context.Arguments).Union(args).ToArray(); | ||
| 58 | } | ||
| 59 | |||
| 60 | return this.ParseStandardCommandLine(context, args); | ||
| 61 | } | ||
| 62 | |||
| 63 | private ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context, string[] args) | ||
| 64 | { | ||
| 65 | var next = String.Empty; | ||
| 66 | |||
| 67 | var command = Commands.Unknown; | ||
| 68 | var showLogo = true; | ||
| 69 | var showVersion = false; | ||
| 70 | var outputFolder = String.Empty; | ||
| 71 | var outputFile = String.Empty; | ||
| 72 | var outputType = String.Empty; | ||
| 73 | var verbose = false; | ||
| 74 | var files = new List<string>(); | ||
| 75 | var defines = new List<string>(); | ||
| 76 | var includePaths = new List<string>(); | ||
| 77 | var locFiles = new List<string>(); | ||
| 78 | var libraryFiles = new List<string>(); | ||
| 79 | var suppressedWarnings = new List<int>(); | ||
| 80 | |||
| 81 | var bindFiles = false; | ||
| 82 | var bindPaths = new List<string>(); | ||
| 83 | |||
| 84 | var intermediateFolder = String.Empty; | ||
| 85 | |||
| 86 | var cabCachePath = String.Empty; | ||
| 87 | var cultures = new List<string>(); | ||
| 88 | var contentsFile = String.Empty; | ||
| 89 | var outputsFile = String.Empty; | ||
| 90 | var builtOutputsFile = String.Empty; | ||
| 91 | |||
| 92 | this.Parse(context, args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) => | ||
| 93 | { | ||
| 94 | if (cmdline.IsSwitch(arg)) | ||
| 95 | { | ||
| 96 | var parameter = arg.Substring(1); | ||
| 97 | switch (parameter.ToLowerInvariant()) | ||
| 98 | { | ||
| 99 | case "?": | ||
| 100 | case "h": | ||
| 101 | case "help": | ||
| 102 | cmdline.ShowHelp = true; | ||
| 103 | return true; | ||
| 104 | |||
| 105 | case "bindfiles": | ||
| 106 | bindFiles = true; | ||
| 107 | return true; | ||
| 108 | |||
| 109 | case "bindpath": | ||
| 110 | cmdline.GetNextArgumentOrError(bindPaths); | ||
| 111 | return true; | ||
| 112 | |||
| 113 | case "cc": | ||
| 114 | cmdline.GetNextArgumentOrError(ref cabCachePath); | ||
| 115 | return true; | ||
| 116 | |||
| 117 | case "cultures": | ||
| 118 | cmdline.GetNextArgumentOrError(cultures); | ||
| 119 | return true; | ||
| 120 | case "contentsfile": | ||
| 121 | cmdline.GetNextArgumentOrError(ref contentsFile); | ||
| 122 | return true; | ||
| 123 | case "outputsfile": | ||
| 124 | cmdline.GetNextArgumentOrError(ref outputsFile); | ||
| 125 | return true; | ||
| 126 | case "builtoutputsfile": | ||
| 127 | cmdline.GetNextArgumentOrError(ref builtOutputsFile); | ||
| 128 | return true; | ||
| 129 | |||
| 130 | case "d": | ||
| 131 | case "define": | ||
| 132 | cmdline.GetNextArgumentOrError(defines); | ||
| 133 | return true; | ||
| 134 | |||
| 135 | case "i": | ||
| 136 | case "includepath": | ||
| 137 | cmdline.GetNextArgumentOrError(includePaths); | ||
| 138 | return true; | ||
| 139 | |||
| 140 | case "intermediatefolder": | ||
| 141 | cmdline.GetNextArgumentOrError(ref intermediateFolder); | ||
| 142 | return true; | ||
| 143 | |||
| 144 | case "loc": | ||
| 145 | cmdline.GetNextArgumentAsFilePathOrError(locFiles, "localization files"); | ||
| 146 | return true; | ||
| 147 | |||
| 148 | case "lib": | ||
| 149 | cmdline.GetNextArgumentAsFilePathOrError(libraryFiles, "library files"); | ||
| 150 | return true; | ||
| 151 | |||
| 152 | case "o": | ||
| 153 | case "out": | ||
| 154 | cmdline.GetNextArgumentOrError(ref outputFile); | ||
| 155 | return true; | ||
| 156 | |||
| 157 | case "outputtype": | ||
| 158 | cmdline.GetNextArgumentOrError(ref outputType); | ||
| 159 | return true; | ||
| 160 | |||
| 161 | case "nologo": | ||
| 162 | showLogo = false; | ||
| 163 | return true; | ||
| 164 | |||
| 165 | case "v": | ||
| 166 | case "verbose": | ||
| 167 | verbose = true; | ||
| 168 | return true; | ||
| 169 | |||
| 170 | case "version": | ||
| 171 | case "-version": | ||
| 172 | showVersion = true; | ||
| 173 | return true; | ||
| 174 | } | ||
| 175 | |||
| 176 | return false; | ||
| 177 | } | ||
| 178 | else | ||
| 179 | { | ||
| 180 | files.AddRange(CommandLineHelper.GetFiles(arg, "source code")); | ||
| 181 | return true; | ||
| 182 | } | ||
| 183 | }); | ||
| 184 | |||
| 185 | this.Messaging.ShowVerboseMessages = verbose; | ||
| 186 | |||
| 187 | if (showVersion) | ||
| 188 | { | ||
| 189 | return new VersionCommand(); | ||
| 190 | } | ||
| 191 | |||
| 192 | if (showLogo) | ||
| 193 | { | ||
| 194 | AppCommon.DisplayToolHeader(); | ||
| 195 | } | ||
| 196 | |||
| 197 | if (this.ShowHelp) | ||
| 198 | { | ||
| 199 | return new HelpCommand(command); | ||
| 200 | } | ||
| 201 | |||
| 202 | switch (command) | ||
| 203 | { | ||
| 204 | case Commands.Build: | ||
| 205 | { | ||
| 206 | var sourceFiles = GatherSourceFiles(files, outputFolder); | ||
| 207 | var variables = this.GatherPreprocessorVariables(defines); | ||
| 208 | var bindPathList = this.GatherBindPaths(bindPaths); | ||
| 209 | var type = CalculateOutputType(outputType, outputFile); | ||
| 210 | return new BuildCommand(this.ServiceProvider, this.Messaging, this.ExtensionManager, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cabCachePath, cultures, bindFiles, bindPathList, intermediateFolder, contentsFile, outputsFile, builtOutputsFile); | ||
| 211 | } | ||
| 212 | |||
| 213 | case Commands.Compile: | ||
| 214 | { | ||
| 215 | var sourceFiles = GatherSourceFiles(files, outputFolder); | ||
| 216 | var variables = GatherPreprocessorVariables(defines); | ||
| 217 | return new CompileCommand(this.ServiceProvider, this.Messaging, this.ExtensionManager, sourceFiles, variables); | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | return null; | ||
| 222 | } | ||
| 223 | |||
| 224 | private static OutputType CalculateOutputType(string outputType, string outputFile) | ||
| 225 | { | ||
| 226 | if (String.IsNullOrEmpty(outputType)) | ||
| 227 | { | ||
| 228 | outputType = Path.GetExtension(outputFile); | ||
| 229 | } | ||
| 230 | |||
| 231 | switch (outputType.ToLowerInvariant()) | ||
| 232 | { | ||
| 233 | case "bundle": | ||
| 234 | case ".exe": | ||
| 235 | return OutputType.Bundle; | ||
| 236 | |||
| 237 | case "library": | ||
| 238 | case ".wixlib": | ||
| 239 | return OutputType.Library; | ||
| 240 | |||
| 241 | case "module": | ||
| 242 | case ".msm": | ||
| 243 | return OutputType.Module; | ||
| 244 | |||
| 245 | case "patch": | ||
| 246 | case ".msp": | ||
| 247 | return OutputType.Patch; | ||
| 248 | |||
| 249 | case ".pcp": | ||
| 250 | return OutputType.PatchCreation; | ||
| 251 | |||
| 252 | case "product": | ||
| 253 | case ".msi": | ||
| 254 | return OutputType.Product; | ||
| 255 | |||
| 256 | case "transform": | ||
| 257 | case ".mst": | ||
| 258 | return OutputType.Transform; | ||
| 259 | |||
| 260 | case "wixout": | ||
| 261 | case ".wixout": | ||
| 262 | return OutputType.Wixout; | ||
| 263 | } | ||
| 264 | |||
| 265 | return OutputType.Unknown; | ||
| 266 | } | ||
| 267 | |||
| 268 | #if UNUSED | ||
| 269 | private static CommandLine Parse(string commandLineString, Func<CommandLine, string, bool> parseArgument) | ||
| 270 | { | ||
| 271 | var arguments = CommandLine.ParseArgumentsToArray(commandLineString).ToArray(); | ||
| 272 | |||
| 273 | return CommandLine.Parse(arguments, null, parseArgument); | ||
| 274 | } | ||
| 275 | |||
| 276 | private static CommandLine Parse(string[] commandLineArguments, Func<CommandLine, string, bool> parseArgument) | ||
| 277 | { | ||
| 278 | return CommandLine.Parse(commandLineArguments, null, parseArgument); | ||
| 279 | } | ||
| 280 | #endif | ||
| 281 | |||
| 282 | private ICommandLine Parse(ICommandLineContext context, string[] commandLineArguments, Func<CommandLineParser, string, bool> parseCommand, Func<CommandLineParser, string, bool> parseArgument) | ||
| 283 | { | ||
| 284 | this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments); | ||
| 285 | |||
| 286 | this.QueueArgumentsAndLoadExtensions(this.OriginalArguments); | ||
| 287 | |||
| 288 | this.ProcessRemainingArguments(context, parseArgument, parseCommand); | ||
| 289 | |||
| 290 | return this; | ||
| 291 | } | ||
| 292 | |||
| 293 | private static IEnumerable<SourceFile> GatherSourceFiles(IEnumerable<string> sourceFiles, string intermediateDirectory) | ||
| 294 | { | ||
| 295 | var files = new List<SourceFile>(); | ||
| 296 | |||
| 297 | foreach (var item in sourceFiles) | ||
| 298 | { | ||
| 299 | var sourcePath = item; | ||
| 300 | var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir"); | ||
| 301 | |||
| 302 | files.Add(new SourceFile(sourcePath, outputPath)); | ||
| 303 | } | ||
| 304 | |||
| 305 | return files; | ||
| 306 | } | ||
| 307 | |||
| 308 | private IDictionary<string, string> GatherPreprocessorVariables(IEnumerable<string> defineConstants) | ||
| 309 | { | ||
| 310 | var variables = new Dictionary<string, string>(); | ||
| 311 | |||
| 312 | foreach (var pair in defineConstants) | ||
| 313 | { | ||
| 314 | string[] value = pair.Split(new[] { '=' }, 2); | ||
| 315 | |||
| 316 | if (variables.ContainsKey(value[0])) | ||
| 317 | { | ||
| 318 | this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]])); | ||
| 319 | continue; | ||
| 320 | } | ||
| 321 | |||
| 322 | variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]); | ||
| 323 | } | ||
| 324 | |||
| 325 | return variables; | ||
| 326 | } | ||
| 327 | |||
| 328 | private IEnumerable<BindPath> GatherBindPaths(IEnumerable<string> bindPaths) | ||
| 329 | { | ||
| 330 | var result = new List<BindPath>(); | ||
| 331 | |||
| 332 | foreach (var bindPath in bindPaths) | ||
| 333 | { | ||
| 334 | var bp = BindPath.Parse(bindPath); | ||
| 335 | |||
| 336 | if (Directory.Exists(bp.Path)) | ||
| 337 | { | ||
| 338 | result.Add(bp); | ||
| 339 | } | ||
| 340 | else if (File.Exists(bp.Path)) | ||
| 341 | { | ||
| 342 | this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); | ||
| 343 | } | ||
| 344 | } | ||
| 345 | |||
| 346 | return result; | ||
| 347 | } | ||
| 348 | |||
| 349 | /// <summary> | ||
| 350 | /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity | ||
| 351 | /// </summary> | ||
| 352 | /// <param name="args">The list of strings to check.</param> | ||
| 353 | /// <param name="index">The index (in args) of the commandline parameter to be validated.</param> | ||
| 354 | /// <returns>True if a valid switch exists there, false if not.</returns> | ||
| 355 | public bool IsSwitch(string arg) | ||
| 356 | { | ||
| 357 | return arg != null && arg.Length > 1 && ('/' == arg[0] || '-' == arg[0]); | ||
| 358 | } | ||
| 359 | |||
| 360 | /// <summary> | ||
| 361 | /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity | ||
| 362 | /// </summary> | ||
| 363 | /// <param name="args">The list of strings to check.</param> | ||
| 364 | /// <param name="index">The index (in args) of the commandline parameter to be validated.</param> | ||
| 365 | /// <returns>True if a valid switch exists there, false if not.</returns> | ||
| 366 | public bool IsSwitchAt(IEnumerable<string> args, int index) | ||
| 367 | { | ||
| 368 | var arg = args.ElementAtOrDefault(index); | ||
| 369 | return IsSwitch(arg); | ||
| 370 | } | ||
| 371 | |||
| 372 | public void GetNextArgumentOrError(ref string arg) | ||
| 373 | { | ||
| 374 | this.TryGetNextArgumentOrError(out arg); | ||
| 375 | } | ||
| 376 | |||
| 377 | public void GetNextArgumentOrError(IList<string> args) | ||
| 378 | { | ||
| 379 | if (this.TryGetNextArgumentOrError(out var arg)) | ||
| 380 | { | ||
| 381 | args.Add(arg); | ||
| 382 | } | ||
| 383 | } | ||
| 384 | |||
| 385 | public void GetNextArgumentAsFilePathOrError(IList<string> args, string fileType) | ||
| 386 | { | ||
| 387 | if (this.TryGetNextArgumentOrError(out var arg)) | ||
| 388 | { | ||
| 389 | foreach (var path in CommandLineHelper.GetFiles(arg, fileType)) | ||
| 390 | { | ||
| 391 | args.Add(path); | ||
| 392 | } | ||
| 393 | } | ||
| 394 | } | ||
| 395 | |||
| 396 | public bool TryGetNextArgumentOrError(out string arg) | ||
| 397 | { | ||
| 398 | if (TryDequeue(this.RemainingArguments, out arg) && !this.IsSwitch(arg)) | ||
| 399 | { | ||
| 400 | return true; | ||
| 401 | } | ||
| 402 | |||
| 403 | this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument; | ||
| 404 | |||
| 405 | return false; | ||
| 406 | } | ||
| 407 | |||
| 408 | private static bool TryDequeue(Queue<string> q, out string arg) | ||
| 409 | { | ||
| 410 | if (q.Count > 0) | ||
| 411 | { | ||
| 412 | arg = q.Dequeue(); | ||
| 413 | return true; | ||
| 414 | } | ||
| 415 | |||
| 416 | arg = null; | ||
| 417 | return false; | ||
| 418 | } | ||
| 419 | |||
| 420 | private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments) | ||
| 421 | { | ||
| 422 | List<string> args = new List<string>(); | ||
| 423 | |||
| 424 | foreach (var arg in commandLineArguments) | ||
| 425 | { | ||
| 426 | if ('@' == arg[0]) | ||
| 427 | { | ||
| 428 | var responseFileArguments = CommandLineParser.ParseResponseFile(arg.Substring(1)); | ||
| 429 | args.AddRange(responseFileArguments); | ||
| 430 | } | ||
| 431 | else | ||
| 432 | { | ||
| 433 | args.Add(arg); | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | this.OriginalArguments = args.ToArray(); | ||
| 438 | } | ||
| 439 | |||
| 440 | private void QueueArgumentsAndLoadExtensions(string[] args) | ||
| 441 | { | ||
| 442 | for (var i = 0; i < args.Length; ++i) | ||
| 443 | { | ||
| 444 | var arg = args[i]; | ||
| 445 | |||
| 446 | if ("-ext" == arg || "/ext" == arg) | ||
| 447 | { | ||
| 448 | if (!this.IsSwitchAt(args, ++i)) | ||
| 449 | { | ||
| 450 | this.ExtensionManager.Load(args[i]); | ||
| 451 | } | ||
| 452 | else | ||
| 453 | { | ||
| 454 | this.ErrorArgument = arg; | ||
| 455 | break; | ||
| 456 | } | ||
| 457 | } | ||
| 458 | else | ||
| 459 | { | ||
| 460 | this.RemainingArguments.Enqueue(arg); | ||
| 461 | } | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | private void ProcessRemainingArguments(ICommandLineContext context, Func<CommandLineParser, string, bool> parseArgument, Func<CommandLineParser, string, bool> parseCommand) | ||
| 466 | { | ||
| 467 | var extensions = this.ExtensionManager.Create<IExtensionCommandLine>(); | ||
| 468 | |||
| 469 | foreach (var extension in extensions) | ||
| 470 | { | ||
| 471 | extension.PreParse(context); | ||
| 472 | } | ||
| 473 | |||
| 474 | while (!this.ShowHelp && | ||
| 475 | String.IsNullOrEmpty(this.ErrorArgument) && | ||
| 476 | TryDequeue(this.RemainingArguments, out var arg)) | ||
| 477 | { | ||
| 478 | if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments. | ||
| 479 | { | ||
| 480 | continue; | ||
| 481 | } | ||
| 482 | |||
| 483 | if ('-' == arg[0] || '/' == arg[0]) | ||
| 484 | { | ||
| 485 | if (!parseArgument(this, arg) && | ||
| 486 | !this.TryParseCommandLineArgumentWithExtension(arg, extensions)) | ||
| 487 | { | ||
| 488 | this.ErrorArgument = arg; | ||
| 489 | } | ||
| 490 | } | ||
| 491 | else if (String.IsNullOrEmpty(this.ActiveCommand) && parseCommand != null) // First non-switch must be the command, if commands are supported. | ||
| 492 | { | ||
| 493 | if (parseCommand(this, arg)) | ||
| 494 | { | ||
| 495 | this.ActiveCommand = arg; | ||
| 496 | } | ||
| 497 | else | ||
| 498 | { | ||
| 499 | this.ErrorArgument = arg; | ||
| 500 | } | ||
| 501 | } | ||
| 502 | else if (!this.TryParseCommandLineArgumentWithExtension(arg, extensions) && | ||
| 503 | !parseArgument(this, arg)) | ||
| 504 | { | ||
| 505 | this.ErrorArgument = arg; | ||
| 506 | } | ||
| 507 | } | ||
| 508 | } | ||
| 509 | |||
| 510 | private bool TryParseCommandLineArgumentWithExtension(string arg, IEnumerable<IExtensionCommandLine> extensions) | ||
| 511 | { | ||
| 512 | foreach (var extension in extensions) | ||
| 513 | { | ||
| 514 | if (extension.TryParseArgument(this, arg)) | ||
| 515 | { | ||
| 516 | return true; | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | return false; | ||
| 521 | } | ||
| 522 | |||
| 523 | private static List<string> ParseResponseFile(string responseFile) | ||
| 524 | { | ||
| 525 | string arguments; | ||
| 526 | |||
| 527 | using (StreamReader reader = new StreamReader(responseFile)) | ||
| 528 | { | ||
| 529 | arguments = reader.ReadToEnd(); | ||
| 530 | } | ||
| 531 | |||
| 532 | return CommandLineParser.ParseArgumentsToArray(arguments); | ||
| 533 | } | ||
| 534 | |||
| 535 | private static List<string> ParseArgumentsToArray(string arguments) | ||
| 536 | { | ||
| 537 | // Scan and parse the arguments string, dividing up the arguments based on whitespace. | ||
| 538 | // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed. | ||
| 539 | // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments. | ||
| 540 | // Escaped quotes and escaped backslashes also need to be unescaped by this process. | ||
| 541 | |||
| 542 | // Collects the final list of arguments to be returned. | ||
| 543 | var argsList = new List<string>(); | ||
| 544 | |||
| 545 | // True if we are inside an unescaped quote, meaning whitespace should be ignored. | ||
| 546 | var insideQuote = false; | ||
| 547 | |||
| 548 | // Index of the start of the current argument substring; either the start of the argument | ||
| 549 | // or the start of a quoted or unquoted sequence within it. | ||
| 550 | var partStart = 0; | ||
| 551 | |||
| 552 | // The current argument string being built; when completed it will be added to the list. | ||
| 553 | var arg = new StringBuilder(); | ||
| 554 | |||
| 555 | for (int i = 0; i <= arguments.Length; i++) | ||
| 556 | { | ||
| 557 | if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote)) | ||
| 558 | { | ||
| 559 | // Reached a whitespace separator or the end of the string. | ||
| 560 | |||
| 561 | // Finish building the current argument. | ||
| 562 | arg.Append(arguments.Substring(partStart, i - partStart)); | ||
| 563 | |||
| 564 | // Skip over the whitespace character. | ||
| 565 | partStart = i + 1; | ||
| 566 | |||
| 567 | // Add the argument to the list if it's not empty. | ||
| 568 | if (arg.Length > 0) | ||
| 569 | { | ||
| 570 | argsList.Add(CommandLineParser.ExpandEnvironmentVariables(arg.ToString())); | ||
| 571 | arg.Length = 0; | ||
| 572 | } | ||
| 573 | } | ||
| 574 | else if (i > partStart && arguments[i - 1] == '\\') | ||
| 575 | { | ||
| 576 | // Check the character following an unprocessed backslash. | ||
| 577 | // Unescape quotes, and backslashes followed by a quote. | ||
| 578 | if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"')) | ||
| 579 | { | ||
| 580 | // Unescape the quote or backslash by skipping the preceeding backslash. | ||
| 581 | arg.Append(arguments.Substring(partStart, i - 1 - partStart)); | ||
| 582 | arg.Append(arguments[i]); | ||
| 583 | partStart = i + 1; | ||
| 584 | } | ||
| 585 | } | ||
| 586 | else if (arguments[i] == '"') | ||
| 587 | { | ||
| 588 | // Add the quoted or unquoted section to the argument string. | ||
| 589 | arg.Append(arguments.Substring(partStart, i - partStart)); | ||
| 590 | |||
| 591 | // And skip over the quote character. | ||
| 592 | partStart = i + 1; | ||
| 593 | |||
| 594 | insideQuote = !insideQuote; | ||
| 595 | } | ||
| 596 | } | ||
| 597 | |||
| 598 | return argsList; | ||
| 599 | } | ||
| 600 | |||
| 601 | private static string ExpandEnvironmentVariables(string arguments) | ||
| 602 | { | ||
| 603 | var id = Environment.GetEnvironmentVariables(); | ||
| 604 | |||
| 605 | var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)"); | ||
| 606 | MatchCollection matches = regex.Matches(arguments); | ||
| 607 | |||
| 608 | string value = String.Empty; | ||
| 609 | for (int i = 0; i <= (matches.Count - 1); i++) | ||
| 610 | { | ||
| 611 | try | ||
| 612 | { | ||
| 613 | var key = matches[i].Value; | ||
| 614 | regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)")); | ||
| 615 | value = id[key].ToString(); | ||
| 616 | arguments = regex.Replace(arguments, value); | ||
| 617 | } | ||
| 618 | catch (NullReferenceException) | ||
| 619 | { | ||
| 620 | // Collapse unresolved environment variables. | ||
| 621 | arguments = regex.Replace(arguments, value); | ||
| 622 | } | ||
| 623 | } | ||
| 624 | |||
| 625 | return arguments; | ||
| 626 | } | ||
| 627 | } | ||
| 628 | } | ||
