Skip to content

Commit bca372e

Browse files
committed
Added support for injected inputs
1 parent 023d926 commit bca372e

4 files changed

Lines changed: 92 additions & 39 deletions

File tree

src/FSharp.SystemCommandLine/CommandBuilders.fs

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ open System.CommandLine.Binding
88
open System.CommandLine.Builder
99
open System.CommandLine.Parsing
1010

11-
type private IVD<'T> = IValueDescriptor<'T>
11+
type private HI<'T> = HandlerInput<'T>
1212
let private def<'T> = Unchecked.defaultof<'T>
1313

1414
type CommandSpec<'Inputs, 'Output> =
1515
{
1616
Description: string
17-
Inputs: IValueDescriptor list
17+
Inputs: HandlerInput list
1818
Handler: 'Inputs -> 'Output
1919
SubCommands: System.CommandLine.Command list
2020
}
@@ -36,7 +36,7 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'I, 'J, 'K, 'L, 'M, 'N,
3636
Handler = handler
3737
SubCommands = spec.SubCommands
3838
}
39-
39+
4040
member val CommandLineBuilder = CommandLineBuilder().UseDefaults() with get, set
4141

4242
member this.Yield _ =
@@ -50,67 +50,67 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'I, 'J, 'K, 'L, 'M, 'N,
5050
{ spec with Description = description }
5151

5252
[<CustomOperation("inputs")>]
53-
member this.Inputs (spec: CommandSpec<'T, 'Output>, a: IVD<'A>) =
53+
member this.Inputs (spec: CommandSpec<'T, 'Output>, a: HI<'A>) =
5454
{ newHandler def<'A -> 'Output> spec with Inputs = [ a ] }
5555

5656
[<CustomOperation("inputs")>]
57-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>)) =
57+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>)) =
5858
{ newHandler def<'A * 'B -> 'Output> spec with Inputs = [ a; b ] }
5959

6060
[<CustomOperation("inputs")>]
61-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>)) =
61+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>)) =
6262
{ newHandler def<'A * 'B * 'C -> 'Output> spec with Inputs = [ a; b; c ] }
6363

6464
[<CustomOperation("inputs")>]
65-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>)) =
65+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>)) =
6666
{ newHandler def<'A * 'B * 'C * 'D -> 'Output> spec with Inputs = [ a; b; c; d ] }
6767

6868
[<CustomOperation("inputs")>]
69-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>)) =
69+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>)) =
7070
{ newHandler def<'A * 'B * 'C * 'D * 'E -> 'Output> spec with Inputs = [ a; b; c; d; e ] }
7171

7272
[<CustomOperation("inputs")>]
73-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>)) =
73+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>)) =
7474
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F -> 'Output> spec with Inputs = [ a; b; c; d; e; f ] }
7575

7676
[<CustomOperation("inputs")>]
77-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>)) =
77+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>)) =
7878
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g ] }
7979

8080
[<CustomOperation("inputs")>]
81-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>, h: IVD<'H>)) =
81+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>, h: HI<'H>)) =
8282
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h ] }
8383

8484
[<CustomOperation("inputs")>]
85-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>, h: IVD<'H>, i: IVD<'I>)) =
85+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>, h: HI<'H>, i: HI<'I>)) =
8686
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H * 'I -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h; i ] }
8787

8888
[<CustomOperation("inputs")>]
89-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>, h: IVD<'H>, i: IVD<'I>, j: IVD<'J>)) =
89+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>, h: HI<'H>, i: HI<'I>, j: HI<'J>)) =
9090
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H * 'I * 'J -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h; i; j ] }
9191

9292
[<CustomOperation("inputs")>]
93-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>, h: IVD<'H>, i: IVD<'I>, j: IVD<'J>, k: IVD<'K>)) =
93+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>, h: HI<'H>, i: HI<'I>, j: HI<'J>, k: HI<'K>)) =
9494
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H * 'I * 'J * 'K -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h; i; j; k ] }
9595

