forked from dotnet/fsharp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFSharpCommandLineBuilder.fs
More file actions
133 lines (102 loc) · 5.2 KB
/
Copy pathFSharpCommandLineBuilder.fs
File metadata and controls
133 lines (102 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Build
open System
open System.Text
open Microsoft.Build.Framework
open Microsoft.Build.Utilities
open Internal.Utilities
[<assembly: System.Runtime.InteropServices.ComVisible(false)>]
[<assembly: System.CLSCompliant(true)>]
do ()
// Shim to match nullness checking library support in preview
[<AutoOpen>]
module Utils =
/// Indicates that a type may be null. 'MaybeNull<string>' used internally in the F# compiler as unchecked
/// replacement for 'string?' for example for future FS-1060.
type MaybeNull<'T when 'T: not null and 'T: not struct> = 'T | null
type FSharpCommandLineBuilder() =
// In addition to generating a command-line that will be handed to cmd.exe, we also generate
// an array of individual arguments. The former needs to be quoted (and cmd.exe will strip the
// quotes while parsing), whereas the latter is not. See bug 4357 for background; this helper
// class gets us out of the business of unparsing-then-reparsing arguments.
let builder = new CommandLineBuilder()
let mutable args = [] // in reverse order
let mutable srcs = [] // in reverse order
/// Return a list of the arguments (with no quoting for the cmd.exe shell)
member _.CapturedArguments() = List.rev args
/// Return a list of the sources (with no quoting for the cmd.exe shell)
member _.CapturedFilenames() = List.rev srcs
/// Return a full command line (with quoting for the cmd.exe shell)
override x.ToString() = builder.ToString()
member _.AppendFileNamesIfNotNull(filenames: ITaskItem[], sep: string) =
builder.AppendFileNamesIfNotNull(filenames, sep)
// do not update "args", not used
for item in filenames do
let tmp = new CommandLineBuilder()
tmp.AppendSwitchUnquotedIfNotNull("", item.ItemSpec) // we don't want to quote the file name, this is a way to get that
let s = tmp.ToString()
if s <> String.Empty then
srcs <- tmp.ToString() :: srcs
member _.AppendSwitchesIfNotNull(switch: string, values: string[], sep: string) =
builder.AppendSwitchIfNotNull(switch, values, sep)
let tmp = new CommandLineBuilder()
tmp.AppendSwitchUnquotedIfNotNull(switch, values, sep)
let s = tmp.ToString()
if s <> String.Empty then
args <- s :: args
member _.AppendSwitchIfNotNull(switch: string, value: string MaybeNull, ?metadataNames: string[]) =
let metadataNames = defaultArg metadataNames [||]
builder.AppendSwitchIfNotNull(switch, value)
let tmp = new CommandLineBuilder()
tmp.AppendSwitchUnquotedIfNotNull(switch, value)
let providedMetaData =
metadataNames |> Array.filter (String.IsNullOrWhiteSpace >> not)
if providedMetaData.Length > 0 then
tmp.AppendTextUnquoted ","
tmp.AppendTextUnquoted(providedMetaData |> String.concat ",")
let s = tmp.ToString()
if s <> String.Empty then
args <- s :: args
member this.AppendOptionalSwitch(switch: string, value: bool option) =
match value with
| Some true -> this.AppendSwitch($"{switch}+")
| Some false -> this.AppendSwitch($"{switch}-")
| None -> ()
member _.AppendSwitchUnquotedIfNotNull(switch: string, value: string MaybeNull) =
assert (switch = "") // we only call this method for "OtherFlags"
// Unfortunately we still need to mimic what cmd.exe does, but only for "OtherFlags".
let ParseCommandLineArgs (commandLine: string) = // returns list in reverse order
let mutable args = []
let mutable i = 0 // index into commandLine
let len = commandLine.Length
while i < len do
// skip whitespace
while i < len && System.Char.IsWhiteSpace(commandLine, i) do
i <- i + 1
if i < len then
// parse an argument
let sb = new StringBuilder()
let mutable finished = false
let mutable insideQuote = false
while i < len && not finished do
match commandLine.[i] with
| '"' ->
insideQuote <- not insideQuote
i <- i + 1
| c when not insideQuote && System.Char.IsWhiteSpace(c) -> finished <- true
| c ->
sb.Append(c) |> ignore
i <- i + 1
args <- sb.ToString() :: args
args
builder.AppendSwitchUnquotedIfNotNull(switch, value)
let tmp = new CommandLineBuilder()
tmp.AppendSwitchUnquotedIfNotNull(switch, value)
let s = tmp.ToString()
if s <> String.Empty then
args <- ParseCommandLineArgs(s) @ args
member _.AppendSwitch(switch: string) =
builder.AppendSwitch(switch)
args <- switch :: args
member internal x.GetCapturedArguments() =
[| yield! x.CapturedArguments(); yield! x.CapturedFilenames() |]