sal/run_builder_implementation_plan.md
2025-04-04 15:05:48 +02:00

179 lines
4.5 KiB
Markdown

# Run Builder Implementation Plan
This document outlines the plan for refactoring the `run.rs` module to use the builder pattern.
## Current Implementation Analysis
The current implementation has several functions for running commands and scripts:
- `run_command` and `run_command_silent` for single commands
- `run_script` and `run_script_silent` for multiline scripts
- `run` and `run_silent` as convenience functions that detect whether the input is a command or script
These functions don't support all the options we want (die, async, log), and they don't follow the builder pattern.
## Builder Pattern Implementation Plan
### 1. Create a `RunBuilder` struct
```rust
pub struct RunBuilder<'a> {
cmd: &'a str,
die: bool,
silent: bool,
async_exec: bool,
log: bool,
}
```
### 2. Implement Default Values and Builder Methods
```rust
impl<'a> RunBuilder<'a> {
pub fn new(cmd: &'a str) -> Self {
Self {
cmd,
die: true, // Default: true
silent: false, // Default: false
async_exec: false, // Default: false
log: false, // Default: false
}
}
pub fn die(mut self, die: bool) -> Self {
self.die = die;
self
}
pub fn silent(mut self, silent: bool) -> Self {
self.silent = silent;
self
}
pub fn async_exec(mut self, async_exec: bool) -> Self {
self.async_exec = async_exec;
self
}
pub fn log(mut self, log: bool) -> Self {
self.log = log;
self
}
pub fn execute(self) -> Result<CommandResult, RunError> {
// Implementation will go here
}
}
```
### 3. Implement the `execute` Method
The `execute` method will:
1. Determine if the command is a script or a single command
2. Handle the `async_exec` option by spawning a process without waiting
3. Handle the `log` option by logging command execution if enabled
4. Handle the `die` option by returning a CommandResult instead of an Err when die=false
5. Use the existing internal functions for the actual execution
### 4. Create a Public Function to Start the Builder
```rust
pub fn run(cmd: &str) -> RunBuilder {
RunBuilder::new(cmd)
}
```
### 5. Update Existing Functions for Backward Compatibility
Update the existing functions to use the new builder pattern internally for backward compatibility.
## Structure Diagram
```mermaid
classDiagram
class RunBuilder {
+String cmd
+bool die
+bool silent
+bool async_exec
+bool log
+new(cmd: &str) RunBuilder
+die(bool) RunBuilder
+silent(bool) RunBuilder
+async_exec(bool) RunBuilder
+log(bool) RunBuilder
+execute() Result<CommandResult, RunError>
}
class CommandResult {
+String stdout
+String stderr
+bool success
+int code
}
RunBuilder ..> CommandResult : produces
note for RunBuilder "Builder pattern implementation\nfor command execution"
```
## Implementation Details
### Handling the `async_exec` Option
When `async_exec` is true, we'll spawn the process but not wait for it to complete. We'll return a CommandResult with:
- Empty stdout and stderr
- success = true (since we don't know the outcome)
- code = 0 (since we don't know the exit code)
### Handling the `log` Option
When `log` is true, we'll log the command execution with a "[LOG]" prefix. For example:
```
[LOG] Executing command: ls -la
```
### Handling the `die` Option
When `die` is false and a command fails, instead of returning an Err, we'll return a CommandResult with:
- success = false
- The appropriate error message in stderr
- code = -1 or the actual exit code if available
## Usage Examples
After implementation, users will be able to use the builder pattern like this:
```rust
// Simple usage with defaults
let result = run("ls -la").execute()?;
// With options
let result = run("ls -la")
.silent(true)
.die(false)
.execute()?;
// Async execution
run("long_running_command")
.async_exec(true)
.execute()?;
// With logging
let result = run("important_command")
.log(true)
.execute()?;
// Script execution
let result = run("echo 'Hello'\necho 'World'")
.silent(true)
.execute()?;
```
## Implementation Steps
1. Add the `RunBuilder` struct and its methods
2. Implement the `execute` method
3. Create the public `run` function
4. Update the existing functions to use the builder pattern internally
5. Add tests for the new functionality
6. Update documentation