9696
[<CustomOperation("inputs")>]
97-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>, h: IVD<'H>, i: IVD<'I>, j: IVD<'J>, k: IVD<'K>, l: IVD<'L>)) =
97+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>, h: HI<'H>, i: HI<'I>, j: HI<'J>, k: HI<'K>, l: HI<'L>)) =
9898
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H * 'I * 'J * 'K * 'L -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h; i; j; k; l ] }
9999

100100
[<CustomOperation("inputs")>]
101-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>, h: IVD<'H>, i: IVD<'I>, j: IVD<'J>, k: IVD<'K>, l: IVD<'L>, m: IVD<'M>)) =
101+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>, h: HI<'H>, i: HI<'I>, j: HI<'J>, k: HI<'K>, l: HI<'L>, m: HI<'M>)) =
102102
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H * 'I * 'J * 'K * 'L * 'M -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h; i; j; k; l; m ] }
103103

104104
[<CustomOperation("inputs")>]
105-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>, h: IVD<'H>, i: IVD<'I>, j: IVD<'J>, k: IVD<'K>, l: IVD<'L>, m: IVD<'M>, n: IVD<'N>)) =
105+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>, h: HI<'H>, i: HI<'I>, j: HI<'J>, k: HI<'K>, l: HI<'L>, m: HI<'M>, n: HI<'N>)) =
106106
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H * 'I * 'J * 'K * 'L * 'M * 'N -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h; i; j; k; l; m; n ] }
107107

108108
[<CustomOperation("inputs")>]
109-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>, h: IVD<'H>, i: IVD<'I>, j: IVD<'J>, k: IVD<'K>, l: IVD<'L>, m: IVD<'M>, n: IVD<'N>, o: IVD<'O>)) =
109+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>, h: HI<'H>, i: HI<'I>, j: HI<'J>, k: HI<'K>, l: HI<'L>, m: HI<'M>, n: HI<'N>, o: HI<'O>)) =
110110
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H * 'I * 'J * 'K * 'L * 'M * 'N * 'O -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h; i; j; k; l; m; n; o ] }
111111

112112
[<CustomOperation("inputs")>]
113-
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: IVD<'A>, b: IVD<'B>, c: IVD<'C>, d: IVD<'D>, e: IVD<'E>, f: IVD<'F>, g: IVD<'G>, h: IVD<'H>, i: IVD<'I>, j: IVD<'J>, k: IVD<'K>, l: IVD<'L>, m: IVD<'M>, n: IVD<'N>, o: IVD<'O>, p: IVD<'P>)) =
113+
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>, f: HI<'F>, g: HI<'G>, h: HI<'H>, i: HI<'I>, j: HI<'J>, k: HI<'K>, l: HI<'L>, m: HI<'M>, n: HI<'N>, o: HI<'O>, p: HI<'P>)) =
114114
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H * 'I * 'J * 'K * 'L * 'M * 'N * 'O * 'P -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h; i; j; k; l; m; n; o; p ] }
115115

116116
[<CustomOperation("setHandler")>]
@@ -124,16 +124,31 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'I, 'J, 'K, 'L, 'M, 'N,
124124
/// Sets general properties on the command.
125125
member this.SetGeneralProperties (spec: CommandSpec<'T, 'U>) (cmd: Command) =
126126
cmd.Description <- spec.Description
127-
spec.Inputs |> List.choose (function | :? Option as opt -> Some opt | _ -> None) |> List.iter cmd.AddOption
128-
spec.Inputs |> List.choose (function | :? Argument as arg -> Some arg | _ -> None) |> List.iter cmd.AddArgument
127+
spec.Inputs
128+
|> Seq.iter (fun input ->
129+
match input.Source with
130+
| ParsedOption o -> cmd.AddOption o
131+
| ParsedArgument a -> cmd.AddArgument a
132+
| InjectedDependency -> () // DI system will inject this input
133+
)
134+
129135
spec.SubCommands |> List.iter cmd.AddCommand
130136
cmd
131137

