Skip to content

Commit

Permalink
Implemented a way to manually parse >8 inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
JordanMarr committed Jan 14, 2023
1 parent 8f6f68b commit bd53d14
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 12 deletions.
45 changes: 36 additions & 9 deletions src/FSharp.SystemCommandLine/CommandBuilders.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ open System.CommandLine
open System.CommandLine.Builder
open System.CommandLine.Parsing

type private HI<'T> = HandlerInput<'T>
type private IC = System.CommandLine.Invocation.InvocationContext
let private def<'T> = Unchecked.defaultof<'T>

Expand All @@ -24,11 +23,14 @@ type CommandSpec<'Inputs, 'Output> =
Inputs: HandlerInput list
Handler: 'Inputs -> 'Output
SubCommands: System.CommandLine.Command list
/// Registers extra inputs that can be parsed via the InvocationContext if more than 8 are required.
ExtraInputs: HandlerInput list
}
static member Default =
{
Description = "My Command"
Inputs = []
ExtraInputs = []
Handler = def<unit -> 'Output> // Support unit -> 'Output handler by default
SubCommands = []
}
Expand All @@ -40,6 +42,7 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>() =
{
Description = spec.Description
Inputs = spec.Inputs
ExtraInputs = spec.ExtraInputs
Handler = handler
SubCommands = spec.SubCommands
}
Expand All @@ -52,42 +55,52 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>() =
member this.Zero _ =
CommandSpec<unit, 'Output>.Default

/// A description that will be displayed to the command line user.
[<CustomOperation("description")>]
member this.Description (spec: CommandSpec<'T, 'U>, description) =
{ spec with Description = description }

/// A tuple of inputs (max. 8) that must exactly match the handler function inputs.
[<CustomOperation("inputs")>]
member this.Inputs (spec: CommandSpec<'T, 'Output>, a: HI<'A>) =
member this.Inputs (spec: CommandSpec<'T, 'Output>, a: HandlerInput<'A>) =
{ newHandler def<'A -> 'Output> spec with Inputs = [ a ] }

/// A tuple of inputs (max. 8) that must exactly match the handler function inputs.
[<CustomOperation("inputs")>]
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>)) =
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HandlerInput<'A>, b: HandlerInput<'B>)) =
{ newHandler def<'A * 'B -> 'Output> spec with Inputs = [ a; b ] }

/// A tuple of inputs (max. 8) that must exactly match the handler function inputs.
[<CustomOperation("inputs")>]
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>)) =
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HandlerInput<'A>, b: HandlerInput<'B>, c: HandlerInput<'C>)) =
{ newHandler def<'A * 'B * 'C -> 'Output> spec with Inputs = [ a; b; c ] }

/// A tuple of inputs (max. 8) that must exactly match the handler function inputs.
[<CustomOperation("inputs")>]
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>)) =
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HandlerInput<'A>, b: HandlerInput<'B>, c: HandlerInput<'C>, d: HandlerInput<'D>)) =
{ newHandler def<'A * 'B * 'C * 'D -> 'Output> spec with Inputs = [ a; b; c; d ] }

/// A tuple of inputs (max. 8) that must exactly match the handler function inputs.
[<CustomOperation("inputs")>]
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HI<'A>, b: HI<'B>, c: HI<'C>, d: HI<'D>, e: HI<'E>)) =
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HandlerInput<'A>, b: HandlerInput<'B>, c: HandlerInput<'C>, d: HandlerInput<'D>, e: HandlerInput<'E>)) =
{ newHandler def<'A * 'B * 'C * 'D * 'E -> 'Output> spec with Inputs = [ a; b; c; d; e ] }

/// A tuple of inputs (max. 8) that must exactly match the handler function inputs.
[<CustomOperation("inputs")>]
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>)) =
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HandlerInput<'A>, b: HandlerInput<'B>, c: HandlerInput<'C>, d: HandlerInput<'D>, e: HandlerInput<'E>, f: HandlerInput<'F>)) =
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F -> 'Output> spec with Inputs = [ a; b; c; d; e; f ] }

/// A tuple of inputs (max. 8) that must exactly match the handler function inputs.
[<CustomOperation("inputs")>]
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>)) =
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HandlerInput<'A>, b: HandlerInput<'B>, c: HandlerInput<'C>, d: HandlerInput<'D>, e: HandlerInput<'E>, f: HandlerInput<'F>, g: HandlerInput<'G>)) =
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g ] }

