Skip to content

Commit 125d9d1

Browse files
committed
receive FormattableString, add ignore, print stdError, FirstAsync wait
1 parent 3c7290d commit 125d9d1

File tree

4 files changed

+125
-39
lines changed

4 files changed

+125
-39
lines changed

README.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ await "cat package.json | grep name";
164164

165165
// receive result msg of stdout
166166
var branch = await "git branch --show-current";
167-
await $"dep deploy --branch={branch}";
167+
await run($"dep deploy --branch={branch}");
168168

169169
// parallel request (similar as Task.WhenAll)
170170
await new[]
@@ -213,10 +213,10 @@ writing shell script in C# has advantage over bash/cmd/PowerShell
213213
using Zx;
214214
using static Zx.Env;
215215

216-
// Env.verbose, write all stdout log to console. default is true.
216+
// Env.verbose, write all stdout/stderror log to console. default is true.
217217
verbose = false;
218218

219-
// Env.shell, default is Windows -> "cmd /c", Linux -> "bash -c";.
219+
// Env.shell, default is Windows -> "cmd /c", Linux -> "(which bash) -c";.
220220
shell = "/bin/sh -c";
221221

222222
// Env.terminateToken, CancellationToken that triggered by SIGTERM(Ctrl + C).
@@ -236,16 +236,30 @@ Console.WriteLine(text);
236236
// Env.sleep(int seconds|TimeSpan timeSpan), wrapper of Task.Delay.
237237
await sleep(5); // wait 5 seconds
238238
239-
// Env.withTimeout(string command, int seconds|TimeSpan timeSpan), execute process with timeout.
240-
await withTimeout("echo foo", 10);
239+
// Env.withTimeout(string command, int seconds|TimeSpan timeSpan), execute process with timeout. Require to use with "$".
240+
await withTimeout($"echo foo", 10);
241241

242-
// Env.withCancellation(string command, CancellationToken cancellationToken), execute process with cancellation.
243-
await withCancellation("echo foo", terminateToken);
242+
// Env.withCancellation(string command, CancellationToken cancellationToken), execute process with cancellation. Require to use with "$".
243+
await withCancellation($"echo foo", terminateToken);
244+
245+
// Env.run(FormattableString), automatically escaped and quoted. argument string requires to use with "$"
246+
await run($"mkdir {dir}");
244247

