Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,36 @@ cli {
|> Command.execute
```

Write output to a stream:
```fsharp
// using a console stream
cli {
Exec "dotnet"
Arguments "--list-sdks"
Output (new StreamWriter(Console.OpenStandardOutput()))
}
|> Command.execute

// using a file stream
cli {
Exec "dotnet"
Arguments "--list-sdks"
Output (new StreamWriter(new FileStream("test.txt", FileMode.OpenOrCreate)))
}
|> Command.execute
```
Hint: Using `Output (new StreamWriter(...))` will redirect the output text to your desired target and not save it into `Output.Text` nor `Output.Error` but in order to fix that you can use `Output.from`:
```fsharp
let sb = StringBuilder()
cli {
Exec "dotnet"
Arguments "--list-sdks"
Output (new StringWriter(sb))
}
|> Command.execute
|> Output.from (sb.ToString())
```

Add environment variables for the executing program:
```fsharp
cli {
Expand Down Expand Up @@ -284,6 +314,7 @@ 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.).
- `Stream of TextWriter` a stream that will redirect the output text to the designated target (file, console etc.).

Provided `Fli.WindowStyle`:
- `Hidden` (default)
Expand Down
38 changes: 38 additions & 0 deletions src/Fli.Tests/ShellContext/ShellCommandExecuteWindowsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ open NUnit.Framework
open FsUnit
open Fli
open System
open System.IO
open System.Text
open System.Diagnostics

Expand Down Expand Up @@ -82,6 +83,43 @@ let ``Get output in StringBuilder`` () =

sb.ToString() |> should equal "Test\r\n"

[<Test>]
[<Platform("Win")>]
let ``Get new stream in StringBuilder`` () =
let sb = StringBuilder()

cli {
Shell CMD
Command "echo Test"
Output (new StringWriter(sb))
}
|> Command.execute
|> ignore

sb.ToString() |> should contain "Test\r\n"

[<Test>]
[<Platform("Win")>]
let ``Use from to recreate a valid Output when using stream`` () =
let sb = StringBuilder()

let output =
cli {
Shell CMD
Command "echo Test"
Output (new StringWriter(sb))
}
|> Command.execute

output
|> Output.toText
|> should equal ""

output
|> Output.from (sb.ToString())
|> Output.toText
|> should contain "Test"

[<Test>]
[<Platform("Win")>]
let ``CMD returning non zero process id`` () =
Expand Down
11 changes: 11 additions & 0 deletions src/Fli/CE.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module CE =

open System.Text
open System.IO
open Domain

type ICommandContext<'a> with
Expand Down Expand Up @@ -55,6 +56,11 @@ module CE =
member _.Output(context: ICommandContext<ShellContext>, func: string -> unit) =
Cli.output (Custom func) context.Context

/// Extra `Output` that is being executed immediately after getting output from execution.
[<CustomOperation("Output")>]
member _.Output(context: ICommandContext<ShellContext>, stream: TextWriter) =
Cli.output (Stream stream) context.Context

/// Current executing `working directory`.
[<CustomOperation("WorkingDirectory")>]
member _.WorkingDirectory(context: ICommandContext<ShellContext>, workingDirectory) =
Expand Down Expand Up @@ -127,6 +133,11 @@ module CE =
member _.Output(context: ICommandContext<ExecContext>, func: string -> unit) =
Program.output (Custom func) context.Context

/// Extra `Output` that is being executed immediately after getting output from execution.
[<CustomOperation("Output")>]
member _.Output(context: ICommandContext<ExecContext>, stream: TextWriter) =
Program.output (Stream stream) context.Context

/// Current executing `working directory`.
[<CustomOperation("WorkingDirectory")>]
member _.WorkingDirectory(context: ICommandContext<ExecContext>, workingDirectory) =
Expand Down
31 changes: 25 additions & 6 deletions src/Fli/Command.fs
Original file line number Diff line number Diff line change
Expand Up @@ -109,25 +109,31 @@ module Command =
|> Async.AwaitTask
#endif

let private startProcess (inputFunc: Process -> unit) (outputFunc: string -> unit) psi =
let private startProcess (inputFunc: Process -> unit) (outputFunc: string -> unit) (isStreaming: bool) psi =
let proc = Process.Start(startInfo = psi)
proc |> inputFunc

let text =
if psi.UseShellExecute |> not then
let mutable text =
if psi.UseShellExecute |> not && isStreaming |> not then
proc.StandardOutput.ReadToEnd()
else
proc.BeginOutputReadLine()
""

let error =
if psi.UseShellExecute |> not then
let mutable error =
if psi.UseShellExecute |> not && isStreaming |> not then
proc.StandardError.ReadToEnd()
else
proc.BeginErrorReadLine()
""

proc.OutputDataReceived.Add(fun args -> outputFunc args.Data)
proc.ErrorDataReceived.Add(fun args -> outputFunc args.Data)

proc.WaitForExit()

text |> outputFunc
if isStreaming |> not then
text |> outputFunc

{ Id = proc.Id
Text = text |> trim |> toOption
Expand Down Expand Up @@ -199,6 +205,7 @@ module Command =
| Outputs.File(file) -> File.WriteAllText(file, output)
| Outputs.StringBuilder(stringBuilder) -> output |> stringBuilder.Append |> ignore
| Outputs.Custom(func) -> func.Invoke(output)
| Outputs.Stream(stream) -> stream.WriteLine(output) ; stream.Flush()
| None -> ()

let private setupCancellationToken (cancelAfter: int option) =
Expand Down Expand Up @@ -273,19 +280,31 @@ module Command =

/// Executes the given context as a new process.
static member execute(context: ShellContext) =
let isStreaming: bool =
match context.config.Output with
| Some s -> match s with Outputs.Stream(s) -> true | _ -> false
| _ -> false

context
|> Command.buildProcess
|> startProcess
(writeInput context.config.Input context.config.Encoding)
(writeOutput context.config.Output)
isStreaming

/// Executes the given context as a new process.
static member execute(context: ExecContext) =
let isStreaming: bool =
match context.config.Output with
| Some s -> match s with Outputs.Stream(s) -> true | _ -> false
| _ -> false

context
|> Command.buildProcess
|> startProcess
(writeInput context.config.Input context.config.Encoding)
(writeOutput context.config.Output)
isStreaming

#if NET
/// Executes the given context as a new process asynchronously.
Expand Down
2 changes: 2 additions & 0 deletions src/Fli/Domain.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module Domain =

open System
open System.IO
open System.Text

type ICommandContext<'a> =
Expand Down Expand Up @@ -34,6 +35,7 @@ module Domain =
| File of string
| StringBuilder of StringBuilder
| Custom of Func<string, unit>
| Stream of TextWriter

and WindowStyle =
| Hidden
Expand Down
14 changes: 14 additions & 0 deletions src/Fli/Output.fs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,17 @@ module Output =

/// Throws exception if exit code is not 0.
let throwIfErrored = throw (fun o -> o.ExitCode <> 0)

let from (str: string) (output: Output): Output =
if output.Text = None |> not || output.Error = None |> not then
output
elif output.ExitCode = 0 then
{ Id = output.Id
Text = Some(str)
ExitCode = output.ExitCode
Error = None }
else
{ Id = output.Id
Text = None
ExitCode = output.ExitCode
Error = Some(str) }
Loading