Skip to content

v2.0.0-beta5

Compare
Choose a tag to compare
@JordanMarr JordanMarr released this 26 Jun 16:29
· 58 commits to main since this release

This release provides compatibility with the latest System.CommandLine v2.0.0-beta5 rework!

New Input API

The new Input API has been redesigned to be easier to use and more extensible.
View the Input API in the README for more details and extensibility examples.

Old:

let unzipFile = Input.Argument<FileInfo>("The file to unzip")
let outputDir = Input.OptionMaybe<DirectoryInfo>(["--output"; "-o"], "The output directory")

New:

let unzipFile = argument<FileInfo> "unzipFile" |> desc "The file to unzip"
let outputDir = optionMaybe<DirectoryInfo> "--output" |> alias "-o" |> desc "The output directory"

Improved Validation

The new Input.validate function allows you to return a validation Result against the parsed value.
(There is also an addValidator function that aligns more closely to the built-in System.CommandLine approach.)

open System.IO
open FSharp.SystemCommandLine
open Input

let unzip (zipFile: FileInfo, outputDirMaybe: DirectoryInfo option) = 
    // Default to the zip file dir if None
    let outputDir = defaultArg outputDirMaybe zipFile.Directory
    printfn $"Unzipping {zipFile.Name} to {outputDir.FullName}..."
    
[<EntryPoint>]
let main argv = 
    rootCommand argv {
        description "Unzips a .zip file"
        inputs (
            argument "zipfile"
            |> description "The file to unzip"
            |> validateFileExists
            |> validate (fun zipFile ->
                if zipFile.Length > 500000
                then Error $"File cannot be bigger than 500 KB"
                else Ok ()
            ),

            optionMaybe "--output"
            |> alias "-o"
            |> description "The output directory"
            |> validateDirectoryExists
        )
        setAction unzip
    }

Showing Help by Default in Root Command

For apps that utilize sub-commands, a common pattern is to display help output when no sub-command is entered or do nothing.

To show help, you can replace setAction with one of two new custom operations: helpAction or helpActionAsync.
NOTE that you must include inputs Input.context.

Non-async:

[<EntryPoint>]
let main argv =
    rootCommand argv {
        description "File System Manager"
        inputs Input.context
        helpAction
        addCommands [ listCmd; deleteCmd ]
    }

Async:

[<EntryPoint>]
let main argv =
    rootCommand argv {
        description "File System Manager"
        inputs Input.context
        helpActionAsync
        addCommands [ listCmd; deleteCmd ]
    }
    |> Async.AwaitTask
    |> Async.RunSynchronously

No Action in Root Command

Another common use case for apps that use only sub-commands is to not provide any action (handler) function for the rootCommand.
Previously, you would do this by setting the action as setAction id or setAction Task.FromResult for async apps.

Now you can express these as: noAction or noActionAsync for async apps.

Non-async:

[<EntryPoint>]
let main argv =
    rootCommand argv {
        description "File System Manager"
        noAction
        addCommands [ listCmd; deleteCmd ]
    }

Async app:

[<EntryPoint>]
let main argv =
    rootCommand argv {
        description "File System Manager"
        noActionAsync
        addCommands [ listCmd; deleteCmd ]
    }
    |> Async.AwaitTask
    |> Async.RunSynchronously

Breaking Changes

  • setHandler is deprecated in favor of setAction to maintain parity with System.CommandLine v2.0.0.beta5 naming changes.
  • Input methods like Input.Option, Input.Argument, Input.OptionMaybe, Input.ArgumentMaybe have been replaced with pipelined functions to avoid "overload soup" and to make it easier and more extensible for users to create their own custom functions against the underlying System.CommandLine API.