Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,24 @@ cli {
```
(Hint: if file extension is not assigned to any installed program, it will throw a `System.NullReferenceException`)

Write output to a specific file:
Write output or stream to a specific file:
```fsharp
cli {
Exec "dotnet"
Arguments "--list-sdks"
Output @"absolute\path\to\dotnet-sdks.txt"
}
|> Command.execute

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

Write output to a function (logging, printing, etc.):
Write output or stream to a function (logging, printing, etc.):
```fsharp
let log (output: string) = Debug.Log($"CLI log: {output}")

Expand All @@ -96,6 +103,13 @@ cli {
Output log
}
|> Command.execute

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

Add environment variables for the executing program:
Expand Down
15 changes: 15 additions & 0 deletions src/Fli.Tests/ShellContext/ShellCommandExecuteWindowsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ let ``Get output in StringBuilder`` () =

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

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

cli {
Shell CMD
Command "echo Test"
Stream sb
}
|> Command.execute
|> ignore

sb.ToString() |> should equal "Test"

[<Test>]
[<Platform("Win")>]
let ``CMD returning non zero process id`` () =
Expand Down
37 changes: 37 additions & 0 deletions src/Fli/CE.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,25 @@ 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.
Comment thread
CaptnCodr marked this conversation as resolved.
[<CustomOperation("Stream")>]
member _.Stream(context: ICommandContext<ShellContext>, output: Outputs) = Cli.stream output context.Context

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

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

/// Extra `Output` that is being executed immediately after getting output from execution.
[<CustomOperation("Stream")>]
member _.Strem(context: ICommandContext<ShellContext>, func: string -> unit) =
Comment thread
CaptnCodr marked this conversation as resolved.
Outdated
Cli.stream (Custom func) context.Context

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

[<CustomOperation("Stream")>]
member _.Stream(context: ICommandContext<ExecContext>, output: Outputs) = Program.stream output context.Context

/// Extra `Output` that is being executed immediately after getting output from execution.
Comment thread
CaptnCodr marked this conversation as resolved.
Outdated
[<CustomOperation("Stream")>]
member _.Stream(context: ICommandContext<ExecContext>, filePath: string) =
Program.stream (File filePath) context.Context

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

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

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

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

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

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

proc.OutputDataReceived.Add(fun args -> text <- text + args.Data ; streamFunc args.Data)
proc.ErrorDataReceived.Add(fun args -> error <- error + args.Data ; streamFunc args.Data)

proc.WaitForExit()

text |> outputFunc
Expand Down Expand Up @@ -201,6 +206,15 @@ module Command =
| Outputs.Custom(func) -> func.Invoke(output)
| None -> ()

let private streamOutput (outputType: Outputs option) (output: string): unit =
match outputType with
| Some(o) ->
match o with
| Outputs.File(file) -> File.AppendAllText(file, output)
| Outputs.StringBuilder(stringBuilder) -> output |> stringBuilder.Append |> ignore
| Outputs.Custom(func) -> func.Invoke(output)
| None -> ()

let private setupCancellationToken (cancelAfter: int option) =
let cts = new CancellationTokenSource()

Expand Down Expand Up @@ -278,6 +292,8 @@ module Command =
|> startProcess
(writeInput context.config.Input context.config.Encoding)
(writeOutput context.config.Output)
(streamOutput context.config.Stream)
(context.config.Stream.IsNone)

/// Executes the given context as a new process.
static member execute(context: ExecContext) =
Expand All @@ -286,6 +302,8 @@ module Command =
|> startProcess
(writeInput context.config.Input context.config.Encoding)
(writeOutput context.config.Output)
(streamOutput context.config.Stream)
(context.config.Stream.IsNone)

#if NET
/// Executes the given context as a new process asynchronously.
Expand Down
4 changes: 4 additions & 0 deletions src/Fli/Domain.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Domain =
Command: string option
Input: string option
Output: Outputs option
Stream: Outputs option
WorkingDirectory: string option
EnvironmentVariables: (string * string) list option
Encoding: Encoding option
Expand Down Expand Up @@ -46,6 +47,7 @@ module Domain =
Arguments: Arguments option
Input: string option
Output: Outputs option
Stream: Outputs option
WorkingDirectory: string option
Verb: string option
UserName: string option
Expand Down Expand Up @@ -96,6 +98,7 @@ module Domain =
Command = None
Input = None
Output = None
Stream = None
WorkingDirectory = None
EnvironmentVariables = None
Encoding = None
Expand All @@ -106,6 +109,7 @@ module Domain =
Arguments = None
Input = None
Output = None
Stream = None
WorkingDirectory = None
Verb = None
UserName = None
Expand Down
18 changes: 18 additions & 0 deletions src/Fli/Dsl.fs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ module Cli =
{ context with
config.Output = outputsOption }

let stream (output: Outputs) (context: ShellContext) =
let outputsOption =
match output with
| File path -> path |> toOptionWithDefault output
| _ -> Some output

{ context with
config.Stream = outputsOption }

let workingDirectory (workingDirectory: string) (context: ShellContext) =
{ context with
config.WorkingDirectory = Some workingDirectory }
Expand Down Expand Up @@ -76,6 +85,15 @@ module Program =
{ context with
config.Output = outputsOption }

let stream (output: Outputs) (context: ExecContext) =
let outputsOption =
match output with
| File path -> path |> toOptionWithDefault output
| _ -> Some output

{ context with
config.Stream = outputsOption }

let workingDirectory (workingDirectory: string) (context: ExecContext) =
{ context with
config.WorkingDirectory = Some workingDirectory }
Expand Down
Loading