Skip to content

Commit ff49b0c

Browse files
committed
Added support for global options.
1 parent 20dfa72 commit ff49b0c

File tree

3 files changed

+82
-11
lines changed

3 files changed

+82
-11
lines changed

src/FSharp.SystemCommandLine/CommandBuilders.fs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,20 @@ let private parseInput<'V> (handlerInputs: HandlerInput list) (ctx: IC) (idx: in
1717
| ParsedArgument a -> ctx.ParseResult.GetValueForArgument<'V>(a :?> Argument<'V>)
1818
| Context -> ctx |> unbox<'V>
1919

20+
let private addGlobalOptionsToParser (globalInputs: HandlerInput list) (parser: Parser) =
21+
globalInputs
22+
|> List.iter (fun gi ->
23+
match gi.Source with
24+
| ParsedOption o -> parser.Configuration.RootCommand.AddGlobalOption(o)
25+
| _ -> ()
26+
)
27+
parser
28+
2029
type CommandSpec<'Inputs, 'Output> =
2130
{
2231
Description: string
2332
Inputs: HandlerInput list
33+
GlobalInputs: HandlerInput list
2434
Handler: 'Inputs -> 'Output
2535
Aliases: string list
2636
SubCommands: System.CommandLine.Command list
@@ -31,6 +41,7 @@ type CommandSpec<'Inputs, 'Output> =
3141
{
3242
Description = "My Command"
3343
Inputs = []
44+
GlobalInputs = []
3445
Aliases = []
3546
ExtraInputs = []
3647
Handler = def<unit -> 'Output> // Support unit -> 'Output handler by default
@@ -44,6 +55,7 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>() =
4455
{
4556
Description = spec.Description
4657
Inputs = spec.Inputs
58+
GlobalInputs = spec.GlobalInputs
4759
Aliases = spec.Aliases
4860
ExtraInputs = spec.ExtraInputs
4961
Handler = handler
@@ -108,6 +120,14 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>() =
108120
member this.SetHandler (spec: CommandSpec<'Inputs, 'Output>, handler: 'Inputs -> 'Output) =
109121
newHandler handler spec
110122

123+
[<CustomOperation("addGlobalOption")>]
124+
member this.AddGlobalOption (spec: CommandSpec<'Inputs, 'Output>, globalInput: HandlerInput) =
125+
{ spec with GlobalInputs = spec.GlobalInputs @ [ globalInput ] }
126+
127+
[<CustomOperation("addGlobalOptions")>]
128+
member this.AddGlobalOptions (spec: CommandSpec<'Inputs, 'Output>, globalInputs: HandlerInput seq) =
129+
{ spec with GlobalInputs = spec.GlobalInputs @ (globalInputs |> List.ofSeq) }
130+
111131
[<Obsolete("'setCommand' has been deprecated in favor of 'addCommand' or 'addCommands'.")>]
112132
[<CustomOperation("setCommand")>]
113133
member this.SetCommand (spec: CommandSpec<'Inputs, 'Output>, subCommand: System.CommandLine.Command) =
@@ -401,6 +421,7 @@ type RootCommandParserBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>() =
401421
|> this.SetHandlerUnit spec
402422
|> ignore
403423
this.CommandLineBuilder.Build()
424+
|> addGlobalOptionsToParser spec.GlobalInputs
404425

405426
/// Executes a Command with a handler that returns int.
406427
member this.Run (spec: CommandSpec<'Inputs, int>) =
@@ -409,6 +430,7 @@ type RootCommandParserBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>() =
409430
|> this.SetHandlerInt spec
410431
|> ignore
411432
this.CommandLineBuilder.Build()
433+
|> addGlobalOptionsToParser spec.GlobalInputs
412434

413435
/// Executes a Command with a handler that returns a Task<unit> or Task<int>.
414436
member this.Run (spec: CommandSpec<'Inputs, Task<'ReturnValue>>) =
@@ -417,6 +439,7 @@ type RootCommandParserBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>() =
417439
|> this.SetHandlerTask spec
418440
|> ignore
419441
this.CommandLineBuilder.Build()
442+
|> addGlobalOptionsToParser spec.GlobalInputs
420443

421444

422445
/// Builds and executes a `System.CommandLine.RootCommand`.
@@ -439,46 +462,73 @@ type RootCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>(args: string ar
439462
|> this.SetGeneralProperties spec
440463
|> this.SetHandlerUnit spec
441464
|> ignore
442-
this.CommandLineBuilder.Build().Parse(args).Invoke()
465+
466+
let parser =
467+
this.CommandLineBuilder.Build()
468+
|> addGlobalOptionsToParser spec.GlobalInputs
469+
470+
parser.Parse(args).Invoke()
443471

444472
/// Executes a Command with a handler that returns int.
445473
member this.Run (spec: CommandSpec<'Inputs, int>) =
446474
this.CommandLineBuilder.Command
447475
|> this.SetGeneralProperties spec
448476
|> this.SetHandlerInt spec
449477
|> ignore
450-
this.CommandLineBuilder.Build().Parse(args).Invoke()
478+
479+
let parser =
480+
this.CommandLineBuilder.Build()
481+
|> addGlobalOptionsToParser spec.GlobalInputs
482+
483+
parser.Parse(args).Invoke()
451484

452485
/// Executes a Command with a handler that returns a Task<unit> or Task<int>.
453486
member this.Run (spec: CommandSpec<'Inputs, Task<'ReturnValue>>) =
454487
this.CommandLineBuilder.Command
455488
|> this.SetGeneralProperties spec
456489
|> this.SetHandlerTask spec
457490
|> ignore
458-
this.CommandLineBuilder.Build().Parse(args).InvokeAsync()
491+
492+
let parser =
493+
this.CommandLineBuilder.Build()
494+
|> addGlobalOptionsToParser spec.GlobalInputs
495+
496+
parser.Parse(args).InvokeAsync()
459497

460498

461499
/// Builds a `System.CommandLine.Command`.
462500
type CommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>(name: string) =
463501
inherit BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>()
464502

503+
let addGlobalOptionsToCommand (globalOptions: HandlerInput list) (cmd: Command) =
504+
globalOptions
505+
|> List.iter (fun g ->
506+
match g.Source with
507+
| ParsedOption o -> cmd.AddGlobalOption(o)
508+
| _ -> ()
509+
)
510+
cmd
511+
465512
/// Returns a Command with a handler that returns unit.
466513
member this.Run (spec: CommandSpec<'Inputs, unit>) =
467514
Command(name)
468515
|> this.SetGeneralProperties spec
469516
|> this.SetHandlerUnit spec
517+
|> addGlobalOptionsToCommand spec.GlobalInputs
470518

471519
/// Executes a Command with a handler that returns int.
472520
member this.Run (spec: CommandSpec<'Inputs, int>) =
473521
Command(name)
474522
|> this.SetGeneralProperties spec
475523
|> this.SetHandlerInt spec
524+
|> addGlobalOptionsToCommand spec.GlobalInputs
476525

477526
/// Executes a Command with a handler that returns a Task<unit> or Task<int>.
478527
member this.Run (spec: CommandSpec<'Inputs, Task<'ReturnValue>>) =
479528
Command(name)
480529
|> this.SetGeneralProperties spec
481530
|> this.SetHandlerTask spec
531+
|> addGlobalOptionsToCommand spec.GlobalInputs
482532

483533

484534
/// Builds a `System.CommandLine.RootCommand` using computation expression syntax.

src/TestConsole/ProgramNestedSubCommands.fs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,26 @@
22

33
open System.IO
44
open FSharp.SystemCommandLine
5+
open System.CommandLine.Invocation
6+
7+
module Global =
8+
let enableLogging = Input.Option<bool>("--enable-logging", false)
9+
let logFile = Input.Option<FileInfo>("--log-file", FileInfo @"c:\temp\default.log")
10+
11+
type Options = { EnableLogging: bool; LogFile: FileInfo }
12+
13+
let options: HandlerInput seq = [ enableLogging; logFile ]
14+
15+
let bind (ctx: InvocationContext) =
16+
{ EnableLogging = enableLogging.GetValue ctx
17+
LogFile = logFile.GetValue ctx }
518

619
let listCmd =
7-
let handler (dir: DirectoryInfo) =
20+
let handler (ctx: InvocationContext, dir: DirectoryInfo) =
21+
let options = Global.bind ctx
22+
if options.EnableLogging then
23+
printfn $"Logging enabled to {options.LogFile.FullName}"
24+
825
if dir.Exists then
926
dir.EnumerateFiles()
1027
|> Seq.iter (fun f -> printfn "%s" f.FullName)
@@ -15,13 +32,17 @@ let listCmd =
1532

1633
command "list" {
1734
description "lists contents of a directory"
18-
inputs dir
35+
inputs (Input.Context(), dir)
1936
setHandler handler
2037
addAlias "ls"
2138
}
2239

2340
let deleteCmd =
24-
let handler (dir: DirectoryInfo, recursive: bool) =
41+
let handler (ctx: InvocationContext, dir: DirectoryInfo, recursive: bool) =
42+
let options = Global.bind ctx
43+
if options.EnableLogging then
44+
printfn $"Logging enabled to {options.LogFile.FullName}"
45+
2546
if dir.Exists then
2647
if recursive then
2748
printfn $"Recursively deleting {dir.FullName}"
@@ -35,7 +56,7 @@ let deleteCmd =
3556

3657
command "delete" {
3758
description "deletes a directory"
38-
inputs (dir, recursive)
59+
inputs (Input.Context(), dir, recursive)
3960
setHandler handler
4061
addAlias "del"
4162
}
@@ -47,11 +68,11 @@ let ioCmd =
4768
addCommands [ deleteCmd; listCmd ]
4869
}
4970

50-
51-
// [<EntryPoint>]
71+
//[<EntryPoint>]
5272
let main argv =
5373
rootCommand argv {
5474
description "Sample app for System.CommandLine"
5575
setHandler id
76+
addGlobalOptions Global.options
5677
addCommand ioCmd
5778
}

src/TestConsole/Properties/launchSettings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
"IO Nested SubCommands": {
1616
"commandName": "Project",
17-
"commandLineArgs": "io list c:/data/"
18-
},
17+
"commandLineArgs": "io list c:/data/ --enable-logging true"
18+
}
1919
}
2020
}

0 commit comments

Comments
 (0)