Skip to content

Commit 3fe9cd2

Browse files
Merge pull request #5 from NextStd/docs/update_installation_steps
docs(command): Add docs for ns_cmd
2 parents 42161cb + 1d5fc53 commit 3fe9cd2

File tree

6 files changed

+305
-0
lines changed

6 files changed

+305
-0
lines changed

src/SUMMARY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,16 @@
2828
- [Dynamic Arrays (`ns_vec`)](data_structures/vectors.md)
2929
- [Key-Value Maps (`ns_hashmap`)](data_structures/hashmap.md)
3030

31+
- [Command Execution (`ns_cmd`)](cmd/README.md)
32+
- [Running Shell Commands](cmd/execution.md)
33+
- [Capturing Stdout & Stderr](cmd/output-capturing.md)
34+
- [Auto-Cleanup (RAII in C)](cmd/auto-cleanup.md)
35+
3136
- [Examples](examples/README.md)
3237
- [Safe User Input](examples/safe-input.md)
3338
- [Building a CLI Menu](examples/cli-menu.md)
3439
- [String Manipulation](examples/string-manipulation.md)
40+
- [Process Execution & Auto-Free](examples/process-exec.md)
3541

3642
- [Contributing](contributing/README.md)
3743
- [Architecture Overview](contributing/architecture.md)

src/cmd/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Command Execution (`ns_cmd`)
2+
3+
Executing shell commands in standard C usually involves reaching for the
4+
`system()` function or wrestling with `popen()`.
5+
6+
Both approaches are fraught with legacy issues. `system()` offers absolutely no
7+
way to capture the output of the command (it just prints directly to the
8+
terminal), and `popen()` requires manually managing streams, allocating buffers,
9+
and parsing raw bytes—often leading to memory leaks or buffer overflows.
10+
11+
The **`ns_cmd`** module completely replaces these legacy functions with a
12+
modern, memory-safe, and highly ergonomic wrapper around Rust's
13+
`std::process::Command`.
14+
15+
## The NextStd Solution
16+
17+
With `ns_cmd`, executing a command and capturing its exact output is safe and
18+
synchronous.
19+
20+
* **Complete Stream Capture:** Both `stdout` and `stderr` are automatically
21+
captured and placed into NextStd's blazingly fast
22+
[Small String Optimized (`ns_string`)](../string/README.md) structs.
23+
* **Exit Codes:** The exact exit code of the shell process is captured and
24+
returned.
25+
* **Smart Routing:** Pass either a standard C string literal (`"uname -a"`) or a
26+
dynamically built `ns_string`. The macros figure it out automatically.
27+
* **Zero Memory Leaks:** Thanks to the `ns_autocmd` macro, process output
28+
structs automatically free their own internal string buffers the moment they
29+
go out of scope.
30+
31+
## Quick Glance
32+
33+
Here is how simple it is to execute a command, read its output, and clean up the
34+
memory—all in four lines of C:
35+
36+
```c
37+
#include <nextstd/ns.h>
38+
#include <nextstd/ns_cmd.h>
39+
40+
int main(void) {
41+
// 1. Initialize the output struct with the auto-cleanup macro
42+
ns_autocmd ns_cmd_output out = {0};
43+
44+
// 2. Run the command safely
45+
ns_cmd_run("uname -a", &out);
46+
47+
// 3. Use the captured data!
48+
ns_println("Status: {}", out.exit_code);
49+
ns_println("Output: {}", out.stdout_data);
50+
51+
// 4. No free() required. Memory cleans itself up here!
52+
return 0;
53+
}
54+
```
55+
56+
## Deep Dive
57+
58+
Explore the specific features of the `ns_cmd` module:
59+
60+
* [Running Shell Commands](execution.md)
61+
* [Capturing Stdout & Stderr](output-capturing.md)
62+
* [Auto-Cleanup (RAII in C)](auto-cleanup.md)

