Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
30fe806
chore(bind): use match pre-conditions
Elsie19 May 14, 2026
9908f97
chore(command): return Option<&str> instead of Option<&String>
Elsie19 May 14, 2026
b254028
chore(complete): push actions in one go
Elsie19 May 14, 2026
d116e3a
chore(complete): prealloc hashmap capacity
Elsie19 May 14, 2026
340d069
chore(complete): take in generic over items instead of HashMap reference
Elsie19 May 14, 2026
a97a121
chore(history): return Path instead of PathBuf
Elsie19 May 14, 2026
cd8b3af
chore(expansion): prealloc minimal length of Vec
Elsie19 May 14, 2026
8e91904
chore(interp): prealloc length of Vec, use Cow when possible, use sli…
Elsie19 May 14, 2026
c5e0036
chore(jobs): prealloc Vec capacity
Elsie19 May 14, 2026
ff7007d
chore(shell): return iterator over &str instead of Vec
Elsie19 May 14, 2026
f8e192c
chore(funcs/interactive_shell): reduce cloning
Elsie19 May 14, 2026
792eafc
chore(completion): replace useless Vec with array
Elsie19 May 14, 2026
11346ce
chore(jobs/config): use Cow when possible
Elsie19 May 14, 2026
6d214e7
chore(brush-builtins): remove unused dependencies
Elsie19 May 14, 2026
015a2df
chore(read): constant size known in VecDeque
Elsie19 May 14, 2026
3fbce06
chore(completion): remove premature collect
Elsie19 May 14, 2026
bbdeb4f
chore(events): use static array instead of Vec
Elsie19 May 14, 2026
febb2c8
chore(entry): remove useless clones
Elsie19 May 16, 2026
a287995
chore(entry): slice instead of vec
Elsie19 May 16, 2026
4396e30
chore(interp): delay construction of vec when possible
Elsie19 May 16, 2026
76748b5
chore(xtask): use `default_value_t` when possible
Elsie19 May 16, 2026
a69dbb4
chore(expansion): deref instead of owning
Elsie19 May 16, 2026
c26f86a
chore(expansion): do not clone automatically in expansion
Elsie19 May 16, 2026
d8d0092
chore(expansion): delay possible owning
Elsie19 May 16, 2026
a318651
chore(expansion): extend from iterator instead of string
Elsie19 May 16, 2026
3093a91
feat(expansion): closures instead of dedicated functions for transforms
Elsie19 May 17, 2026
baeaff7
chore(*): use map_or instead of map_or_else for small static values
Elsie19 May 17, 2026
d007fb9
style: cargo format
Elsie19 May 17, 2026
6a79f04
chore(expansion): with_capacity for expansion vec
Elsie19 May 18, 2026
c04947c
chore(trap): with_capacity for signals vec
Elsie19 May 18, 2026
560ac9c
chore(builtins): with_capacity and extend for Command::new
Elsie19 May 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions brush-builtins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,8 @@ cfg-if = "1.0.4"
chrono = "0.4.44"
clap = { version = "4.6.0", features = ["derive", "wrap_help"] }
fancy-regex = "0.18.0"
futures = "0.3.32"
itertools = "0.14.0"
strum = "0.28.0"
strum_macros = "0.28.0"
thiserror = "2.0.18"
tracing = "0.1.44"

