Merge nucleic/sleek-thistle-egret-fyej into dev

This commit is contained in:
2026-07-18 12:23:09 -07:00
parent b6be87b72d
commit 5648b96ddc
4 changed files with 54 additions and 5 deletions
+39 -1
View File
@@ -37,6 +37,11 @@ pub(crate) struct ExpanderOptions {
/// Whether to perform pathname expansion (globbing). If disabled, glob patterns
/// are returned as literal strings.
pub pathname_expand: bool,
// nash: whether literal (unquoted, non-expansion) text is also subject to
// field splitting. Off by default per POSIX/bash (only expansion results
// split); enabled by callers whose input string is *data*, e.g. the
// `compgen -W` word list.
pub field_split_literal_text: bool,
}
impl Default for ExpanderOptions {
@@ -46,6 +51,7 @@ impl Default for ExpanderOptions {
brace_expand: true,
execute_command_substitutions: true,
pathname_expand: true,
field_split_literal_text: false,
}
}
}
@@ -333,6 +339,15 @@ impl ExpansionPiece {
Self::LiteralText(s) => Self::Unsplittable(s),
}
}
// nash: opt literal text into field splitting (see
// `ExpanderOptions::field_split_literal_text`). Quoted pieces stay intact.
fn make_literal_splittable(self) -> Self {
match self {
Self::LiteralText(s) => Self::Splittable(s),
other => other,
}
}
}
enum ParameterState {
@@ -532,6 +547,8 @@ struct WordExpander<'a, SE: extensions::ShellExtensions> {
in_double_quotes: bool,
/// Whether to use heredoc expansion semantics (literal quotes, no brace expansion).
heredoc_mode: bool,
// nash: see `ExpanderOptions::field_split_literal_text`.
field_split_literal_text: bool,
}
impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
@@ -547,6 +564,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
disable_pathname_expansion: false,
in_double_quotes: false,
heredoc_mode: false,
field_split_literal_text: false,
}
}
@@ -572,6 +590,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
disable_pathname_expansion: !options.pathname_expand,
in_double_quotes: false,
heredoc_mode: false,
field_split_literal_text: options.field_split_literal_text,
}
}
@@ -802,6 +821,13 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
// Go through the fields we have so far.
for existing_field in expansion.fields {
for piece in existing_field.0 {
// nash: callers whose input is data (e.g. compgen -W) opt
// literal text into splitting.
let piece = if self.field_split_literal_text {
piece.make_literal_splittable()
} else {
piece
};
match piece {
// nash: literal text is never field-split (only expansion
// results are), but stays a distinct piece so its glob
@@ -937,7 +963,19 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
ExpansionPiece::Unsplittable(self.expand_tilde_expression(&tilde_expr)?),
),
brush_parser::word::WordPiece::ParameterExpansion(p) => {
self.expand_parameter_expr(p).await?
// nash: whatever a parameter expansion substitutes (including
// literal text from ${v:-default} / ${v:+alt} words) is an
// expansion *result*, so it is subject to field splitting.
// Quoted pieces inside remain Unsplittable.
let mut expansion = self.expand_parameter_expr(p).await?;
for field in &mut expansion.fields {
for piece in &mut field.0 {
if let ExpansionPiece::LiteralText(s) = piece {
*piece = ExpansionPiece::Splittable(std::mem::take(s));
}
}
}
expansion
}
brush_parser::word::WordPiece::BackquotedCommandSubstitution(s)
| brush_parser::word::WordPiece::CommandSubstitution(s) => {