Skip to content

Commit 44980e5

Browse files
authored
feat(api): API usability improvements for Shell::invoke_function (reubeno#596)
* Updates function to require supplying custom `ExecutionParameters`, allowing per-invocation redirection. * Adds an example demonstrating how to invoke a function, with optional redirection. * Exports `OpenFiles` and `OpenFile`, which required some cleanup and adding some missing wrappers.
1 parent 00d08fa commit 44980e5

9 files changed

Lines changed: 260 additions & 88 deletions

File tree

brush-core/examples/call-func.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#![allow(missing_docs)]
2+
3+
use anyhow::Result;
4+
5+
async fn instantiate_shell() -> Result<brush_core::Shell> {
6+
let options = brush_core::CreateOptions::default();
7+
let shell = brush_core::Shell::new(&options).await?;
8+
9+
Ok(shell)
10+
}
11+
12+
async fn define_func(shell: &mut brush_core::Shell) -> Result<()> {
13+
let script = r#"hello() { echo "Hello, world: $@"; return 42; }
14+
"#;
15+
16+
let result = shell
17+
.run_string(script, &shell.default_exec_params())
18+
.await?;
19+
20+
eprintln!("[Function definition result: {}]", result.is_success());
21+
22+
Ok(())
23+
}
24+
25+
async fn run_func(shell: &mut brush_core::Shell, suppress_stdout: bool) -> Result<()> {
26+
let mut params = shell.default_exec_params();
27+
28+
if suppress_stdout {
29+
params
30+
.open_files
31+
.set(brush_core::OpenFiles::STDOUT_FD, brush_core::OpenFile::Null);
32+
}
33+
34+
let result = shell
35+
.invoke_function("hello", std::iter::once("arg"), &params)
36+
.await?;
37+
38+
eprintln!("[Function invocation result: {result}]");
39+
40+
Ok(())
41+
}
42+
43+
async fn run(suppress_stdout: bool) -> Result<()> {
44+
let mut shell = instantiate_shell().await?;
45+
46+
define_func(&mut shell).await?;
47+
48+
for (name, _) in shell.funcs.iter() {
49+
eprintln!("[Found function: {name}]");
50+
}
51+
52+
run_func(&mut shell, suppress_stdout).await?;
53+
54+
Ok(())
55+
}
56+
57+
fn main() -> Result<()> {
58+
const SUPPRESS_STDOUT: bool = true;
59+
60+
// Construct a runtime for us to run async code on.
61+
let rt = tokio::runtime::Builder::new_multi_thread()
62+
.enable_all()
63+
.build()
64+
.unwrap();
65+
66+
rt.block_on(run(SUPPRESS_STDOUT))?;
67+
68+
Ok(())
69+
}

brush-core/src/builtins/history.rs

Whitespace-only changes.

brush-core/src/commands.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ pub(crate) fn compose_std_command<S: AsRef<OsStr>>(
225225
}
226226

227227
// Redirect stdin, if applicable.
228-
match open_files.files.remove(&0) {
228+
match open_files.remove(OpenFiles::STDIN_FD) {
229229
Some(OpenFile::Stdin) | None => (),
230230
Some(stdin_file) => {
231231
let as_stdio: Stdio = stdin_file.into();
@@ -234,7 +234,7 @@ pub(crate) fn compose_std_command<S: AsRef<OsStr>>(
234234
}
235235

236236
// Redirect stdout, if applicable.
237-
match open_files.files.remove(&1) {
237+
match open_files.remove(OpenFiles::STDOUT_FD) {
238238
Some(OpenFile::Stdout) | None => (),
239239
Some(stdout_file) => {
240240
let as_stdio: Stdio = stdout_file.into();
@@ -243,7 +243,7 @@ pub(crate) fn compose_std_command<S: AsRef<OsStr>>(
243243
}
244244

245245
// Redirect stderr, if applicable.
246-
match open_files.files.remove(&2) {
246+
match open_files.remove(OpenFiles::STDERR_FD) {
247247
Some(OpenFile::Stderr) | None => {}
248248
Some(stderr_file) => {
249249
let as_stdio: Stdio = stderr_file.into();
@@ -255,7 +255,6 @@ pub(crate) fn compose_std_command<S: AsRef<OsStr>>(
255255
#[cfg(unix)]
256256
{
257257
let fd_mappings = open_files
258-
.files
259258
.into_iter()
260259
.map(|(child_fd, open_file)| FdMapping {
261260
child_fd: i32::try_from(child_fd).unwrap(),
@@ -267,7 +266,7 @@ pub(crate) fn compose_std_command<S: AsRef<OsStr>>(
267266
}
268267
#[cfg(not(unix))]
269268
{
270-
if !open_files.files.is_empty() {
269+
if !open_files.is_empty() {
271270
return error::unimp("fd redirections on non-Unix platform");
272271
}
273272
}
@@ -596,11 +595,8 @@ pub(crate) async fn invoke_command_in_subshell_and_get_output(
596595
params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
597596

598597
// Set up pipe so we can read the output.
599-
let (reader, writer) = sys::pipes::pipe()?;
600-
params
601-
.open_files
602-
.files
603-
.insert(1, openfiles::OpenFile::PipeWriter(writer));
598+
let (reader, writer) = openfiles::pipe()?;
599+
params.open_files.set(OpenFiles::STDOUT_FD, writer.into());
604600

605601
// Run the command.
606602
let result = subshell.run_string(s, &params).await?;
@@ -614,7 +610,7 @@ pub(crate) async fn invoke_command_in_subshell_and_get_output(
614610
shell.last_exit_status = result.exit_code;
615611

616612
// Extract output.
617-
let output_str = std::io::read_to_string(reader)?;
613+
let output_str = std::io::read_to_string(OpenFile::from(reader))?;
618614

619615
Ok(output_str)
620616
}

brush-core/src/completion.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,10 @@ impl Spec {
674674
// handler depth count to suppress any debug traps.
675675
shell.traps.handler_depth += 1;
676676

677-
let invoke_result = shell.invoke_function(function_name, &args).await;
677+
let params = shell.default_exec_params();
678+
let invoke_result = shell
679+
.invoke_function(function_name, args.iter(), &params)
680+
.await;
678681
tracing::debug!(target: trace_categories::COMPLETION, "[completion function '{function_name}' returned: {invoke_result:?}]");
679682

680683
shell.traps.handler_depth -= 1;

brush-core/src/extendedtests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ pub(crate) fn apply_unary_predicate_to_str(
125125
}
126126
ast::UnaryPredicate::FdIsOpenTerminal => {
127127
if let Ok(fd) = operand.parse::<u32>() {
128-
if let Some(open_file) = params.open_files.files.get(&fd) {
128+
if let Some(open_file) = params.open_files.get(fd) {
129129
Ok(open_file.is_term())
130130
} else {
131131
Ok(false)

brush-core/src/interp.rs

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ struct PipelineExecutionContext<'a> {
101101

102102
current_pipeline_index: usize,
103103
pipeline_len: usize,
104-
output_pipes: &'a mut Vec<sys::pipes::PipeReader>,
104+
output_pipes: &'a mut Vec<openfiles::OpenPipeReader>,
105105

106106
process_group_id: Option<i32>,
107107
}
@@ -110,7 +110,7 @@ struct PipelineExecutionContext<'a> {
110110
#[derive(Clone, Default)]
111111
pub struct ExecutionParameters {
112112
/// The open files tracked by the current context.
113-
pub(crate) open_files: openfiles::OpenFiles,
113+
pub open_files: openfiles::OpenFiles,
114114
/// Policy for how to manage spawned external processes.
115115
pub process_group_policy: ProcessGroupPolicy,
116116
}
@@ -134,7 +134,7 @@ impl ExecutionParameters {
134134
/// Returns the file descriptor with the given number.
135135
#[allow(clippy::unwrap_in_result)]
136136
pub(crate) fn fd(&self, fd: u32) -> Option<openfiles::OpenFile> {
137-
self.open_files.files.get(&fd).map(|f| f.try_dup().unwrap())
137+
self.open_files.get(fd).map(|f| f.try_dup().unwrap())
138138
}
139139

140140
pub(crate) fn stdin_file(&self) -> openfiles::OpenFile {
@@ -944,10 +944,7 @@ impl ExecuteInPipeline for ast::SimpleCommand {
944944
let (installed_fd_num, substitution_file) =
945945
setup_process_substitution(context.shell, &params, kind, subshell_command)?;
946946

947-
params
948-
.open_files
949-
.files
950-
.insert(installed_fd_num, substitution_file);
947+
params.open_files.set(installed_fd_num, substitution_file);
951948

952949
args.push(CommandArg::String(std::format!(
953950
"/dev/fd/{installed_fd_num}"
@@ -1373,21 +1370,22 @@ fn setup_pipeline_redirection(
13731370
// Find the stdout from the preceding process.
13741371
if let Some(preceding_output_reader) = context.output_pipes.pop() {
13751372
// Set up stdin of this process to take stdout of the preceding process.
1376-
open_files
1377-
.files
1378-
.insert(0, OpenFile::PipeReader(preceding_output_reader));
1373+
open_files.set(
1374+
OpenFiles::STDIN_FD,
1375+
OpenFile::PipeReader(preceding_output_reader),
1376+
);
13791377
} else {
1380-
open_files.files.insert(0, OpenFile::Null);
1378+
open_files.set(OpenFiles::STDIN_FD, OpenFile::Null);
13811379
}
13821380
}
13831381

13841382
// If this is a non-last command in a multi-command, then we need to arrange to redirect output
13851383
// to a pipe that we can read later.
13861384
if context.pipeline_len > 1 && context.current_pipeline_index < context.pipeline_len - 1 {
13871385
// Set up stdout of this process to go to stdin of the succeeding process.
1388-
let (reader, writer) = sys::pipes::pipe()?;
1386+
let (reader, writer) = openfiles::pipe()?;
13891387
context.output_pipes.push(reader);
1390-
open_files.files.insert(1, OpenFile::PipeWriter(writer));
1388+
open_files.set(OpenFiles::STDOUT_FD, writer.into());
13911389
}
13921390

13931391
Ok(())
@@ -1426,8 +1424,8 @@ pub(crate) async fn setup_redirect(
14261424
let stdout_file = OpenFile::File(opened_file);
14271425
let stderr_file = stdout_file.try_dup()?;
14281426

1429-
params.open_files.files.insert(1, stdout_file);
1430-
params.open_files.files.insert(2, stderr_file);
1427+
params.open_files.set(OpenFiles::STDOUT_FD, stdout_file);
1428+
params.open_files.set(OpenFiles::STDERR_FD, stderr_file);
14311429
}
14321430

14331431
ast::IoRedirect::File(specified_fd_num, kind, target) => {
@@ -1504,7 +1502,7 @@ pub(crate) async fn setup_redirect(
15041502

15051503
let target_file = OpenFile::File(opened_file);
15061504

1507-
params.open_files.files.insert(fd_num, target_file);
1505+
params.open_files.set(fd_num, target_file);
15081506
}
15091507

15101508
ast::IoFileRedirectTarget::Fd(fd) => {
@@ -1518,10 +1516,10 @@ pub(crate) async fn setup_redirect(
15181516

15191517
let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
15201518

1521-
if let Some(f) = params.open_files.files.get(fd) {
1519+
if let Some(f) = params.open_files.get(*fd) {
15221520
let target_file = f.try_dup()?;
15231521

1524-
params.open_files.files.insert(fd_num, target_file);
1522+
params.open_files.set(fd_num, target_file);
15251523
} else {
15261524
return Err(error::Error::BadFileDescriptor(*fd));
15271525
}
@@ -1562,21 +1560,20 @@ pub(crate) async fn setup_redirect(
15621560
.map_err(|_| error::Error::InvalidRedirection)?;
15631561

15641562
// Duplicate the fd.
1565-
let target_file =
1566-
if let Some(f) = params.open_files.files.get(&source_fd_num) {
1567-
f.try_dup()?
1568-
} else {
1569-
return Err(error::Error::BadFileDescriptor(source_fd_num));
1570-
};
1563+
let target_file = if let Some(f) = params.open_files.get(source_fd_num) {
1564+
f.try_dup()?
1565+
} else {
1566+
return Err(error::Error::BadFileDescriptor(source_fd_num));
1567+
};
15711568

1572-
params.open_files.files.insert(fd_num, target_file);
1569+
params.open_files.set(fd_num, target_file);
15731570
} else {
15741571
return Err(error::Error::InvalidRedirection);
15751572
}
15761573

15771574
if dash {
15781575
// Close the specified fd. Ignore it if it's not valid.
1579-
params.open_files.files.remove(&fd_num);
1576+
params.open_files.remove(fd_num);
15801577
}
15811578
}
15821579

@@ -1595,15 +1592,12 @@ pub(crate) async fn setup_redirect(
15951592
)?;
15961593

15971594
let target_file = substitution_file.try_dup()?;
1598-
params
1599-
.open_files
1600-
.files
1601-
.insert(substitution_fd, substitution_file);
1595+
params.open_files.set(substitution_fd, substitution_file);
16021596

16031597
let fd_num = specified_fd_num
16041598
.unwrap_or_else(|| get_default_fd_for_redirect_kind(kind));
16051599

1606-
params.open_files.files.insert(fd_num, target_file);
1600+
params.open_files.set(fd_num, target_file);
16071601
}
16081602
_ => return error::unimp("invalid process substitution"),
16091603
}
@@ -1624,7 +1618,7 @@ pub(crate) async fn setup_redirect(
16241618

16251619
let f = setup_open_file_with_contents(io_here_doc.as_str())?;
16261620

1627-
params.open_files.files.insert(fd_num, f);
1621+
params.open_files.set(fd_num, f);
16281622
}
16291623

16301624
ast::IoRedirect::HereString(fd_num, word) => {
@@ -1636,7 +1630,7 @@ pub(crate) async fn setup_redirect(
16361630

16371631
let f = setup_open_file_with_contents(expanded_word.as_str())?;
16381632

1639-
params.open_files.files.insert(fd_num, f);
1633+
params.open_files.set(fd_num, f);
16401634
}
16411635
}
16421636

@@ -1670,22 +1664,17 @@ fn setup_process_substitution(
16701664
child_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
16711665

16721666
// Set up pipe so we can connect to the command.
1673-
let (reader, writer) = sys::pipes::pipe()?;
1667+
let (reader, writer) = openfiles::pipe()?;
1668+
let (reader, writer) = (reader.into(), writer.into());
16741669

16751670
let target_file = match kind {
16761671
ast::ProcessSubstitutionKind::Read => {
1677-
child_params
1678-
.open_files
1679-
.files
1680-
.insert(1, openfiles::OpenFile::PipeWriter(writer));
1681-
OpenFile::PipeReader(reader)
1672+
child_params.open_files.set(OpenFiles::STDOUT_FD, writer);
1673+
reader
16821674
}
16831675
ast::ProcessSubstitutionKind::Write => {
1684-
child_params
1685-
.open_files
1686-
.files
1687-
.insert(0, openfiles::OpenFile::PipeReader(reader));
1688-
OpenFile::PipeWriter(writer)
1676+
child_params.open_files.set(OpenFiles::STDIN_FD, reader);
1677+
writer
16891678
}
16901679
};
16911680

@@ -1700,7 +1689,7 @@ fn setup_process_substitution(
17001689
// Starting at 63 (a.k.a. 64-1)--and decrementing--look for an
17011690
// available fd.
17021691
let mut candidate_fd_num = 63;
1703-
while params.open_files.files.contains_key(&candidate_fd_num) {
1692+
while params.open_files.contains(candidate_fd_num) {
17041693
candidate_fd_num -= 1;
17051694
if candidate_fd_num == 0 {
17061695
return error::unimp("no available file descriptors");
@@ -1723,5 +1712,7 @@ fn setup_open_file_with_contents(contents: &str) -> Result<OpenFile, error::Erro
17231712
writer.write_all(bytes)?;
17241713
drop(writer);
17251714

1726-
Ok(OpenFile::PipeReader(reader))
1715+
Ok(OpenFile::PipeReader(openfiles::OpenPipeReader::from(
1716+
reader,
1717+
)))
17271718
}

brush-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub use arithmetic::EvalError;
3838
pub use commands::{CommandArg, ExecutionContext};
3939
pub use error::Error;
4040
pub use interp::{ExecutionParameters, ExecutionResult, ProcessGroupPolicy};
41+
pub use openfiles::{OpenFile, OpenFiles, OpenPipeReader, OpenPipeWriter};
4142
pub use shell::{CreateOptions, Shell};
4243
pub use terminal::TerminalControl;
4344
pub use variables::{ShellValue, ShellVariable};

0 commit comments

Comments
 (0)