Merge nucleic/sleek-thistle-egret-fyej into dev
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
//! Example of instantiating a shell and calling a shell function in it.
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
async fn instantiate_shell() -> Result<brush_core::Shell> {
|
||||
let shell = brush_core::Shell::builder().build().await?;
|
||||
Ok(shell)
|
||||
}
|
||||
|
||||
async fn define_func(shell: &mut brush_core::Shell) -> Result<()> {
|
||||
let script = r#"hello() { echo "Hello, world: $@"; return 42; }
|
||||
"#;
|
||||
|
||||
let result = shell
|
||||
.run_string(
|
||||
script,
|
||||
&brush_core::SourceInfo::default(),
|
||||
&shell.default_exec_params(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
eprintln!("[Function definition result: {}]", result.is_success());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_func(shell: &mut brush_core::Shell, suppress_stdout: bool) -> Result<()> {
|
||||
let mut params = shell.default_exec_params();
|
||||
|
||||
if suppress_stdout {
|
||||
params.set_fd(
|
||||
brush_core::openfiles::OpenFiles::STDOUT_FD,
|
||||
brush_core::openfiles::null()?,
|
||||
);
|
||||
}
|
||||
|
||||
let result = shell
|
||||
.invoke_function("hello", std::iter::once("arg"), ¶ms)
|
||||
.await?;
|
||||
|
||||
eprintln!("[Function invocation result: {result}]");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run(suppress_stdout: bool) -> Result<()> {
|
||||
let mut shell = instantiate_shell().await?;
|
||||
|
||||
define_func(&mut shell).await?;
|
||||
|
||||
for (name, _) in shell.funcs().iter() {
|
||||
eprintln!("[Found function: {name}]");
|
||||
}
|
||||
|
||||
run_func(&mut shell, suppress_stdout).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
const SUPPRESS_STDOUT: bool = true;
|
||||
|
||||
// Construct a runtime for us to run async code on.
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
|
||||
rt.block_on(run(SUPPRESS_STDOUT))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,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(())
|
||||
}
|
||||
Reference in New Issue
Block a user