{"id":16286774,"url":"https://github.com/jordanmarr/fsharp.systemcommandline","last_synced_at":"2026-02-08T23:01:11.001Z","repository":{"id":49474484,"uuid":"469439095","full_name":"JordanMarr/FSharp.SystemCommandLine","owner":"JordanMarr","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-05T19:07:12.000Z","size":294,"stargazers_count":136,"open_issues_count":1,"forks_count":6,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-02-06T04:56:29.089Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JordanMarr.png","metadata":{"files":{"readme":"README-beta4.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"JordanMarr"}},"created_at":"2022-03-13T17:11:05.000Z","updated_at":"2026-02-05T19:07:16.000Z","dependencies_parsed_at":"2023-02-09T21:30:42.180Z","dependency_job_id":"ff546c23-e113-4ff3-8108-c95da865df1f","html_url":"https://github.com/JordanMarr/FSharp.SystemCommandLine","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/JordanMarr/FSharp.SystemCommandLine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanMarr%2FFSharp.SystemCommandLine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanMarr%2FFSharp.SystemCommandLine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanMarr%2FFSharp.SystemCommandLine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanMarr%2FFSharp.SystemCommandLine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JordanMarr","download_url":"https://codeload.github.com/JordanMarr/FSharp.SystemCommandLine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanMarr%2FFSharp.SystemCommandLine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29248487,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-08T22:49:53.206Z","status":"ssl_error","status_checked_at":"2026-02-08T22:49:51.384Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-10T19:43:44.455Z","updated_at":"2026-02-08T23:01:10.979Z","avatar_url":"https://github.com/JordanMarr.png","language":"F#","readme":"### FSharp.SystemCommandLine \n[![NuGet version (FSharp.SystemCommandLine)](https://img.shields.io/nuget/v/FSharp.SystemCommandLine.svg?style=flat-square)](https://www.nuget.org/packages/FSharp.SystemCommandLine/)\n\nThe purpose of this library is to provide quality of life improvements when using the [`System.CommandLine`](https://github.com/dotnet/command-line-api) API in F#.\n\n## Features\n\n* Mismatches between `inputs` and `setHandler` function parameters are caught at compile time\n* `Input.Option` helper method avoids the need to use the `System.CommandLine.Option` type directly (which conflicts with the F# `Option` type) \n* `Input.OptionMaybe` and `Input.ArgumentMaybe` methods allow you to use F# `option` types in your handler function.\n* `Input.Context` method allows you to pass the `System.CommandLine.Invocation.InvocationContext` to your handler function.\n\n## Examples\n\n### Simple App\n\n```F#\nopen FSharp.SystemCommandLine\nopen System.IO\n\nlet unzip (zipFile: FileInfo, outputDirMaybe: DirectoryInfo option) = \n    // Default to the zip file dir if None\n    let outputDir = defaultArg outputDirMaybe zipFile.Directory\n\n    if zipFile.Exists\n    then printfn $\"Unzipping {zipFile.Name} to {outputDir.FullName}\"\n    else printfn $\"File does not exist: {zipFile.FullName}\"\n    \n[\u003cEntryPoint\u003e]\nlet main argv = \n    let zipFile = Input.Argument\u003cFileInfo\u003e(\"The file to unzip\")    \n    let outputDirMaybe = Input.OptionMaybe\u003cDirectoryInfo\u003e([\"--output\"; \"-o\"], \"The output directory\")\n\n    rootCommand argv {\n        description \"Unzips a .zip file\"\n        inputs (zipFile, outputDirMaybe) // must be set before setHandler\n        setHandler unzip\n    }\n```\n\n💥WARNING: You must declare `inputs` before `setHandler` or else the type checking will not work properly and you will get a build error!💥\n\n```batch\n\u003e unzip.exe \"c:\\test\\stuff.zip\"\n    Result: Unzipping stuff.zip to c:\\test\n    \n\u003e unzip.exe \"c:\\test\\stuff.zip\" -o \"c:\\test\\output\"\n    Result: Unzipping stuff.zip to c:\\test\\output\n```\n\n\n_Notice that mismatches between the `setHandler` and the `inputs` are caught as a compile time error:_\n![fs scl demo](https://user-images.githubusercontent.com/1030435/164288239-e0ff595d-cdb2-47f8-9381-50c89aedd481.gif)\n\n\n### Simple App that Returns a Status Code\n\nYou may optionally return a status code from your handler function.\n\n```F#\nopen FSharp.SystemCommandLine\nopen System.IO\n\nlet unzip (zipFile: FileInfo, outputDirMaybe: DirectoryInfo option) = \n    // Default to the zip file dir if None\n    let outputDir = defaultArg outputDirMaybe zipFile.Directory\n\n    if zipFile.Exists then\n        printfn $\"Unzipping {zipFile.Name} to {outputDir.FullName}\"\n        0 // Program successfully completed.\n    else \n        printfn $\"File does not exist: {zipFile.FullName}\"\n        2 // The system cannot find the file specified.\n    \n[\u003cEntryPoint\u003e]\nlet main argv = \n    rootCommand argv {\n        description \"Unzips a .zip file\"\n        inputs (\n            Input.Argument\u003cFileInfo\u003e(\"The file to unzip\"),\n            Input.OptionMaybe\u003cDirectoryInfo\u003e([\"--output\"; \"-o\"], \"The output directory\")\n        )\n        setHandler unzip\n    }\n```\n\n\n### App with SubCommands\n\n```F#\nopen System.IO\nopen FSharp.SystemCommandLine\n\n// Ex: fsm.exe list \"c:\\temp\"\nlet listCmd = \n    let handler (dir: DirectoryInfo) = \n        if dir.Exists \n        then dir.EnumerateFiles() |\u003e Seq.iter (fun f -\u003e printfn \"%s\" f.Name)\n        else printfn $\"{dir.FullName} does not exist.\"\n        \n    let dir = Input.Argument\u003cDirectoryInfo\u003e(\"dir\", \"The directory to list\")\n\n    command \"list\" {\n        description \"lists contents of a directory\"\n        inputs dir\n        setHandler handler\n    }\n\n// Ex: fsm.exe delete \"c:\\temp\" --recursive\nlet deleteCmd = \n    let handler (dir: DirectoryInfo, recursive: bool) = \n        if dir.Exists then \n            if recursive\n            then printfn $\"Recursively deleting {dir.FullName}\"\n            else printfn $\"Deleting {dir.FullName}\"\n        else \n            printfn $\"{dir.FullName} does not exist.\"\n\n    let dir = Input.Argument\u003cDirectoryInfo\u003e(\"dir\", \"The directory to delete\")\n    let recursive = Input.Option\u003cbool\u003e(\"--recursive\", false)\n\n    command \"delete\" {\n        description \"deletes a directory\"\n        inputs (dir, recursive)\n        setHandler handler\n    }\n        \n\n[\u003cEntryPoint\u003e]\nlet main argv = \n    rootCommand argv {\n        description \"File System Manager\"\n        setHandler id\n        // if using async task sub commands, setHandler to `Task.FromResult`\n        // setHandler Task.FromResult         \n        addCommand listCmd\n        addCommand deleteCmd\n    }\n```\n\n```batch\n\u003e fsm.exe list \"c:\\_github\\FSharp.SystemCommandLine\\src\\FSharp.SystemCommandLine\"\n    CommandBuilders.fs\n    FSharp.SystemCommandLine.fsproj\n    pack.cmd\n    Types.fs\n\n\u003e fsm.exe delete \"c:\\_github\\FSharp.SystemCommandLine\\src\\FSharp.SystemCommandLine\"\n    Deleting c:\\_github\\FSharp.SystemCommandLine\\src\\FSharp.SystemCommandLine\n\n\u003e fsm.exe delete \"c:\\_github\\FSharp.SystemCommandLine\\src\\FSharp.SystemCommandLine\" --recursive\n    Recursively deleting c:\\_github\\FSharp.SystemCommandLine\\src\\FSharp.SystemCommandLine\n```\n\n### Passing the InvocationContext\n\nYou may need to pass the `InvocationContext` to your handler function for the following reasons:\n* You need to get `CancellationToken`, `IConsole` or `ParseResult`\n* If you have more than 8 inputs, you will need to manually get the parsed values via the `InvocationContext`.\n\nYou can pass the `InvocationContext` via the `Input.Context()` method.\n\n```F#\nmodule Program\n\nopen FSharp.SystemCommandLine\nopen System.Threading\nopen System.Threading.Tasks\nopen System.CommandLine.Invocation\n\nlet app (ctx: InvocationContext, words: string array, separator: string) =\n    task {\n        let cancel = ctx.GetCancellationToken()\n        for i in [1..20] do\n            if cancel.IsCancellationRequested then \n                printfn \"Cancellation Requested\"\n                raise (new System.OperationCanceledException())\n            else \n                printfn $\"{i}\"\n                do! Task.Delay(1000)\n\n        System.String.Join(separator, words)\n        |\u003e printfn \"Result: %s\"\n    }\n    \n[\u003cEntryPoint\u003e]\nlet main argv = \n    let ctx = Input.Context()\n    let words = Input.Option\u003cstring array\u003e([\"--word\"; \"-w\"], [||], \"A list of words to be appended\")\n    let separator = Input.Option\u003cstring\u003e([\"--separator\"; \"-s\"], \", \", \"A character that will separate the joined words.\")\n\n    rootCommand argv {\n        description \"Appends words together\"\n        inputs (ctx, words, separator)\n        setHandler app\n    }\n    |\u003e Async.AwaitTask\n    |\u003e Async.RunSynchronously\n```\n\n### Example with more than 8 inputs\nCurrently, a command handler function is limited to accept a tuple with no more than eight inputs.\nIf you need more, you can pass in the InvocationContext to your handler and manually get as many input values as you like (assuming they have been registered via the rootCommand or command builder's `add` operation:\n\n```F#\nmodule Program\n\nopen FSharp.SystemCommandLine\n\nmodule Parameters = \n    let words = Input.Option\u003cstring[]\u003e([\"--word\"; \"-w\"], Array.empty, \"A list of words to be appended\")\n    let separator = Input.OptionMaybe\u003cstring\u003e([\"--separator\"; \"-s\"], \"A character that will separate the joined words.\")\n\nlet app (ctx: System.CommandLine.Invocation.InvocationContext) =\n    // Manually parse as many parameters as you need\n    let words = Parameters.words.GetValue ctx\n    let separator = Parameters.separator.GetValue ctx\n\n    // Do work\n    let separator = separator |\u003e Option.defaultValue \", \"\n    System.String.Join(separator, words) |\u003e printfn \"Result: %s\"\n    0\n    \n[\u003cEntryPoint\u003e]\nlet main argv = \n    rootCommand argv {\n        description \"Appends words together\"\n        inputs (Input.Context())\n        setHandler app\n        addInputs [ Parameters.words; Parameters.separator ]\n    }\n```\n\n### Example using Microsoft.Extensions.Hosting\n\nThis example requires the following nuget packages:\n\n- Microsoft.Extensions.Configuration\n- Microsoft.Extensions.Hosting\n- Serilog.Extensions.Hosting\n- Serilog.Sinks.Console\n- Serilog.Sinks.File\n\n```F#\nopen System\nopen System.IO\nopen FSharp.SystemCommandLine\nopen Microsoft.Extensions.DependencyInjection\nopen Microsoft.Extensions.Configuration\nopen Microsoft.Extensions.Hosting\nopen Microsoft.Extensions.Logging\nopen Serilog\n\nlet buildHost (argv: string[]) =\n    Host.CreateDefaultBuilder(argv)\n        .ConfigureHostConfiguration(fun configHost -\u003e\n            configHost.SetBasePath(Directory.GetCurrentDirectory()) |\u003e ignore\n            configHost.AddJsonFile(\"appsettings.json\", optional = false) |\u003e ignore\n        )\n        .UseSerilog(fun hostingContext configureLogger -\u003e \n            configureLogger\n                .MinimumLevel.Information()\n                .Enrich.FromLogContext()\n                .WriteTo.Console()\n                .WriteTo.File(\n                    path = \"logs/log.txt\", \n                    rollingInterval = RollingInterval.Year\n                )\n                |\u003e ignore\n        )\n        .Build()        \n\nlet exportHandler (logger: ILogger) (connStr: string, outputDir: DirectoryInfo, startDate: DateTime, endDate: DateTime) =\n    task {\n        logger.Information($\"Querying from {StartDate} to {EndDate}\", startDate, endDate)            \n        // Do export stuff...\n    }\n\n[\u003cEntryPoint\u003e]\nlet main argv =\n    let host = buildHost argv\n    let logger = host.Services.GetService\u003cILogger\u003e()\n    let cfg = host.Services.GetService\u003cIConfiguration\u003e()\n\n    let connStr = Input.Option\u003cstring\u003e(\n        aliases = [\"-c\"; \"--connection-string\"], \n        defaultValue = cfg[\"ConnectionStrings:DB\"],\n        description = \"Database connection string\")\n\n    let outputDir = Input.Option\u003cDirectoryInfo\u003e(\n        aliases = [\"-o\";\"--output-directory\"], \n        defaultValue = DirectoryInfo(cfg[\"DefaultOutputDirectory\"]), \n        description = \"Output directory folder.\")\n\n    let startDate = Input.Option\u003cDateTime\u003e(\n        name = \"--start-date\", \n        defaultValue = DateTime.Today.AddDays(-7), \n        description = \"Start date (defaults to 1 week ago from today)\")\n        \n    let endDate = Input.Option\u003cDateTime\u003e(\n        name = \"--end-date\", \n        defaultValue = DateTime.Today, \n        description = \"End date (defaults to today)\")\n\n    rootCommand argv {\n        description \"Data Export\"\n        inputs (connStr, outputDir, startDate, endDate)\n        setHandler (exportHandler logger)\n    }\n    |\u003e Async.AwaitTask\n    |\u003e Async.RunSynchronously\n```\n\n### Global Options\n\nThis example shows to create global options to all child commands. \n\n```F#\nmodule ProgramNestedSubCommands\n\nopen System.IO\nopen FSharp.SystemCommandLine\nopen System.CommandLine.Invocation\n\nmodule Global = \n    let enableLogging = Input.Option\u003cbool\u003e(\"--enable-logging\", false)\n    let logFile = Input.Option\u003cFileInfo\u003e(\"--log-file\", FileInfo @\"c:\\temp\\default.log\")\n\n    type Options = { EnableLogging: bool; LogFile: FileInfo }\n\n    let options: HandlerInput seq = [ enableLogging; logFile ] \n\n    let bind (ctx: InvocationContext) = \n        { EnableLogging = enableLogging.GetValue ctx\n          LogFile = logFile.GetValue ctx }\n\nlet listCmd =\n    let handler (ctx: InvocationContext, dir: DirectoryInfo) =\n        let options = Global.bind ctx\n        if options.EnableLogging then \n            printfn $\"Logging enabled to {options.LogFile.FullName}\"\n\n        if dir.Exists then\n            dir.EnumerateFiles()\n            |\u003e Seq.iter (fun f -\u003e printfn \"%s\" f.FullName)\n        else\n            printfn $\"{dir.FullName} does not exist.\"\n\n    let dir = Input.Argument(\"directory\", DirectoryInfo(@\"c:\\default\"))\n\n    command \"list\" {\n        description \"lists contents of a directory\"\n        inputs (Input.Context(), dir)\n        setHandler handler\n        addAlias \"ls\"\n    }\n\nlet deleteCmd =\n    let handler (ctx: InvocationContext, dir: DirectoryInfo, recursive: bool) =\n        let options = Global.bind ctx\n        if options.EnableLogging then \n            printfn $\"Logging enabled to {options.LogFile.FullName}\"\n        \n        if dir.Exists then\n            if recursive then\n                printfn $\"Recursively deleting {dir.FullName}\"\n            else\n                printfn $\"Deleting {dir.FullName}\"\n        else\n            printfn $\"{dir.FullName} does not exist.\"\n\n    let dir = Input.Argument(\"directory\", DirectoryInfo(@\"c:\\default\"))\n    let recursive = Input.Option(\"--recursive\", false)\n\n    command \"delete\" {\n        description \"deletes a directory\"\n        inputs (Input.Context(), dir, recursive)\n        setHandler handler\n        addAlias \"del\"\n    }\n\nlet ioCmd = \n    command \"io\" {\n        description \"Contains IO related subcommands.\"\n        setHandler id\n        addCommands [ deleteCmd; listCmd ]\n    }\n\n[\u003cEntryPoint\u003e]\nlet main argv =\n    let ctx = Input.Context()\n    \n    let parser = \n        rootCommandParser {\n            description \"Sample app for System.CommandLine\"\n            setHandler id\n            addGlobalOptions Global.options\n            addCommand ioCmd\n        }\n\n    let parseResult = parser.Parse(argv)\n\n    // Get global option value from the parseResult\n    let loggingEnabled = Global.enableLogging.GetValue parseResult\n    printfn $\"ROOT: Logging enabled: {loggingEnabled}\"\n\n    parseResult.Invoke()\n```\n\n### Database Migrations Example\n\nThis real-life example for running database migrations demonstrates the following features:\n* Uses Microsoft.Extensions.Hosting.\n* Uses async/task commands.\n* Passes the `ILogger` dependency to the commands.\n* Shows help if no command is passed.\n\n```F#\nmodule Program\n\nopen EvolveDb\nopen System.Data.SqlClient\nopen System.IO\nopen Microsoft.Extensions.Hosting\nopen Microsoft.Extensions.Configuration\nopen Microsoft.Extensions.DependencyInjection\nopen Serilog\nopen EvolveDb.Configuration\nopen FSharp.SystemCommandLine\nopen System.CommandLine.Invocation\nopen System.CommandLine.Help\n\nlet buildHost (argv: string[]) =\n    Host.CreateDefaultBuilder(argv)\n        .ConfigureHostConfiguration(fun configHost -\u003e\n            configHost.SetBasePath(Directory.GetCurrentDirectory()) |\u003e ignore\n            configHost.AddJsonFile(\"appsettings.json\", optional = false) |\u003e ignore\n        )\n        .UseSerilog(fun hostingContext configureLogger -\u003e\n            configureLogger\n                .MinimumLevel.Information()\n                .Enrich.FromLogContext()\n                .WriteTo.Console()\n                .WriteTo.File(\n                    path = \"logs/log.txt\", \n                    rollingInterval = RollingInterval.Year\n                )\n                |\u003e ignore\n        )\n        .Build()\n\nlet repairCmd (logger: ILogger) = \n    let handler (env: string) =\n        task {\n            logger.Information($\"Environment: {env}\")\n            logger.Information(\"Starting EvolveDb Repair (correcting checksums).\")\n            let! connStr = KeyVault.getConnectionString env\n            use conn = new SqlConnection(connStr)\n            let evolve = Evolve(conn, fun msg -\u003e printfn \"%s\" msg) \n            evolve.TransactionMode \u003c- TransactionKind.CommitAll\n            evolve.Locations \u003c- [| \"Scripts\" |]\n            evolve.IsEraseDisabled \u003c- true\n            evolve.MetadataTableName \u003c- \"_EvolveChangelog\"\n            evolve.Repair()\n        }\n\n    command \"repair\" {\n        description \"Corrects checksums in the database.\"\n        inputs (Input.Argument\u003cstring\u003e(\"env\", \"The keyvault environment: [dev, beta, prod].\"))\n        setHandler handler\n    }\n\nlet migrateCmd (logger: ILogger) =\n    let handler (env: string) =\n        task {\n            logger.Information($\"Environment: {env}\")\n            logger.Information(\"Starting EvolveDb Migrate.\")\n            let! connStr = KeyVault.getConnectionString env\n            use conn = new SqlConnection(connStr)\n            let evolve = Evolve(conn, fun msg -\u003e printfn \"%s\" msg) \n            evolve.TransactionMode \u003c- TransactionKind.CommitAll\n            evolve.Locations \u003c- [| \"Scripts\" |]\n            evolve.IsEraseDisabled \u003c- true\n            evolve.MetadataTableName \u003c- \"_EvolveChangelog\"\n            evolve.Migrate()\n        }\n\n    command \"migrate\" {\n        description \"Migrates the database.\"\n        inputs (Input.Argument\u003cstring\u003e(\"env\", \"The keyvault environment: [dev, beta, prod].\"))\n        setHandler handler\n    }\n\nlet showHelp (ctx: InvocationContext) =\n    let hc = HelpContext(ctx.HelpBuilder, ctx.Parser.Configuration.RootCommand, System.Console.Out)\n    ctx.HelpBuilder.Write(hc)\n\n[\u003cEntryPoint\u003e]\nlet main argv =\n    let host = buildHost argv\n    let logger = host.Services.GetService\u003cILogger\u003e()\n\n    rootCommand argv {\n        description \"Database Migrations\"\n        inputs (Input.Context())\n        setHandler (showHelp)\n        addCommand (repairCmd logger)\n        addCommand (migrateCmd logger)\n    }\n\n```\n\n### Creating a Root Command Parser\n\nIf you want to manually invoke your root command, use the `rootCommandParser` CE (because the `rootCommand` CE is auto-executing).\n\n```F#\nopen FSharp.SystemCommandLine\nopen System.CommandLine.Parsing\n\nlet app (words: string array, separator: string option) =\n    let separator = defaultArg separator \", \"\n    System.String.Join(separator, words) |\u003e printfn \"Result: %s\"\n    0\n    \n[\u003cEntryPoint\u003e]\nlet main argv = \n    let words = Input.Option([\"--word\"; \"-w\"], Array.empty, \"A list of words to be appended\")\n    let separator = Input.OptionMaybe([\"--separator\"; \"-s\"], \"A character that will separate the joined words.\")\n\n    let parser = \n        rootCommandParser {\n            description \"Appends words together\"\n            inputs (words, separator)\n            setHandler app\n        }\n\n    parser.Parse(argv).Invoke()\n```\n\n### Showing Help as the Default\nA common design is to show help information if no commands have been passed:\n\n```F#\nopen System.CommandLine.Invocation\nopen System.CommandLine.Help\nopen FSharp.SystemCommandLine\n\nlet helloCmd = \n    let handler name = printfn $\"Hello, {name}.\"\n    let name = Input.Argument(\"Name\")\n    command \"hello\" {\n        description \"Says hello.\"\n        inputs name\n        setHandler handler\n    }\n\n[\u003cEntryPoint\u003e]\nlet main argv = \n    let showHelp (ctx: InvocationContext) =\n        let hc = HelpContext(ctx.HelpBuilder, ctx.Parser.Configuration.RootCommand, System.Console.Out)\n        ctx.HelpBuilder.Write(hc)\n        \n    let ctx = Input.Context()    \n    rootCommand argv {\n        description \"Says hello or shows help by default.\"\n        inputs ctx\n        setHandler showHelp\n        addCommand helloCmd\n    }\n```\n\n## Customizing the Default Pipeline\n\nSystem.CommandLine has a `CommandLineBuilder` that allows the user to customize various behaviors.\n\nFSharp.SystemCommandLine is configured to use the built-in defaults (via `CommandLineBuilder().UseDefaults()`), but you can easily override them via the `usePipeline` custom operation which gives you access to the `CommandLineBuilder`. \n\nFor example, the default behavior intercepts input strings that start with a \"@\" character via the \"TryReplaceToken\" feature. This will cause an issue if you need to accept input that starts with \"@\". Fortunately, you can disable this via `usePipeline`:\n\n```F#\nmodule TokenReplacerExample\n\nopen FSharp.SystemCommandLine\nopen System.CommandLine.Builder // Necessary when overriding the builder via usePipeline\n\nlet app (package: string) =\n    if package.StartsWith(\"@\") then\n        printfn $\"{package}\"\n        0\n    else\n        eprintfn \"The package name does not start with a leading @\"\n        1\n\n[\u003cEntryPoint\u003e]\nlet main argv =\n\n    // The package option needs to accept strings that start with \"@\" symbol.\n    // For example, \"--package @shoelace-style/shoelace\".\n    // To accomplish this, we will need to modify the default pipeline settings below.\n    let package = Input.Option([ \"--package\"; \"-p\" ], \"A package name that may have a leading '@' character.\")\n\n    rootCommand argv {\n        description \"Can be called with a leading '@' package\"\n\n        usePipeline (fun builder -\u003e \n            // Override default token replacer to ignore `@` processing\n            builder.UseTokenReplacer(fun _ _ _ -\u003e false)\n        )\n        \n        inputs package\n        setHandler app\n    }\n\n```\n\nAs you can see, there are a lot of options that can be configured here (note that you need to `open System.CommandLine.Builder`):\n\n![image](https://user-images.githubusercontent.com/1030435/199282781-1800b79c-7638-4242-8ca0-777d7237e20a.png)\n\n\n## Setting Input Properties Manually\n\nSometimes it may be necessary to access the underlying `System.CommandLine` base input properties manually to take advantage of features like custom validation. You can do this by using overloads of `Input.Argument`, `Input.Option` and `Input.OptionMaybe` that provide a `configure` argument. \nThe `configure` argument is a function that allows you to modify the underlying `System.CommandLine` `Option\u003c'T\u003e` or `Argument\u003c'T\u003e`.\n\n```F#\nmodule Program\n\nopen FSharp.SystemCommandLine\n\nlet app (name: string) =\n    printfn $\"Hello, {name}\"\n    \n[\u003cEntryPoint\u003e]\nlet main argv = \n\n    let name = \n        Input.Option\u003cstring\u003e(\"--name\", fun o -\u003e \n            o.Description \u003c- \"User name\"\n            o.SetDefaultValue \"-- NotSet --\"\n            o.AddValidator(fun result -\u003e \n                let nameValue = result.GetValueForOption(o)\n                if System.String.IsNullOrWhiteSpace(nameValue)\n                then result.ErrorMessage \u003c- \"Name cannot be an empty string.\"\n                elif nameValue.Length \u003e 10\n                then result.ErrorMessage \u003c- \"Name cannot exceed more than 10 characters.\"\n            )\n        )\n    \n    rootCommand argv {\n        description \"Provides a friendly greeting.\"\n        inputs name\n        setHandler app\n    }\n```\n\nAlternatively, you can manually create a `System.CommandLine` `Option\u003c'T\u003e` or `Argument\u003c'T\u003e` and then convert it for use with a `CommandBuilder` via the `Input.OfOption` or `Input.OfArgument` helper methods.\n\nNote that you can also import the `FSharp.SystemCommandLine.Aliases` namespace to use the `Arg\u003c'T\u003e` and `Opt\u003c'T\u003e` aliases:\n\n```F# \n\nlet connectionString = \n    System.CommandLine.Option\u003cstring\u003e(\n        getDefaultValue = (fun () -\u003e \"conn string\"),\n        aliases = [| \"-cs\";\"--connectionString\" |],\n        description = \"An optional connection string to the server to import into\"\n    )\n    |\u003e Input.OfOption\n```\n","funding_links":["https://github.com/sponsors/JordanMarr"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjordanmarr%2Ffsharp.systemcommandline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjordanmarr%2Ffsharp.systemcommandline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjordanmarr%2Ffsharp.systemcommandline/lists"}