Expand Down
13 changes: 7 additions & 6 deletions brush-builtins/src/bind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,22 +461,23 @@ fn display_funcs_and_bindings(
let sorted_funcs = interfaces::InputFunction::iter().sorted_by_key(|f| f.to_string());

for func in sorted_funcs {
if let Some(seqs) = sequences_by_func.get(&func) {
if reusable {
match sequences_by_func.get(&func) {
Some(seqs) if reusable => {
for seq in seqs {
writeln!(context.stdout(), "\"{seq}\": {func}")?;
}
} else {
}
Some(seqs) => {
writeln!(
context.stdout(),
"{func} can be found on {}.",
seqs.iter().map(|seq| std::format!("\"{seq}\"")).join(", ")
)?;
}
} else {
if reusable {
None if reusable => {
writeln!(context.stdout(), "# {func} (not bound)")?;
} else {
}
None => {
writeln!(context.stdout(), "{func} is not bound to any keys")?;
}
}
Expand Down
17 changes: 9 additions & 8 deletions brush-builtins/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ pub(crate) struct CommandCommand {
}

impl CommandCommand {
fn command(&self) -> Option<&String> {
self.command_and_args.first()
fn command(&self) -> Option<&str> {
self.command_and_args.first().map(|s| s.as_str())
}
}

Expand All @@ -42,11 +42,9 @@ impl builtins::Command for CommandCommand {
// Silently exit if no command was provided.
if let Some(command_name) = self.command() {
if self.print_description || self.print_verbose_description {
if let Some(found_cmd) = Self::try_find_command(
context.shell,
command_name.as_str(),
self.use_default_path,
) {
if let Some(found_cmd) =
Self::try_find_command(context.shell, command_name, self.use_default_path)
{
if self.print_description {
writeln!(context.stdout(), "{found_cmd}")?;
} else {
Expand Down Expand Up @@ -134,7 +132,10 @@ impl CommandCommand {
use_default_path: bool,
) -> Result<ExecutionResult, brush_core::Error> {
command_name.clone_into(&mut context.command_name);
let command_and_args = self.command_and_args.iter().map(|arg| arg.into()).collect();
let command_and_args = self
.command_and_args
.iter()
.map(brush_core::CommandArg::from);

let path_dirs = if use_default_path {
Some(sys::fs::get_default_standard_utils_paths())
Expand Down
70 changes: 28 additions & 42 deletions brush-builtins/src/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,42 +148,24 @@ impl CommonCompleteCommandArgs {
fn resolve_actions(&self) -> Vec<CompleteAction> {
let mut actions = self.actions.clone();

if self.action_alias {
actions.push(CompleteAction::Alias);
}
if self.action_builtin {
actions.push(CompleteAction::Builtin);
}
if self.action_command {
actions.push(CompleteAction::Command);
}
if self.action_directory {
actions.push(CompleteAction::Directory);
}
if self.action_exported {
actions.push(CompleteAction::Export);
}
if self.action_file {
actions.push(CompleteAction::File);
}
if self.action_group {
actions.push(CompleteAction::Group);
}
if self.action_job {
actions.push(CompleteAction::Job);
}
if self.action_keyword {
actions.push(CompleteAction::Keyword);
}
if self.action_service {
actions.push(CompleteAction::Service);
}
if self.action_user {
actions.push(CompleteAction::User);
}
if self.action_variable {
actions.push(CompleteAction::Variable);
}
actions.extend(
[
(self.action_alias, CompleteAction::Alias),
(self.action_builtin, CompleteAction::Builtin),
(self.action_command, CompleteAction::Command),
(self.action_directory, CompleteAction::Directory),
(self.action_exported, CompleteAction::Export),
(self.action_file, CompleteAction::File),
(self.action_group, CompleteAction::Group),
(self.action_job, CompleteAction::Job),
(self.action_keyword, CompleteAction::Keyword),
(self.action_service, CompleteAction::Service),
(self.action_user, CompleteAction::User),
(self.action_variable, CompleteAction::Variable),
]
.into_iter()
.filter_map(|(enabled, action)| enabled.then_some(action)),
);

actions
}
Expand Down Expand Up @@ -575,7 +557,8 @@ impl builtins::Command for CompOptCommand {
&self,
context: brush_core::ExecutionContext<'_, SE>,
) -> Result<brush_core::ExecutionResult, Self::Error> {
let mut options = HashMap::new();
let mut options =
HashMap::with_capacity(self.disabled_options.len() + self.enabled_options.len());
for option in &self.disabled_options {
options.insert(option.clone(), false);
}
Expand Down Expand Up @@ -637,14 +620,17 @@ impl builtins::Command for CompOptCommand {
}

impl CompOptCommand {
fn set_options_for_spec(spec: &mut Spec, options: &HashMap<CompleteOption, bool>) {
fn set_options_for_spec<'a, I>(spec: &mut Spec, options: I)
where
I: IntoIterator<Item = (&'a CompleteOption, &'a bool)>,
{
Self::set_options(&mut spec.options, options);
}

fn set_options(
target_options: &mut completion::GenerationOptions,
options: &HashMap<CompleteOption, bool>,
) {
fn set_options<'a, I>(target_options: &mut completion::GenerationOptions, options: I)
where
I: IntoIterator<Item = (&'a CompleteOption, &'a bool)>,
{
for (option, value) in options {
match option {
CompleteOption::BashDefault => target_options.bash_default = *value,
Expand Down
32 changes: 16 additions & 16 deletions brush-builtins/src/history.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use brush_core::{ExecutionExitCode, ExecutionResult, builtins, error, history};
use clap::Parser;
use std::{io::Write, path::PathBuf};
use std::{
io::Write,
path::{Path, PathBuf},
};

/// Query or manipulate the shell's command history.
// TODO(history): Evaluate which of the options conflict with each other.
Expand Down Expand Up @@ -67,7 +70,7 @@ impl builtins::Command for HistoryCommand {
let stderr = context.stderr();

if let Some(history) = context.shell.history_mut() {
self.execute_with_history(history, config, stdout, stderr)
self.execute_with_history(history, &config, stdout, stderr)
} else {
Err(brush_core::ErrorKind::HistoryNotEnabled.into())
}
Expand All @@ -81,7 +84,7 @@ impl HistoryCommand {
fn execute_with_history(
&self,
history: &mut history::History,
config: HistoryConfig,
config: &HistoryConfig,
stdout: impl Write,
mut stderr: impl Write,
) -> Result<ExecutionResult, brush_core::Error> {
Expand Down Expand Up @@ -118,8 +121,8 @@ impl HistoryCommand {

if let Some(append_option) = &self.append_session_to_file {
if let Some(file_path) = get_effective_history_file_path(
config.default_history_file_path,
append_option.as_ref(),
config.default_history_file_path.as_deref(),
append_option.as_deref(),
) {
history.flush(
file_path,
Expand All @@ -142,8 +145,8 @@ impl HistoryCommand {

if let Some(write_option) = &self.write_session_to_file {
if let Some(file_path) = get_effective_history_file_path(
config.default_history_file_path,
write_option.as_ref(),
config.default_history_file_path.as_deref(),
write_option.as_deref(),
) {
history.flush(
file_path,
Expand Down Expand Up @@ -171,7 +174,7 @@ impl HistoryCommand {
None
};

display_history(history, &config, max_entries, stdout, stderr)?;
display_history(history, config, max_entries, stdout, stderr)?;

Ok(ExecutionResult::success())
}
Expand Down Expand Up @@ -211,14 +214,11 @@ fn display_history(
Ok(())
}

fn get_effective_history_file_path(
default_history_file_path: Option<PathBuf>,
option: Option<&String>,
) -> Option<PathBuf> {
option.map_or_else(
|| default_history_file_path,
|file_path| Some(PathBuf::from(file_path)),
)
fn get_effective_history_file_path<'a>(
default_history_file_path: Option<&'a Path>,
option: Option<&'a str>,
) -> Option<&'a Path> {
option.map(Path::new).or(default_history_file_path)
}

#[cfg(test)]
Expand Down
4 changes: 1 addition & 3 deletions brush-builtins/src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,7 @@ fn build_variable_fields(
match input_line {
Some(line) if skip_ifs_splitting => {
// With -N, don't split - put entire input in first variable.
let mut fields = VecDeque::new();
fields.push_back(line.to_string());
fields
VecDeque::from([line.to_string()])
}
Some(line) => split_line_by_ifs(ifs, line, Some(num_variables)),
None => VecDeque::new(),
Expand Down
2 changes: 1 addition & 1 deletion brush-builtins/src/trap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl builtins::Command for TrapCommand {
} else {
let handler = &self.args[0];

let mut signal_types = vec![];
let mut signal_types = Vec::with_capacity(self.args.len() - 1);
for signal in &self.args[1..] {
signal_types.push(signal.parse()?);
}
Expand Down
2 changes: 1 addition & 1 deletion brush-core/examples/call-func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async fn run_func(shell: &mut brush_core::Shell, suppress_stdout: bool) -> Resul
}

let result = shell
.invoke_function("hello", std::iter::once("arg"), &params)
.invoke_function("hello", std::iter::once("arg"), params)
.await?;

eprintln!("[Function invocation result: {result}]");
Expand Down
10 changes: 6 additions & 4 deletions brush-core/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,16 @@ pub trait Command: clap::Parser {
if !Self::takes_plus_options() {
Self::try_parse_from(args)
} else {
let args = args.into_iter();

let (lower, _) = args.size_hint();

// N.B. clap doesn't support named options like '+x'. To work around this, we
// establish a pattern of renaming them.
let mut updated_args = vec![];
let mut updated_args = Vec::with_capacity(lower);
for arg in args {
if let Some(plus_options) = arg.strip_prefix("+") {
for c in plus_options.chars() {
updated_args.push(format!("--+{c}"));
}
updated_args.extend(plus_options.chars().map(|c| format!("--+{c}")));
} else {
updated_args.push(arg);
}
Expand Down
11 changes: 7 additions & 4 deletions brush-core/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,17 +324,20 @@ impl<'a, SE: extensions::ShellExtensions> SimpleCommand<'a, SE> {
/// * `params` - The execution parameters for the command.
/// * `command_name` - The name of the command to execute.
/// * `args` - The arguments to the command, including the command itself.
pub const fn new(
pub fn new<I>(
shell: ShellForCommand<'a, SE>,
params: ExecutionParameters,
command_name: String,
args: Vec<CommandArg>,
) -> Self {
args: I,
) -> Self
where
I: IntoIterator<Item = CommandArg>,
{
Self {
shell,
params,
command_name,
args,
args: args.into_iter().collect(),
use_functions: true,
path_dirs: None,
process_group_id: None,
Expand Down
Loading
Loading