/// A tuple of inputs (max. 8) that must exactly match the handler function inputs.
[<CustomOperation("inputs")>]
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>)) =
member this.Inputs (spec: CommandSpec<'T, 'Output>, (a: HandlerInput<'A>, b: HandlerInput<'B>, c: HandlerInput<'C>, d: HandlerInput<'D>, e: HandlerInput<'E>, f: HandlerInput<'F>, g: HandlerInput<'G>, h: HandlerInput<'H>)) =
{ newHandler def<'A * 'B * 'C * 'D * 'E * 'F * 'G * 'H -> 'Output> spec with Inputs = [ a; b; c; d; e; f; g; h ] }

/// Sets a handler function that takes a tuple of inputs (max. 8). NOTE: This must be set after the inputs.
[<CustomOperation("setHandler")>]
member this.SetHandler (spec: CommandSpec<'Inputs, 'Output>, handler: 'Inputs -> 'Output) =
newHandler handler spec
Expand All @@ -97,14 +110,21 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>() =
member this.SetCommand (spec: CommandSpec<'Inputs, 'Output>, subCommand: System.CommandLine.Command) =
{ spec with SubCommands = spec.SubCommands @ [ subCommand ] }

/// Adds a sub-command.
[<CustomOperation("addCommand")>]
member this.AddCommand (spec: CommandSpec<'Inputs, 'Output>, subCommand: System.CommandLine.Command) =
{ spec with SubCommands = spec.SubCommands @ [ subCommand ] }

/// Adds sub-commands.
[<CustomOperation("addCommands")>]
member this.AddCommands (spec: CommandSpec<'Inputs, 'Output>, subCommands: System.CommandLine.Command seq) =
{ spec with SubCommands = spec.SubCommands @ (subCommands |> Seq.toList) }

/// Registers an additional input that can be manually parsed via the InvocationContext. (Use when more than 8 inputs are required.)
[<CustomOperation("add")>]
member this.Add(spec: CommandSpec<'Inputs, 'Output>, extraInput: HandlerInput<'Value>) =
{ spec with ExtraInputs = spec.ExtraInputs @ [ extraInput ] }

/// Sets general properties on the command.
member this.SetGeneralProperties (spec: CommandSpec<'T, 'U>) (cmd: Command) =
cmd.Description <- spec.Description
Expand All @@ -115,6 +135,13 @@ type BaseCommandBuilder<'A, 'B, 'C, 'D, 'E, 'F, 'G, 'H, 'Output>() =
| ParsedArgument a -> cmd.AddArgument a
| Context -> ()
)
spec.ExtraInputs
|> Seq.iter (fun input ->
match input.Source with
| ParsedOption o -> cmd.AddOption o
| ParsedArgument a -> cmd.AddArgument a
| Context -> ()
)

spec.SubCommands |> List.iter cmd.AddCommand
cmd
Expand Down
4 changes: 2 additions & 2 deletions src/FSharp.SystemCommandLine/FSharp.SystemCommandLine.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Version>0.13.0-beta4</Version>
<Version>0.14.0-beta4</Version>
<Description>F# computation expressions for working with the System.CommandLine API.</Description>
<Authors>Jordan Marr</Authors>
<PackageTags>F# fsharp System.CommandLine</PackageTags>
<PackageTags>F# fsharp System.CommandLine cli</PackageTags>
<PackageProjectUrl>https://github.com/JordanMarr/FSharp.SystemCommandLine</PackageProjectUrl>
</PropertyGroup>

Expand Down
5 changes: 5 additions & 0 deletions src/FSharp.SystemCommandLine/Inputs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ type HandlerInput<'T>(inputType: HandlerInputSource) =
inherit HandlerInput(inputType)
static member OfOption<'T>(o: Option<'T>) = o :> Option |> ParsedOption |> HandlerInput<'T>
static member OfArgument<'T>(a: Argument<'T>) = a :> Argument |> ParsedArgument |> HandlerInput<'T>
member this.GetValue(ctx: System.CommandLine.Invocation.InvocationContext) =
match this.Source with
| ParsedOption o -> o :?> Option<'T> |> ctx.ParseResult.GetValueForOption
| ParsedArgument a -> a :?> Argument<'T> |> ctx.ParseResult.GetValueForArgument
| Context -> ctx |> unbox<'T>


/// Creates CLI options and arguments to be passed as command `inputs`.
Expand Down
2 changes: 1 addition & 1 deletion src/TestConsole/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let app (words: string array, separator: string option) =
System.String.Join(separator, words) |> printfn "Result: %s"
0

//[<EntryPoint>]
[<EntryPoint>]
let main argv =
let words = Input.Option(["--word"; "-w"], Array.empty, "A list of words to be appended")
let separator = Input.OptionMaybe(["--separator"; "-s"], "A character that will separate the joined words.")
Expand Down
30 changes: 30 additions & 0 deletions src/TestConsole/ProgramExtraInputs.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// This option can be used when more than 8 inputs are required.
module ProgramExtraInputs

open FSharp.SystemCommandLine

module Parameters =
let words = Input.Option<string[]>(["--word"; "-w"], Array.empty, "A list of words to be appended")
let separator = Input.OptionMaybe<string>(["--separator"; "-s"], "A character that will separate the joined words.")

let app (ctx: System.CommandLine.Invocation.InvocationContext) =
// Manually parse parameters
let words = Parameters.words.GetValue ctx
let separator = Parameters.separator.GetValue ctx

// Append words together
let separator = separator |> Option.defaultValue ", "
System.String.Join(separator, words) |> printfn "Result: %s"
0

//[<EntryPoint>]
let main argv =
let ctx = Input.Context()

rootCommand argv {
description "Appends words together"
inputs ctx
setHandler app
add Parameters.words
add Parameters.separator
}
1 change: 1 addition & 0 deletions src/TestConsole/TestConsole.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Compile Include="ProgramSubCommand.fs" />
<Compile Include="ProgramTask.fs" />
<Compile Include="ProgramTokenReplacer.fs" />
<Compile Include="ProgramExtraInputs.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

Expand Down

0 comments on commit bd53d14

Please sign in to comment.