132138
/// Sets a command handler that returns unit.
133139
member this.SetActionHandler (spec: CommandSpec<'Inputs, unit>) (cmd: Command) =
134-
let inputs = spec.Inputs |> List.toArray
135140
let handler (args: obj) = spec.Handler (args :?> 'Inputs)
136141

142+
let inputs =
143+
spec.Inputs
144+
|> List.choose (fun input ->
145+
match input.Source with
146+
| ParsedOption o -> o :> IValueDescriptor |> Some
147+
| ParsedArgument a -> a :> IValueDescriptor |> Some
148+
| InjectedDependency -> None
149+
)
150+
|> List.toArray
151+
137152
match spec.Inputs.Length with
138153
| 00 -> cmd.SetHandler(Action(fun () -> handler ()))
139154
| 01 -> cmd.SetHandler(Action<'A>(fun a -> handler (a)), inputs)
@@ -157,12 +172,21 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'I, 'J, 'K, 'L, 'M, 'N,
157172

158173
/// Sets a command handler that returns a Task.
159174
member this.SetFuncHandler (spec: CommandSpec<'Inputs, Task<'ReturnValue>>) (cmd: Command) =
160-
let inputs = spec.Inputs |> List.toArray
161175
let handler (args: obj) =
162176
task {
163177
return! spec.Handler (args :?> 'Inputs)
164178
}
165179

180+
let inputs =
181+
spec.Inputs
182+
|> List.choose (fun input ->
183+
match input.Source with
184+
| ParsedOption o -> o :> IValueDescriptor |> Some
185+
| ParsedArgument a -> a :> IValueDescriptor |> Some
186+
| InjectedDependency -> None
187+
)
188+
|> List.toArray
189+
166190
match spec.Inputs.Length with
167191
| 00 -> cmd.SetHandler(Func<Task>(fun () -> handler ()))
168192
| 01 -> cmd.SetHandler(Func<'A, Task>(fun a -> handler (a)), inputs)

src/FSharp.SystemCommandLine/FSharp.SystemCommandLine.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<ItemGroup>
1414
<Compile Include="Aliases.fs" />
15-
<Compile Include="Types.fs" />
15+
<Compile Include="Inputs.fs" />
1616
<Compile Include="CommandBuilders.fs" />
1717
</ItemGroup>
1818

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[<AutoOpen>]
2-
module FSharp.SystemCommandLine.Types
2+
module FSharp.SystemCommandLine.Inputs
33

44
open System
55
open System.CommandLine
@@ -13,38 +13,55 @@ module private Parser =
1313
| t when t = typeof<IO.FileInfo> -> IO.FileInfo(tokenValue) |> box :?> 'T |> Some
1414
| t -> Convert.ChangeType(tokenValue, t) :?> 'T |> Some
1515

16+
type HandlerInputSource =
17+
| ParsedOption of Option
18+
| ParsedArgument of Argument
19+
| InjectedDependency
20+
21+
type HandlerInput(source: HandlerInputSource) =
22+
member this.Source = source
23+
24+
type HandlerInput<'T>(inputType: HandlerInputSource) =
25+
inherit HandlerInput(inputType)
26+
static member OfOption<'T>(o: Option<'T>) = o :> Option |> ParsedOption |> HandlerInput<'T>
27+
static member OfArgument<'T>(a: Argument<'T>) = a :> Argument |> ParsedArgument |> HandlerInput<'T>
28+
1629
/// Creates CLI options and arguments to be passed as command `inputs`.
1730
type Input =
18-
31+
1932
/// Creates a CLI option of type 'T.
2033
static member Option<'T>(name: string, ?description: string) =
2134
Option<'T>(
2235
name,
2336
description = (description |> Option.defaultValue null)
24-
) :> Binding.IValueDescriptor<'T>
37+
)
38+
|> HandlerInput.OfOption
2539

2640
/// Creates a CLI option of type 'T.
2741
static member Option<'T>(aliases: string seq, ?description: string) =
2842
Option<'T>(
2943
aliases |> Seq.toArray,
3044
description = (description |> Option.defaultValue null)
31-
) :> Binding.IValueDescriptor<'T>
45+
)
46+
|> HandlerInput.OfOption
3247

