• Stars
    star
    165
  • Rank 228,906 (Top 5 %)
  • Language
    F#
  • License
    MIT License
  • Created about 2 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Execute CLI commands from your F# code in F# style!

Fli

build Nuget

Execute CLI commands from your F# code in F# style!

Fli is part of the F# Advent Calendar 2022: A little story about Fli

Features

  • Starting processes easily
  • Execute CLI commands in your favourite shell
  • F# computation expression syntax
  • Wrap authenticated CLI tools
  • No external dependencies

Install

Get it from Nuget: dotnet add package Fli

Usage

open Fli and start

For example:

cli {
    Shell CMD
    Command "echo Hello World!"
}
|> Command.execute

that starts CMD.exe as Shell and echo Hello World! is the command to execute.

Run a file with PowerShell from a specific directory:

cli {
    Shell PWSH
    Command "test.bat"
    WorkingDirectory (Environment.GetFolderPath Environment.SpecialFolder.UserProfile)
}
|> Command.execute

Executing programs with arguments:

cli {
    Exec "path/to/executable"
    Arguments "--info"
}
|> Command.execute

an example with git:

cli {
    Exec "git"
    Arguments ["commit"; "-m"; "Fixing issue #1337."]
}
|> Command.execute

Add a verb to your executing program:

cli {
    Exec "adobe.exe"
    Arguments (Path.Combine ((Environment.GetFolderPath Environment.SpecialFolder.UserProfile), "test.pdf"))
    Verb "open"
}
|> Command.execute

Write output to a specific file:

cli {
    Exec "dotnet"
    Arguments "--list-sdks"
    Output @"absolute\path\to\dotnet-sdks.txt"
}
|> Command.execute

Write output to a function (logging, printing, etc.):

let log (output: string) = Debug.Log($"CLI log: {output}")

cli {
    Exec "dotnet"
    Arguments "--list-sdks"
    Output log
}
|> Command.execute

Add environment variables for the executing program:

cli {
    Exec "git"
    EnvironmentVariables [("GIT_AUTHOR_NAME", "Jon Doe"); ("GIT_AUTHOR_EMAIL", "[email protected]")]
}
|> Command.execute

Add credentials to program:

cli {
    Exec "program"
    Credentials ("domain", "bobk", "password123")
}
|> Command.execute

Hint: Running a process as a different user is supported on all platforms. Other options (Domain, Password) are only available on Windows. As an alternative for not Windows based systems there is:

cli {
    Exec "path/to/program"
    Username "admin"
}
|> Command.execute

Command.execute

Command.execute returns record: type Output = { Id: int; Text: string option; ExitCode: int; Error: string option } which has getter methods to get only one value:

toId: Output -> int
toText: Output -> string
toExitCode: Output -> int
toError: Output -> string

example:

cli {
    Shell CMD
    Command "echo Hello World!"
}
|> Command.execute // { Id = 123; Text = Some "Hello World!"; ExitCode = 0; Error = None }
|> Output.toText // "Hello World!"

// same with Output.toId:
cli { ... }
|> Command.execute // { Id = 123; Text = Some "Hello World!"; ExitCode = 0; Error = None }
|> Output.toId // 123

// same with Output.toExitCode:
cli { ... }
|> Command.execute // { Id = 123; Text = Some "Hello World!"; ExitCode = 0; Error = None }
|> Output.toExitCode // 0

// in case of an error:
cli { ... }
|> Command.execute // { Id = 123; Text = None; ExitCode = 1; Error = Some "This is an error!" }
|> Output.toError // "This is an error!"

Printing Output fields

There are printing methods in Output too:

printId: Output -> unit
printText: Output -> unit
printExitCode: Output -> unit
printError: Output -> unit

Instead of writing:

cli { ... }
|> Command.execute
|> Output.toText
|> printfn "%s"

For a little shorter code you can use:

cli { ... }
|> Command.execute
|> Output.printText

Command.toString

Command.toString concatenates only the the executing shell/program + the given commands/arguments:

cli {
    Shell PS
    Command "Write-Host Hello World!"
}
|> Command.toString // "powershell.exe -Command Write-Host Hello World!"

and:

cli {
    Exec "cmd.exe"
    Arguments [ "/C"; "echo"; "Hello World!" ]
}
|> Command.toString // "cmd.exe /C echo Hello World!"

Builder operations:

ShellContext operations (cli { Shell ... }):

Operation Type
Shell Fli.Shells
Command string
Input string
Output Outputs (see below)
WorkingDirectory string
EnvironmentVariable string * string
EnvironmentVariables (string * string) list
Encoding System.Text.Encoding
CancelAfter int

ExecContext operations (cli { Exec ... }):

Operation Type
Exec string
Arguments string / string seq / string list / string array
Input string
Output Outputs (see below)
Verb string
Username string
Credentials string * string * string
WorkingDirectory string
EnvironmentVariable string * string
EnvironmentVariables (string * string) list
Encoding System.Text.Encoding
CancelAfter int

Currently provided Fli.Shells:

  • CMD runs cmd.exe /c ... or cmd.exe /k ... (depends if Input is provided or not)
  • PS runs powershell.exe -Command ...
  • PWSH runs pwsh.exe -Command ...
  • WSL runs wsl.exe -- ...
  • BASH runs bash -c ...
  • CUSTOM (shell: string * flag: string) runs the specified shell with the specified starting argument (flag)

Provided Fli.Outputs:

  • File of string a string with an absolute path of the output file.
  • StringBuilder of StringBuilder a StringBuilder which will be filled with the output text.
  • Custom of Func<string, unit> a custom function (string -> unit) that will be called with the output string (logging, printing etc.).

Do you miss something?

Open an issue or start a discussion.

Inspiration

Use CE's for CLI commands came in mind while using FsHttp.