command-stream 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,382 @@
1
+ # Best Practices for command-stream (Rust)
2
+
3
+ This document covers best practices, common patterns, and pitfalls to avoid when using the command-stream Rust library.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Argument Handling with Macros](#argument-handling-with-macros)
8
+ - [String Interpolation](#string-interpolation)
9
+ - [Security Best Practices](#security-best-practices)
10
+ - [Error Handling](#error-handling)
11
+ - [Async Patterns](#async-patterns)
12
+ - [Common Pitfalls](#common-pitfalls)
13
+
14
+ ---
15
+
16
+ ## Argument Handling with Macros
17
+
18
+ ### Using the cmd!/s!/sh! Macros
19
+
20
+ The command-stream macros (`cmd!`, `s!`, `sh!`, `cs!`) provide safe interpolation similar to JavaScript's `$` template literal:
21
+
22
+ ```rust
23
+ use command_stream::s;
24
+
25
+ #[tokio::main]
26
+ async fn main() -> Result<(), Box<dyn std::error::Error>> {
27
+ // Simple command
28
+ let result = s!("echo hello world").await?;
29
+
30
+ // With interpolation (automatically quoted)
31
+ let name = "world";
32
+ let result = s!("echo hello {}", name).await?;
33
+
34
+ // Multiple arguments
35
+ let file = "test.txt";
36
+ let flag = "--verbose";
37
+ let result = s!("cat {} {}", file, flag).await?;
38
+
39
+ Ok(())
40
+ }
41
+ ```
42
+
43
+ ### Handling Multiple Arguments
44
+
45
+ When you have a collection of arguments, handle them correctly:
46
+
47
+ ```rust
48
+ use command_stream::{run, quote};
49
+
50
+ // CORRECT: Use quote::quote_all for multiple arguments
51
+ let args = vec!["file.txt", "--public", "--verbose"];
52
+ let quoted_args = quote::quote_all(&args);
53
+ let result = run(format!("command {}", quoted_args)).await?;
54
+
55
+ // CORRECT: Build command with individual quotes
56
+ let args = vec!["file.txt", "--public", "--verbose"];
57
+ let cmd = format!("command {}",
58
+ args.iter()
59
+ .map(|a| quote::quote(a))
60
+ .collect::<Vec<_>>()
61
+ .join(" ")
62
+ );
63
+ let result = run(cmd).await?;
64
+ ```
65
+
66
+ ### Vec/Slice Handling Patterns
67
+
68
+ Unlike JavaScript where arrays are handled automatically in template literals, Rust requires explicit handling:
69
+
70
+ ```rust
71
+ use command_stream::quote;
72
+
73
+ // Pattern 1: quote_all function
74
+ let args = vec!["file.txt", "--verbose"];
75
+ let args_str = quote::quote_all(&args);
76
+ // Result: "file.txt --verbose" (each arg properly quoted)
77
+
78
+ // Pattern 2: Manual iteration
79
+ let args = vec!["file with spaces.txt", "--verbose"];
80
+ let args_str = args.iter()
81
+ .map(|a| quote::quote(a))
82
+ .collect::<Vec<_>>()
83
+ .join(" ");
84
+ // Result: "'file with spaces.txt' --verbose"
85
+
86
+ // Pattern 3: Format with multiple placeholders
87
+ let file = "data.txt";
88
+ let flag1 = "--verbose";
89
+ let flag2 = "--force";
90
+ let result = s!("cmd {} {} {}", file, flag1, flag2).await?;
91
+ ```
92
+
93
+ ---
94
+
95
+ ## String Interpolation
96
+
97
+ ### Safe Interpolation (Default)
98
+
99
+ The `quote` function automatically escapes dangerous characters:
100
+
101
+ ```rust
102
+ use command_stream::quote::quote;
103
+
104
+ let dangerous = "'; rm -rf /; echo '";
105
+ let safe = quote(dangerous);
106
+ // Result: "''\\'' rm -rf /; echo '\\'''"
107
+ // Shell will treat this as a literal string
108
+ ```
109
+
110
+ ### When Quoting is Applied
111
+
112
+ ```rust
113
+ use command_stream::quote::{quote, needs_quoting};
114
+
115
+ // Safe strings pass through unchanged
116
+ assert_eq!(quote("hello"), "hello");
117
+ assert_eq!(quote("/path/to/file"), "/path/to/file");
118
+
119
+ // Dangerous strings are quoted
120
+ assert_eq!(quote("hello world"), "'hello world'");
121
+ assert_eq!(quote("$var"), "'$var'");
122
+
123
+ // Check if quoting is needed
124
+ assert!(!needs_quoting("hello"));
125
+ assert!(needs_quoting("hello world"));
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Security Best Practices
131
+
132
+ ### Never Trust User Input
133
+
134
+ ```rust
135
+ use command_stream::{run, quote};
136
+
137
+ async fn process_file(user_filename: &str) -> Result<(), Box<dyn std::error::Error>> {
138
+ // CORRECT: Quote user input
139
+ let safe_filename = quote::quote(user_filename);
140
+ let result = run(format!("cat {}", safe_filename)).await?;
141
+
142
+ // ALSO CORRECT: Use macro interpolation
143
+ let result = s!("cat {}", user_filename).await?;
144
+
145
+ Ok(())
146
+ }
147
+ ```
148
+
149
+ ### Validate Before Execution
150
+
151
+ ```rust
152
+ use command_stream::run;
153
+ use std::path::Path;
154
+
155
+ async fn delete_file(filename: &str) -> Result<(), Box<dyn std::error::Error>> {
156
+ // Validate: no path traversal
157
+ if filename.contains("..") || filename.starts_with('/') {
158
+ return Err("Invalid filename".into());
159
+ }
160
+
161
+ // Validate: file exists and is a file (not directory)
162
+ let path = Path::new(filename);
163
+ if !path.is_file() {
164
+ return Err("Not a file".into());
165
+ }
166
+
167
+ run(format!("rm {}", quote::quote(filename))).await?;
168
+ Ok(())
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Error Handling
175
+
176
+ ### Check Results
177
+
178
+ ```rust
179
+ use command_stream::run;
180
+
181
+ async fn example() -> Result<(), Box<dyn std::error::Error>> {
182
+ let result = run("ls nonexistent").await?;
183
+
184
+ match result.code {
185
+ 0 => println!("Success: {}", result.stdout),
186
+ 2 => eprintln!("File not found"),
187
+ 127 => eprintln!("Command not found"),
188
+ code => eprintln!("Unknown error (code {})", code),
189
+ }
190
+
191
+ Ok(())
192
+ }
193
+ ```
194
+
195
+ ### Using the ? Operator
196
+
197
+ ```rust
198
+ use command_stream::{run, Result};
199
+
200
+ async fn critical_operation() -> Result<String> {
201
+ let result = run("important-command").await?;
202
+
203
+ if result.code != 0 {
204
+ return Err(command_stream::Error::CommandFailed {
205
+ code: result.code,
206
+ message: result.stderr,
207
+ });
208
+ }
209
+
210
+ Ok(result.stdout)
211
+ }
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Async Patterns
217
+
218
+ ### Basic Async Usage
219
+
220
+ ```rust
221
+ use command_stream::run;
222
+
223
+ #[tokio::main]
224
+ async fn main() -> Result<(), Box<dyn std::error::Error>> {
225
+ let result = run("echo hello").await?;
226
+ println!("{}", result.stdout);
227
+ Ok(())
228
+ }
229
+ ```
230
+
231
+ ### Parallel Execution
232
+
233
+ ```rust
234
+ use command_stream::run;
235
+ use tokio;
236
+
237
+ async fn parallel_tasks() -> Result<(), Box<dyn std::error::Error>> {
238
+ // Run multiple commands in parallel
239
+ let (r1, r2, r3) = tokio::join!(
240
+ run("task1"),
241
+ run("task2"),
242
+ run("task3")
243
+ );
244
+
245
+ println!("Task 1: {}", r1?.stdout);
246
+ println!("Task 2: {}", r2?.stdout);
247
+ println!("Task 3: {}", r3?.stdout);
248
+
249
+ Ok(())
250
+ }
251
+ ```
252
+
253
+ ### Using ProcessRunner for Control
254
+
255
+ ```rust
256
+ use command_stream::{ProcessRunner, RunOptions};
257
+
258
+ async fn controlled_execution() -> Result<(), Box<dyn std::error::Error>> {
259
+ let options = RunOptions {
260
+ capture: true,
261
+ mirror: false, // Don't print to terminal
262
+ ..Default::default()
263
+ };
264
+
265
+ let mut runner = ProcessRunner::new("long-command", options);
266
+ runner.start().await?;
267
+ let result = runner.run().await?;
268
+
269
+ println!("Captured: {}", result.stdout);
270
+ Ok(())
271
+ }
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Common Pitfalls
277
+
278
+ ### 1. String Formatting Without Quoting
279
+
280
+ **Problem:** Using `format!` without quoting can cause issues with special characters.
281
+
282
+ ```rust
283
+ // WRONG: Spaces break the command
284
+ let filename = "my file.txt";
285
+ let cmd = format!("cat {}", filename);
286
+ // Result: "cat my file.txt" - interpreted as two args!
287
+
288
+ // CORRECT: Quote the value
289
+ let cmd = format!("cat {}", quote::quote(filename));
290
+ // Result: "cat 'my file.txt'" - single argument
291
+ ```
292
+
293
+ ### 2. Vec Join Without Proper Quoting
294
+
295
+ **Problem:** Joining a Vec without quoting each element.
296
+
297
+ ```rust
298
+ // WRONG: join doesn't quote elements
299
+ let args = vec!["file with spaces.txt", "--flag"];
300
+ let cmd = format!("command {}", args.join(" "));
301
+ // Result: "command file with spaces.txt --flag" - BROKEN!
302
+
303
+ // CORRECT: Use quote_all
304
+ let cmd = format!("command {}", quote::quote_all(&args));
305
+ // Result: "command 'file with spaces.txt' --flag"
306
+ ```
307
+
308
+ ### 3. Forgetting .await
309
+
310
+ **Problem:** Async functions return futures that must be awaited.
311
+
312
+ ```rust
313
+ // WRONG: Command never executes
314
+ let result = run("echo hello"); // Returns Future, not Result!
315
+
316
+ // CORRECT: Await the future
317
+ let result = run("echo hello").await?;
318
+ ```
319
+
320
+ ### 4. Not Handling Non-Zero Exit Codes
321
+
322
+ **Problem:** Assuming success without checking.
323
+
324
+ ```rust
325
+ // RISKY: May fail silently
326
+ let result = run("risky-command").await?;
327
+ use_output(&result.stdout);
328
+
329
+ // BETTER: Check exit code
330
+ let result = run("risky-command").await?;
331
+ if result.code == 0 {
332
+ use_output(&result.stdout);
333
+ } else {
334
+ handle_error(&result.stderr);
335
+ }
336
+ ```
337
+
338
+ ### 5. Blocking in Async Context
339
+
340
+ **Problem:** Using `run_sync` in async context blocks the runtime.
341
+
342
+ ```rust
343
+ // WRONG in async context
344
+ async fn bad_example() {
345
+ // This blocks the entire runtime thread!
346
+ let result = run_sync("slow-command");
347
+ }
348
+
349
+ // CORRECT: Use async version
350
+ async fn good_example() {
351
+ let result = run("slow-command").await;
352
+ }
353
+ ```
354
+
355
+ ---
356
+
357
+ ## Quick Reference
358
+
359
+ ### Do's
360
+
361
+ - Use `quote::quote()` for individual values
362
+ - Use `quote::quote_all()` for Vec/slice of arguments
363
+ - Use macro interpolation (`s!`, `cmd!`) for safe templating
364
+ - Always `.await` async operations
365
+ - Check exit codes for critical operations
366
+ - Validate user input before execution
367
+
368
+ ### Don'ts
369
+
370
+ - Never format user input without quoting
371
+ - Never use `args.join(" ")` without quoting each element
372
+ - Don't forget `.await` on futures
373
+ - Don't assume commands succeed
374
+ - Don't block async contexts with `run_sync`
375
+
376
+ ---
377
+
378
+ ## See Also
379
+
380
+ - [../js/BEST-PRACTICES.md](../js/BEST-PRACTICES.md) - JavaScript best practices
381
+ - [src/quote.rs](src/quote.rs) - Quote function implementation
382
+ - [src/macros.rs](src/macros.rs) - Macro implementations