245248
// Env.process(string command), same as `await string` but returns Task<string>.
246249
var t = process("dotnet info");
250+
251+
// Env.ignore(Task), ignore ProcessErrorException
252+
await ignore(run($"dotnet noinfo"));
253+
254+
// ***2 receives tuple of result (StdOut, StdError).
255+
var (stdout, stderror) = run2($"");
256+
var (stdout, stderror) = withTimeout2($"");
257+
var (stdout, stderror) = withCancellation2($"");
258+
var (stdout, stderror) = process2($"");
247259
```
248260

261+
`await string` does not escape argument so recommend to use `run($"string")` when use with argument.
262+
249263
Reference
250264
---
251265
`ProcessX.StartAsync` overloads, you can set workingDirectory, environmentVariable, encoding.
@@ -266,10 +280,10 @@ StartReadBinaryAsync(string command, string? workingDirectory = null, IDictionar
266280
StartReadBinaryAsync(string fileName, string? arguments, string? workingDirectory = null, IDictionary<string, string>? environmentVariable = null, Encoding? encoding = null)
267281
StartReadBinaryAsync(ProcessStartInfo processStartInfo)
268282

269-
// return Task<string>
283+
// return Task<string> ;get the first result(if empty, throws exception) and wait completed
270284
FirstAsync(CancellationToken cancellationToken = default)
271285

272-
// return Task<string?>
286+
// return Task<string?> ;get the first result(if empty, returns null) and wait completed
273287
FirstOrDefaultAsync(CancellationToken cancellationToken = default)
274288

275289
// return Task

sandbox/ConsoleApp/Program.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,19 @@
44
// ProcessX and C# 9.0 Top level statement; like google/zx.
55

66
using Cysharp.Diagnostics;
7+
using System.Runtime.CompilerServices;
78
using Zx;
89
using static Zx.Env;
910

1011

1112

12-
await ProcessX.StartAsync("cmd /c mkdir foo").WaitAsync();
13-
14-
return;
15-
16-
1713

1814
// `await string` execute process like shell
19-
await "cat package.json | grep name";
15+
//await "cat package.json | grep name";
2016

2117
// receive result msg of stdout
2218
var branch = await "git branch --show-current";
23-
await $"dep deploy --branch={branch}";
19+
//await $"dep deploy --branch={branch}";
2420

2521
// parallel request (similar as Task.WhenAll)
2622
await new[]
@@ -56,6 +52,7 @@ await new[]
5652

5753

5854

55+
await ignore(run($"dotnet noinfo"));
5956

6057

6158

src/ProcessX/ProcessAsyncEnumerable.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4-
using System.Text;
54
using System.Threading;
65
using System.Threading.Channels;
76
using System.Threading.Tasks;
@@ -35,27 +34,37 @@ public async Task WaitAsync(CancellationToken cancellationToken = default)
3534
}
3635

3736
/// <summary>
38-
/// Returning first value. If does not return any data, returns empty string.
37+
/// Returning first value and wait complete asynchronously.
3938
/// </summary>
4039
public async Task<string> FirstAsync(CancellationToken cancellationToken = default)
4140
{
41+
string? data = null;
4242
await foreach (var item in this.WithCancellation(cancellationToken).ConfigureAwait(false))
4343
{
44-
return item;
44+
data = (item ?? "");
45+
}
46+
47+
if (data == null)
48+
{
49+
throw new InvalidOperationException("Process does not return any data.");
50+
}
51+
else
52+
{
53+
return data;
4554
}
46-
throw new InvalidOperationException("Process does not return any data.");
4755
}
4856

4957
/// <summary>
50-
/// Returning first value or null.
58+
/// Returning first value or null and wait complete asynchronously.
5159
/// </summary>
5260
public async Task<string?> FirstOrDefaultAsync(CancellationToken cancellationToken = default)
5361
{
62+
string? data = null;
5463
await foreach (var item in this.WithCancellation(cancellationToken).ConfigureAwait(false))
5564
{
56-
return item;
65+
data = (item ?? "");
5766
}
58-
return default;
67+
return data;
5968
}
6069

6170
public async Task<string[]> ToTask(CancellationToken cancellationToken = default)

src/ProcessX/Zx/Env.cs

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,40 +80,83 @@ public static Task sleep(TimeSpan timeSpan, CancellationToken cancellationToken
8080
return Task.Delay(timeSpan, cancellationToken);
8181
}
8282

83-
public static async Task<string> withTimeout(string command, int seconds)
83+
public static async Task<string> withTimeout(FormattableString command, int seconds)
8484
{
8585
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(seconds)))
8686
{
87-
return await ProcessStartAsync(command, cts.Token);
87+
return (await ProcessStartAsync(EscapeFormattableString.Escape(command), cts.Token)).StdOut;
8888
}
8989
}
9090

91-
public static async Task<string> withTimeout(string command, TimeSpan timeSpan)
91+
public static async Task<(string StdOut, string StdError)> withTimeout2(FormattableString command, int seconds)
92+
{
93+
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(seconds)))
94+
{
95+
return (await ProcessStartAsync(EscapeFormattableString.Escape(command), cts.Token));
96+
}
97+
}
98+
99+
public static async Task<string> withTimeout(FormattableString command, TimeSpan timeSpan)
92100
{
93101
using (var cts = new CancellationTokenSource(timeSpan))
94102
{
95-
return await ProcessStartAsync(command, cts.Token);
103+
return (await ProcessStartAsync(EscapeFormattableString.Escape(command), cts.Token)).StdOut;
96104
}
97105
}
98106

99-
public static async Task<string> withCancellation(string command, CancellationToken cancellationToken)
107+
public static async Task<(string StdOut, string StdError)> withTimeout2(FormattableString command, TimeSpan timeSpan)
100108
{
101-
return await ProcessStartAsync(command, cancellationToken);
109+
using (var cts = new CancellationTokenSource(timeSpan))
110+
{
111+
return (await ProcessStartAsync(EscapeFormattableString.Escape(command), cts.Token));
112+
}
113+
}
114+
115+
public static async Task<string> withCancellation(FormattableString command, CancellationToken cancellationToken)
116+
{
117+
return (await ProcessStartAsync(EscapeFormattableString.Escape(command), cancellationToken)).StdOut;
118+
}
119+
120+
public static async Task<(string StdOut, string StdError)> withCancellation2(FormattableString command, CancellationToken cancellationToken)
121+
{
122+
return (await ProcessStartAsync(EscapeFormattableString.Escape(command), cancellationToken));
102123
}
103124

104125
public static Task<string> run(FormattableString command, CancellationToken cancellationToken = default)
105126
{
106127
return process(EscapeFormattableString.Escape(command), cancellationToken);
107128
}
108129

130+
public static Task<(string StdOut, string StdError)> run2(FormattableString command, CancellationToken cancellationToken = default)
131+
{
132+
return process2(EscapeFormattableString.Escape(command), cancellationToken);
133+
}
134+
109135
public static string escape(FormattableString command)
110136
{
111137
return EscapeFormattableString.Escape(command);
112138
}
113139

114-
public static Task<string> process(string command, CancellationToken cancellationToken = default)
140+
public static async Task<string> process(string command, CancellationToken cancellationToken = default)
141+
{
142+
return (await ProcessStartAsync(command, cancellationToken)).StdOut;
143+
}
144+
145+
public static async Task<(string StdOut, string StdError)> process2(string command, CancellationToken cancellationToken = default)
115146
{
116-
return ProcessStartAsync(command, cancellationToken);
147+
return await ProcessStartAsync(command, cancellationToken);
148+
}
149+
150+
public static async Task<T> ignore<T>(Task<T> task)
151+
{
152+
try
153+
{
154+
return await task.ConfigureAwait(false);
155+
}
156+
catch (ProcessErrorException)
157+
{
158+
return default(T)!;
159+
}
117160
}
118161

119162
public static async Task<string> question(string question)
@@ -145,20 +188,43 @@ public static IDisposable color(ConsoleColor color)
145188
return new ColorScope(current);
146189
}
147190

148-
static async Task<string> ProcessStartAsync(string command, CancellationToken cancellationToken, bool forceSilcent = false)
191+
static async Task<(string StdOut, string StdError)> ProcessStartAsync(string command, CancellationToken cancellationToken, bool forceSilcent = false)
149192
{
150193
var cmd = shell + " " + command;
151-
var sb = new StringBuilder();
152-
await foreach (var item in ProcessX.StartAsync(cmd, workingDirectory, envVars).WithCancellation(cancellationToken).ConfigureAwait(false))
194+
var sbOut = new StringBuilder();
195+
var sbError = new StringBuilder();
196+
197+
var (_, stdout, stderror) = ProcessX.GetDualAsyncEnumerable(cmd, workingDirectory, envVars);
198+
199+
var runStdout = Task.Run(async () =>
153200
{
154-
sb.AppendLine(item);
201+
await foreach (var item in stdout.WithCancellation(cancellationToken).ConfigureAwait(false))
202+
{
203+
sbOut.AppendLine(item);
155204

156-
if (verbose && !forceSilcent)
205+
if (verbose && !forceSilcent)
206+
{
207+
Console.WriteLine(item);
208+
}
209+
}
210+
});
211+
212+
var runStdError = Task.Run(async () =>
213+
{
214+
await foreach (var item in stderror.WithCancellation(cancellationToken).ConfigureAwait(false))
157215
{
158-
Console.WriteLine(item);
216+
sbError.AppendLine(item);
217+
218+
if (verbose && !forceSilcent)
219+
{
220+
Console.WriteLine(item);
221+
}
159222
}
160-
}
161-
return sb.ToString();
223+
});
224+
225+
await Task.WhenAll(runStdout, runStdError).ConfigureAwait(false);
226+
227+
return (sbOut.ToString(), sbError.ToString());
162228
}
163229

164230
class ColorScope : IDisposable

0 commit comments

Comments
 (0)