Repository navigation
Expand file tree
/
Copy pathcustom-builtin.rs
More file actions
152 lines (128 loc) 路 4.87 KB
/
Copy pathcustom-builtin.rs
File metadata and controls
152 lines (128 loc) 路 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! Example of implementing a custom builtin command for a brush-core based shell.
//!
//! This example demonstrates best practices for:
//! - Creating a custom builtin command using the `Command` trait
//! - Defining custom error types with `thiserror`
//! - Parsing command-line arguments with `clap`
//! - Implementing proper error handling and exit code conversion
//! - Using the execution context to interact with shell state and I/O streams
//!
//! Run this example with:
//! ```bash
//! cargo run --package brush-core --example custom-builtin
//! ```
use anyhow::Result;
use clap::Parser;
use std::io::Write;
use brush_core::{ExecutionResult, builtins};
//
// Step 1 (optional): Define a custom error type for your builtin
// ==============================================
// We recommend using `thiserror` to create descriptive error types that can be converted
// to appropriate exit codes.
//
#[derive(Debug, thiserror::Error)]
enum GreetError {
/// The requested repeat count is beyond the supported range.
#[error("repeat count out of range")]
RepeatCountOutOfRange,
/// A shell error occurred during execution; we transparently forward error display
/// to the underlying error.
#[error(transparent)]
ShellError(#[from] brush_core::Error),
/// An I/O error occurred.
#[error("I/O error occurred during greeting: {0}")]
IoError(#[from] std::io::Error),
}
// Mark your error type as a builtin error. This is required to use this error
// type in your command implementation.
impl brush_core::BuiltinError for GreetError {}
// If you define a custom error type, you must map each error variant to an appropriate
// exit code. This ensures the shell interpreter will translate a returned error to
// the appropriate code during execution.
impl From<&GreetError> for brush_core::ExecutionExitCode {
fn from(value: &GreetError) -> Self {
match value {
GreetError::RepeatCountOutOfRange => Self::InvalidUsage,
GreetError::ShellError(e) => e.into(),
GreetError::IoError(_) => Self::GeneralError,
}
}
}
//
// Step 2 (recommended): Define your builtin command arguments
// ==============================================
// We recommend using the `clap` crate and the derive-able `clap::Parser` to define
// command-line arguments and options. This will simplify the work you need to do
// to provide helpful usage information and auto-generated argument validation.
//
/// Greet the user with a friendly message.
#[derive(Parser)]
struct GreetCommand {
/// Number of times to repeat the greeting.
#[arg(short = 'n', long = "repeat", default_value_t = 1)]
repeat_count: usize,
}
//
// Step 3: Implement the Command trait
// ==============================================
// The `Command` trait requires implementing the `execute` method.
//
impl builtins::Command for GreetCommand {
// Specify the error type you will use; this will either be your custom type or
// the default-provided `brush_core::Error` type.
type Error = GreetError;
async fn execute<SE: brush_core::ShellExtensions>(
&self,
context: brush_core::ExecutionContext<'_, SE>,
) -> Result<ExecutionResult, Self::Error> {
// Additional validation.
if self.repeat_count == 0 || self.repeat_count > 10 {
return Err(GreetError::RepeatCountOutOfRange);
}
// For demonstration, we expand a greeting string using shell variable expansion.
// This is a bit contrived, but it shows how to wrap errors coming back from
// `brush_core`.
let greeting = context
.shell
.basic_expand_string(&context.params, "Hello, ${USER}!")
.await?;
// Execute the greeting.
for _ in 0..self.repeat_count {
writeln!(context.stdout(), "{greeting}")?;
}
// Return success
Ok(ExecutionResult::success())
}
}
//
// Step 4: Integrate your builtin into a shell
// ==============================================
// This example shows how to register and use your custom builtin.
//
type SE = brush_core::extensions::DefaultShellExtensions;
async fn run_example() -> Result<()> {
// Create a shell instance with custom builtin registered.
let mut shell = brush_core::Shell::builder()
.builtin("greet", brush_core::builtins::builtin::<GreetCommand, SE>())
.build()
.await?;
// Demonstrate basic usage.
let result = shell
.run_string(
"greet -n 4",
&brush_core::SourceInfo::default(),
&shell.default_exec_params(),
)
.await?;
println!("Exit code: {}\n", u8::from(result.exit_code));
Ok(())
}
fn main() -> Result<()> {
// Construct a `tokio` runtime for async execution
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
rt.block_on(run_example())?;
Ok(())
}