src/cmd/auto-cleanup.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Auto-Cleanup (RAII in C)
2+
3+
One of the biggest pain points in standard C is manual memory management. If a
4+
library dynamically allocates memory to capture a string or a file, the
5+
developer is strictly responsible for calling `free()` when they are done.
6+
7+
If you forget, your program leaks memory. If you call it twice, your program
8+
crashes.
9+
10+
Languages like C++ and Rust solved this decades ago using **RAII** (Resource
11+
Acquisition Is Initialization) and destructors. When a variable goes out of
12+
scope (reaches the closing brace `}` of its block), the compiler automatically
13+
inserts the cleanup code.
14+
15+
With NextStd, you get that exact same magic in C.
16+
17+
## The `ns_autocmd` Macro
18+
19+
NextStd leverages a powerful, widely-supported GCC/Clang compiler extension
20+
called `__attribute__((cleanup))`. We wrap this extension in a simple, ergonomic
21+
macro called `ns_autocmd`.
22+
23+
When you prefix your `ns_cmd_output` struct with `ns_autocmd`, you are attaching
24+
a hidden destructor to the variable. The moment that variable falls out of
25+
scope, the compiler automatically intercepts it and safely frees any internal
26+
`ns_string` heap allocations.
27+
28+
```c
29+
#include <nextstd/ns.h>
30+
#include <nextstd/ns_cmd.h>
31+
32+
void run_status_check() {
33+
// 1. Declare the struct with the auto-cleanup macro
34+
ns_autocmd ns_cmd_output out = {0};
35+
36+
// 2. Run the command
37+
ns_cmd_run("uptime", &out);
38+
39+
// 3. Print the result
40+
ns_println("System Uptime: {}", out.stdout_data);
41+
42+
// 4. End of scope. The compiler automatically calls
43+
// ns_cmd_output_free(&out) right here! No memory leaks!
44+
}
45+
```
46+
47+
## ⚠️ The Golden Rule: Zero-Initialization
48+
49+
There is one critical rule when using `ns_autocmd`:
50+
**You must always zero-initialize your struct (`= {0};`).**
51+
52+
```c
53+
// ❌ DANGEROUS: Contains random stack garbage memory
54+
ns_autocmd ns_cmd_output bad_out;
55+
56+
// ✅ SAFE: Memory is explicitly zeroed out
57+
ns_autocmd ns_cmd_output good_out = {0};
58+
```
59+
60+
**Why is this required?** In C, uninitialized stack variables contain random
61+
"garbage" data left over in RAM.
62+
63+
If `ns_cmd_run` were to fail *before* it could successfully populate your struct
64+
(for example, if the system couldn't spawn a shell), the destructor would
65+
eventually run and try to `free()` that random garbage memory. This results in a
66+
guaranteed segmentation fault.
67+
68+
By zero-initializing the struct (`= {0};`), you guarantee that if the command
69+
fails early, the destructor simply sees `NULL` pointers and lengths of `0`, and
70+
safely does nothing.
71+
72+
## Manual Cleanup
73+
74+
If you are compiling on a highly restrictive embedded compiler that does not
75+
support GCC/Clang extensions, or if you simply prefer manual memory management,
76+
you can still declare the struct normally and use the manual free function:
77+
78+
```c
79+
ns_cmd_output out = {0};
80+
ns_cmd_run("ls", &out);
81+
ns_println("Output: {}", out.stdout_data);
82+
83+
// Manually free the internal strings
84+
ns_cmd_output_free(&out);
85+
```

src/cmd/execution.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Running Shell Commands
2+
3+
The core of the `ns_cmd` module is the `ns_cmd_run` macro. Unlike standard C
4+
functions that force you to strictly cast your types or use entirely different
5+
function names for different inputs (like `print_int` vs `print_string`),
6+
NextStd uses modern C11 features to do the heavy lifting for you.
7+
8+
## The `_Generic` Routing Magic
9+
10+
In standard C, if an execution function expected a dynamic string struct, you
11+
would be forced to convert your simple string literals into structs every single
12+
time you wanted to run a basic command.
13+
14+
NextStd solves this by turning `ns_cmd_run` into a `_Generic` macro wrapper.
15+
16+
When you call `ns_cmd_run`, the C compiler inspects the type of the argument you
17+
passed and automatically routes it to the correct Rust FFI backend function:
18+
19+
* If you pass a `"string literal"`, it routes to `ns_cmd_run_cstr`.
20+
* If you pass an `ns_string` struct, it routes to `ns_cmd_run_ns`.
21+
22+
### 1. Passing Standard C Strings
23+
24+
For 90% of your use cases, you will likely just pass a hardcoded string literal.
25+
The macro accepts it natively:
26+
27+
```c
28+
#include <nextstd/ns.h>
29+
#include <nextstd/ns_cmd.h>
30+
31+
int main(void) {
32+
ns_autocmd ns_cmd_output out = {0};
33+
34+
// The compiler sees a const char* and routes it automatically!
35+
ns_cmd_run("ls -la /var/log", &out);
36+
37+
return 0;
38+
}
39+
```
40+
41+
### 2. Passing Dynamic `ns_string` Structs
42+
43+
If you are building a command dynamically based on user input or file parsing,
44+
you can pass your NextStd `ns_string` directly into the exact same macro. No
45+
unwrapping or `.ptr` access required:
46+
47+
```c
48+
#include <nextstd/ns.h>
49+
#include <nextstd/ns_cmd.h>
50+
#include <nextstd/ns_string.h>
51+
52+
int main(void) {
53+
ns_autocmd ns_cmd_output out = {0};
54+
ns_string my_dynamic_cmd;
55+
56+
// Safely build the command
57+
ns_string_new(&my_dynamic_cmd, "echo 'NextStd is awesome!'");
58+
59+
// Pass the struct directly!
60+
ns_cmd_run(my_dynamic_cmd, &out);
61+
62+
// Clean up the input string
63+
ns_string_free(&my_dynamic_cmd);
64+
65+
return 0;
66+
}
67+
```
68+
69+
## Under the Hood: `sh -c`
70+
71+
When the Rust backend receives your command (regardless of how it was routed),
72+
it securely spawns a new process using `sh -c "<your_command>"`.
73+
74+
This mimics the exact behavior of C's `system()`, meaning you have full access
75+
to standard shell features like piping (`|`), redirecting (`>`), and environment
76+
variable expansion (`$USER`), while still running entirely within Rust's
77+
memory-safe execution sandbox.

src/cmd/output-capturing.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Capturing Stdout & Stderr
2+
3+
The fatal flaw of standard C's `system()` function is that it blindly dumps the
4+
command's output directly to the user's terminal. If your C program actually
5+
needs to *read* that output to parse a version number, check a status, or log an
6+
error, `system()` is completely useless.
7+
8+
You are usually forced to use `popen()`, which requires manually allocating
9+
buffers, reading chunks in a `while` loop, and praying you don't trigger a
10+
buffer overflow.
11+
12+
NextStd completely eliminates this friction.
13+
14+
## The `ns_cmd_output` Struct
15+
16+
When you run a command using `ns_cmd_run`, the entire lifecycle of the process
17+
is captured and packed into a single, clean C struct:
18+
19+
```c
20+
typedef struct ns_cmd_output {
21+
ns_string stdout_data;
22+
ns_string stderr_data;
23+
int exit_code;
24+
} ns_cmd_output;
25+
```
26+
27+
### 1. The Output Streams (`stdout_data` & `stderr_data`)
28+
29+
Instead of raw `char*` arrays that you have to manually resize, NextStd uses the
30+
underlying Rust `std::process::Command` engine to capture the raw bytes of the
31+
streams and convert them directly into memory-safe `ns_string` structs.
32+
33+
**The SSO Advantage:** Because these streams are backed by NextStd's Small
34+
String Optimization (SSO), if your command outputs less than 24 bytes (like
35+
simply printing `"OK"` or an exit status string),
36+
**zero heap allocation occurs.** The output is stored directly inside the struct
37+
on the stack, making it incredibly fast.
38+
39+
### 2. The Exit Code
40+
41+
NextStd captures the exact integer status code returned by the shell. You no
42+
longer have to mess with C's cryptic `WEXITSTATUS` macros just to find out if
43+
your command succeeded. If the process exits cleanly, `exit_code` will be `0`.
44+
45+
## Example: Parsing Success vs. Failure
46+
47+
Because NextStd strictly separates standard output from standard error, your C
48+
program can easily build branching logic based on whether a command failed.
49+
50+
```c
51+
#include <nextstd/ns.h>
52+
#include <nextstd/ns_cmd.h>
53+
54+
int main(void) {
55+
ns_autocmd ns_cmd_output out = {0};
56+
57+
// Attempting to read a file that doesn't exist
58+
ns_cmd_run("cat /path/to/missing_file.txt", &out);
59+
60+
if (out.exit_code != 0) {
61+
ns_println("Command failed with code: {}", out.exit_code);
62+
// We can safely read the exact error message the shell generated
63+
ns_println("Error Reason: {}", out.stderr_data);
64+
} else {
65+
ns_println("File Contents: {}", out.stdout_data);
66+
}
67+
68+
return 0;
69+
}
70+
```
71+
72+
By default, these strings are dynamically allocated (if over 23 bytes) and
73+
require cleanup. However, thanks to a powerful compiler extension, you rarely
74+
ever need to free them manually.

src/examples/process-exec.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Process Execution &amp; Auto-Free

0 commit comments

Comments
 (0)