3348
/// Creates a CLI option of type 'T with a default value.
3449
static member Option<'T>(name: string, defaultValue: 'T, ?description: string) =
3550
Option<'T>(
3651
name,
3752
getDefaultValue = (fun () -> defaultValue),
3853
description = (description |> Option.defaultValue null)
39-
) :> Binding.IValueDescriptor<'T>
54+
)
55+
|> HandlerInput.OfOption
4056

4157
/// Creates a CLI option of type 'T with a default value.
4258
static member Option<'T>(aliases: string seq, defaultValue: 'T, ?description: string) =
4359
Option<'T>(
4460
aliases |> Seq.toArray,
4561
getDefaultValue = (fun () -> defaultValue),
4662
description = (description |> Option.defaultValue null)
47-
) :> Binding.IValueDescriptor<'T>
63+
)
64+
|> HandlerInput.OfOption
4865

4966
/// Creates a CLI option of type 'T option.
5067
static member OptionMaybe<'T>(name: string, ?description: string) =
@@ -56,7 +73,8 @@ type Input =
5673
| None -> failwith "F# Option can only be used with a single argument."
5774
),
5875
description = (description |> Option.defaultValue null)
59-
) :> Binding.IValueDescriptor<'T option>
76+
)
77+
|> HandlerInput.OfOption
6078

6179
/// Creates a CLI option of type 'T option.
6280
static member OptionMaybe<'T>(aliases: string seq, ?description: string) =
@@ -68,22 +86,25 @@ type Input =
6886
| None -> failwith "F# Option can only be used with a single argument."
6987
),
7088
description = (description |> Option.defaultValue null)
71-
) :> Binding.IValueDescriptor<'T option>
89+
)
90+
|> HandlerInput.OfOption
7291

7392
/// Creates a CLI argument of type 'T.
7493
static member Argument<'T>(name: string, ?description: string) =
7594
Argument<'T>(
7695
name,
7796
description = (description |> Option.defaultValue null)
78-
) :> Binding.IValueDescriptor<'T>
97+
)
98+
|> HandlerInput.OfArgument
7999

80100
/// Creates a CLI argument of type 'T with a default value.
81101
static member Argument<'T>(name: string, defaultValue: 'T, ?description: string) =
82102
Argument<'T>(
83103
name,
84104
getDefaultValue = (fun () -> defaultValue),
85105
description = (description |> Option.defaultValue null)
86-
) :> Binding.IValueDescriptor<'T>
106+
)
107+
|> HandlerInput.OfArgument
87108

88109
/// Creates a CLI argument of type 'T option.
89110
static member ArgumentMaybe<'T>(name: string, ?description: string) =
@@ -95,4 +116,9 @@ type Input =
95116
| None -> failwith "F# Option can only be used with a single argument."
96117
),
97118
description = (description |> Option.defaultValue null)
98-
) :> Binding.IValueDescriptor<'T option>
119+
)
120+
|> HandlerInput.OfArgument
121+
122+
/// Creates an injected dependency input.
123+
static member InjectedDependency<'T>() =
124+
HandlerInput<'T>(InjectedDependency)

src/TestConsole/Program.fs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
module Program
2+
23
open FSharp.SystemCommandLine
4+
open System.CommandLine.Invocation
35

4-
let app (words: string array, separator: string option) =
6+
let app (ctx: InvocationContext, words: string array, separator: string option) =
57
let separator = separator |> Option.defaultValue ", "
6-
System.String.Join(separator, words)
7-
|> printfn "Result: %s"
8+
System.String.Join(separator, words) |> printfn "Result: %s"
9+
ctx.ExitCode <- 1
810

911
[<EntryPoint>]
1012
let main argv =
13+
let ctx = Input.InjectedDependency()
1114
let words = Input.Option(["--word"; "-w"], Array.empty, "A list of words to be appended")
1215
let separator = Input.OptionMaybe(["--separator"; "-s"], "A character that will separate the joined words.")
1316

1417
rootCommand argv {
1518
description "Appends words together"
16-
inputs (words, separator)
19+
inputs (ctx, words, separator)
1720
setHandler app
1821
}

0 commit comments

Comments
 (0)