Releases: JordanMarr/FSharp.SystemCommandLine
v2.0.0
v2.0.0-beta7
This release brings FSharp.SystemCommandLine up to date with the new v2.0.0-beta7 of System.CommandLine.
Changes
- BREAKING: The
commandLineConfigurationcomputation expression has been removed (becauseSystem.CommandLine.CommandLineConfigurationhas been removed). - A new
ManualInvocation.rootCommandhas been added. Unlike the defaultrootCommandwhich is automatically executes, this simply returns aSystem.CommandLine.RootCommandfor you to manually invoke:
module Global =
let enableLogging = option "--enable-logging" |> recursive
let zipCmd =
command "zip" {
description "Zips a directory."
inputs (argument "directory" |> validateDirectoryExists)
setAction (fun dir -> async { ... })
}
let unzipCmd =
command "unzip" {
description "Unzips a directory."
inputs (argument "directory" |> validateDirectoryExists)
setAction (fun dir -> async { ... })
}
let main (argv: string[]) =
let cmd =
ManualInvocation.rootCommand {
description "Zip or Unzip a Directory"
noActionAsync
addInputs Global.options
addCommands [ zipCmd; unzipCmd ]
}
let parseResult = cmd.Parse(argv)
let loggingEnabled = Global.enableLogging.GetValue parseResult
printfn $"Logging enabled: {loggingEnabled}"
parseResult.InvokeAsync()
|> Async.AwaitTask
|> Async.RunSynchronouslyconfigure(inrootCommand) has been deprecated.configureParserhas been added torootCommandand the newManualInvocation.rootCommand.configureInvocationhas been added torootCommand.
v2.0.0-beta5
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.RunSynchronouslyNo 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.RunSynchronouslyBreaking Changes
setHandleris deprecated in favor ofsetActionto maintain parity with System.CommandLine v2.0.0.beta5 naming changes.Inputmethods likeInput.Option,Input.Argument,Input.OptionMaybe,Input.ArgumentMaybehave 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.
v1.0.0-beta4
Changes
- Adds support for "global options".
- Added an overload to
InputGetValuemethod to get value from anInvocationContextor aParseResult. - Bumped version from
v0.18.0-beta4tov1.0.0-beta4so as not to scare people away from using the library.
See Global Options example in readme.
v0.18.0-beta4
New Features
- Adds support for "global options".
module ProgramNestedSubCommands
open System.IO
open FSharp.SystemCommandLine
open System.CommandLine.Invocation
module Global =
let enableLogging = Input.Option<bool>("--enable-logging", false)
let logFile = Input.Option<FileInfo>("--log-file", FileInfo @"c:\temp\default.log")
type Options = { EnableLogging: bool; LogFile: FileInfo }
let options: HandlerInput seq = [ enableLogging; logFile ]
let bind (ctx: InvocationContext) =
{ EnableLogging = enableLogging.GetValue ctx
LogFile = logFile.GetValue ctx }
let listCmd =
let handler (ctx: InvocationContext, dir: DirectoryInfo) =
let options = Global.bind ctx
if options.EnableLogging then
printfn $"Logging enabled to {options.LogFile.FullName}"
if dir.Exists then
dir.EnumerateFiles()
|> Seq.iter (fun f -> printfn "%s" f.FullName)
else
printfn $"{dir.FullName} does not exist."
let dir = Input.Argument("directory", DirectoryInfo(@"c:\default"))
command "list" {
description "lists contents of a directory"
inputs (Input.Context(), dir)
setHandler handler
addAlias "ls"
}
let deleteCmd =
let handler (ctx: InvocationContext, dir: DirectoryInfo, recursive: bool) =
let options = Global.bind ctx
if options.EnableLogging then
printfn $"Logging enabled to {options.LogFile.FullName}"
if dir.Exists then
if recursive then
printfn $"Recursively deleting {dir.FullName}"
else
printfn $"Deleting {dir.FullName}"
else
printfn $"{dir.FullName} does not exist."
let dir = Input.Argument("directory", DirectoryInfo(@"c:\default"))
let recursive = Input.Option("--recursive", false)
command "delete" {
description "deletes a directory"
inputs (Input.Context(), dir, recursive)
setHandler handler
addAlias "del"
}
let ioCmd =
command "io" {
description "Contains IO related subcommands."
setHandler id
addGlobalOptions Global.options
addCommands [ deleteCmd; listCmd ]
}
[<EntryPoint>]
let main argv =
rootCommand argv {
description "Sample app for System.CommandLine"
setHandler id
addCommand ioCmd
}Support for command aliases
Changes in v0.17.0-beta4
- New
addAliasandaddAliasesoperations to add command aliases. (thanks to @AngelMunoz! 🚀) - Deprecated
addoverloads and renamed toaddInputandaddInputsfor clarity and consistency
Support for netstandard, `add` overload
Input helpers
This release adds a few new Input methods to make it more convenient to modify the underlying System.CommandLine properties for Option and Argument inputs.
- New
Input.Optionoverloads that take aname/aliasand aconfigurefunction that allows you to manually set properties on the S.CLOption<'T>. - New
Input.OptionMaybeoverloads that take aname/aliasand aconfigurefunction that allows you to manually set properties on the S.CLOption<'T option>. - New
Input.Argumentoverloads that take aname/aliasand aconfigurefunction that allows you to manually set properties on the S.CLArgument<'T>.
Please see updated docs here:
https://github.com/JordanMarr/FSharp.SystemCommandLine#setting-input-properties-manually
Also, two new alias methods (for easier discoverability) for converting existing SCL inputs:
Input.OfOption- An alias forHandlerInput.OfOptionInput.OfArgument- An alias forHandlerInput.OfArgument
Handling of greater than 8 inputs
This release provides a convenient way to handle commands that require more than 8 inputs via the new add custom operation on the rootCommand and command builders.
Additional info
Currently, a command handler function is limited to accept a tuple with no more than eight inputs.
If you need more than eight inputs, you can now pass in the InvocationContext to your handler, which will allow you manually get as many input values as you like (assuming they have been registered via the rootCommand or command builder's add operation:
module Program
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 as many parameters as you need
let words = Parameters.words.GetValue ctx
let separator = Parameters.separator.GetValue ctx
// Do work
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
}
Caveats
This manual binding mechanism is less type safe because the compiler has no way to determine if you have manually added all input parameters to the command; so if you forget to add one that is used, it will result in a runtime error.
(For this reason, you should try to limit your commands to 8 parameters when possible, by factoring your app into separate commands.)
beta4 changes
v0.13.0-beta4 adapts to the System.CommandLine beta4 changes.
The API stays mostly the same with the following changes:
-
💥 Removed
Input.InjectedDependency<'T>()(this allowed you to pass in S.CL injected system dependencies likeInvocationContext,CancellationToken,IConsole, etc. -
⭐ Added
Input.Context()for passingInvocationContextto handler function.
This replacesInput.InjectedDependency<'T>()becauseInvocationContextcan be used to get aCancellationToken,IConsole, etc. -
Handler functions now support 8 input bindings instead of 16 (following changes to S.CL).
(If you need more than 8 input bindings, you can pass theInvocationContextto your handler function using the newInput.Context()method which will allow you to manually get as many parsed values as you want.)
The readme has been updated with an example of passing the InvocationContext:
https://github.com/JordanMarr/FSharp.SystemCommandLine#passing-the-invocationcontext