await Cli. Wrap ( " docker" )
. WithArguments ( " run --detach -e POSTGRES_PASSWORD=password postgres" )
. ExecuteAsync ( ) ;
Show contents of command result
Show how to get process ID
var result = await Cli. Wrap ( " docker" )
. WithArguments ( " run --detach -e POSTGRES_PASSWORD=password postgres" )
. ExecuteBufferedAsync ( ) ;
Show contents of buffered command result
await Cli. Wrap ( " docker" )
. WithArguments ( " run --detach -e POSTGRES_PASSWORD=password postgresss" )
. ExecuteAsync ( ) ;
Show how ExecuteBufferedAsync()
affects the error message
Passing arguments in different ways
var password = " hello world" ;
var result = await Cli. Wrap ( " docker" )
. WithArguments ( $" run --detach -e POSTGRES_PASSWORD= { password} postgres " )
. ExecuteBufferedAsync ( ) ;
Show that the password is not escaped and the arguments end up malformed
Instead of password, it can be anything, e.g. file path
var password = " hello world" ;
var result = await Cli. Wrap ( " docker" )
. WithArguments ( new [ ]
{
" run" ,
" --detach" ,
" -e" , $" POSTGRES_PASSWORD= { password} " ,
" postgres"
} )
. ExecuteBufferedAsync ( ) ;
Show that arguments are escaped correctly
var password = " hello world" ;
var result = await Cli. Wrap ( " docker" )
. WithArguments ( args => args
. Add ( " run" )
. Add ( " --detach" )
. Add ( " -e" ) . Add ( $" POSTGRES_PASSWORD= { password} " )
. Add ( " postgres" )
)
. ExecuteBufferedAsync ( ) ;
Show usage with IFormattable
Custom arguments builder extensions
public static class CliExtensions
{
public static ArgumentsBuilder AddOption (
this ArgumentsBuilder args ,
string name ,
string ? value )
{
if ( string . IsNullOrWhiteSpace ( value) )
return args ;
return args. Add ( name) . Add ( value) ;
}
}
var network = " mynet" ;
var result = await Cli. Wrap ( " docker" )
. WithArguments ( args => args
. Add ( " run" )
. Add ( " --detach" )
. Add ( " -e" ) . Add ( " POSTGRES_PASSWORD=hello world" )
. AddOption ( " --network" , network)
. Add ( " postgres" )
)
. ExecuteBufferedAsync ( ) ;
Show how changing network
to empty string does not pass the option
Event stream execution models
var cmd = Cli. Wrap ( " docker" )
. WithArguments ( args => args
. Add ( " run" )
. Add ( " -e" ) . Add ( " POSTGRES_PASSWORD=hello world" )
. Add ( " postgres" )
) ;
await foreach ( var cmdEvent in cmd. ListenAsync ( ) )
{
switch ( cmdEvent )
{
case StartedCommandEvent started:
{
Console. WriteLine ( " Process started. PID: " + started. ProcessId) ;
break ;
}
case StandardOutputCommandEvent stdOut:
{
Console. ForegroundColor = ConsoleColor. White;
Console. WriteLine ( " OUT> " + stdOut. Text) ;
Console. ResetColor ( ) ;
break ;
}
case StandardErrorCommandEvent stdErr:
{
Console. ForegroundColor = ConsoleColor. DarkRed;
Console. WriteLine ( " ERR> " + stdErr. Text) ;
Console. ResetColor ( ) ;
break ;
}
case ExitedCommandEvent exited:
{
Console. WriteLine ( " Process exited. Code: " + exited. ExitCode) ;
break ;
}
}
}
Show how the events are handled as the process is running
Explain backpressure and single-threaded event flow
Show observable stream execution model
Mention that you can easily create your own execution models
var stdOutBuffer = new StringBuilder( ) ;
var stdErrBuffer = new StringBuilder( ) ;
await Cli. Wrap ( " docker" )
. WithArguments ( args => args
. Add ( " run" )
. Add ( " --detach" )
. Add ( " -e" ) . Add ( " POSTGRES_PASSWORD=hello world" )
. Add ( " postgres" )
)
. WithStandardOutputPipe ( PipeTarget. ToStringBuilder ( stdOutBuffer) )
. WithStandardErrorPipe ( PipeTarget. ToStringBuilder ( stdErrBuffer) )
. ExecuteAsync ( ) ;
Console. WriteLine ( stdOutBuffer) ;
Console. WriteLine ( stdErrBuffer) ;
Explain that this is functionally equivalent to ExecuteBufferedAsync()
(exception non-zero exit code exception message)
Show PipeTarget
contract
await Cli. Wrap ( " docker" )
. WithArguments ( args => args
. Add ( " run" )
. Add ( " --detach" )
. Add ( " -e" ) . Add ( " POSTGRES_PASSWORD=hello world" )
. Add ( " postgres" )
)
. WithStandardOutputPipe ( PipeTarget. ToDelegate ( Console. WriteLine) )
. WithStandardErrorPipe ( PipeTarget. ToDelegate ( Console. WriteLine) )
. ExecuteAsync ( ) ;
Explain that ListenAsync()
and ObserveAsync()
are using a simlar setup under the hood
Show that this supports async delegates too (Func<string, Task>
)
await Cli. Wrap ( " docker" )
. WithArguments ( args => args
. Add ( " run" )
. Add ( " --detach" )
. Add ( " -e" ) . Add ( " POSTGRES_PASSWORD=hello world" )
. Add ( " postgres" )
)
. WithStandardOutputPipe ( PipeTarget. ToFile ( " stdout.txt" ) )
. WithStandardErrorPipe ( PipeTarget. ToFile ( " stderr.txt" ) )
. ExecuteAsync ( ) ;
var result = await Cli. Wrap ( " docker" )
. WithArguments ( args => args
. Add ( " run" )
. Add ( " --detach" )
. Add ( " -e" ) . Add ( " POSTGRES_PASSWORD=hello world" )
. Add ( " postgres" )
)
. WithStandardOutputPipe ( PipeTarget. ToDelegate ( Console. WriteLine) )
. WithStandardErrorPipe ( PipeTarget. ToDelegate ( Console. WriteLine) )
. ExecuteBufferedAsync ( ) ;
Console. WriteLine ( result. StandardOutput) ;
Explain how execution models work without overriding pipes
var cmd = Cli. Wrap ( " docker" )
. WithArguments ( args => args
. Add ( " run" )
. Add ( " -e" ) . Add ( " POSTGRES_PASSWORD=hello world" )
. Add ( " postgres" )
) | ( Console. WriteLine, Console. Error. WriteLine) ;
await cmd. ExecuteAsync ( ) ;
Explain pipe operators
Explain how pipe operators work with tuples
using var cts = new CancellationTokenSource( TimeSpan. FromSeconds ( 6 ) ) ;
try
{
await Cli. Wrap ( " ping" )
. WithArguments ( " -t google.com" )
. WithStandardOutputPipe ( PipeTarget. ToDelegate ( Console. WriteLine) )
. ExecuteAsync ( cts. Token) ;
}
catch ( OperationCanceledException )
{
Console. WriteLine ( " Cancelled!" ) ;
}
Explain how CliWrap kills the process on cancellation
var cmd = PipeSource. FromFile ( " video.mp4" ) | Cli. Wrap ( " ffmpeg" )
. WithArguments ( args => args
. Add ( " -i" ) . Add ( " -" )
. Add ( " video_out.webm" )
) | ( Console. WriteLine, Console. Error. WriteLine) ;
await cmd. ExecuteAsync ( ) ;
Mention other sources
Remind that the same can be done using WithStandardInputPipe(...)
instead
var youtube = new YoutubeClient( ) ;
var streamManifest = await youtube. Videos. Streams. GetManifestAsync ( " https://www.youtube.com/watch?v=-Sf9NPQeZOQ" ) ;
var streamInfo = streamManifest. GetVideoStreams ( ) . GetWithHighestVideoQuality ( ) ;
var stream = await youtube. Videos. Streams. GetAsync ( streamInfo) ;
var cmd = stream | Cli. Wrap ( " ffmpeg" )
. WithArguments ( args => args
. Add ( " -i" ) . Add ( " -" )
. Add ( " -preset" ) . Add ( " ultrafast" )
. Add ( " youtube_video.mp4" )
) | ( Console. WriteLine, Console. Error. WriteLine) ;
await cmd. ExecuteAsync ( ) ;
Process-to-process piping
var cmd =
// Take first 5 seconds of the video and convert to webm
Cli. Wrap ( " ffmpeg" )
. WithArguments ( args => args
. Add ( " -i" ) . Add ( " video.mp4" )
. Add ( " -t" ) . Add ( 5 )
. Add ( " -f" ) . Add ( " webm" )
. Add ( " -" ) ) |
// Reverse the stream and write to file
Cli. Wrap ( " ffmpeg" )
. WithArguments ( args => args
. Add ( " -i" ) . Add ( " -" )
. Add ( " -vf" ) . Add ( " reverse" )
. Add ( " video_altered.webm" ) ) |
( Console. WriteLine, Console. Error. WriteLine) ;
await cmd. ExecuteAsync ( ) ;