Merge nucleic/sleek-thistle-egret-fyej into dev
This commit is contained in:
+10
-1
@@ -11,7 +11,16 @@ Every fork change is marked with a `// nash:` comment. Current patches:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `brush-shell/src/productinfo.rs` | `PRODUCT_NAME` → `nash`; display string identifies the fork |
|
| `brush-shell/src/productinfo.rs` | `PRODUCT_NAME` → `nash`; display string identifies the fork |
|
||||||
| `brush-shell/src/args.rs` | usage/version strings rebranded |
|
| `brush-shell/src/args.rs` | usage/version strings rebranded |
|
||||||
| `brush-core/src/expansion.rs` | corpus divergence D1: added `ExpansionPiece::LiteralText` — literal unquoted text is never field-split (bash splits only expansion results) but keeps glob characters active. Upstream candidate. |
|
| `brush-core/src/expansion.rs` | corpus divergence D1: added `ExpansionPiece::LiteralText` — literal unquoted text is never field-split (bash splits only expansion results) but keeps glob characters active. Parameter-expansion substitutions (`${v:-word}` etc.) convert back to splittable at the expansion boundary; `ExpanderOptions.field_split_literal_text` lets data-string callers opt in. Upstream candidate. |
|
||||||
|
| `brush-core/src/completion.rs` | `compgen -W` word list opts into `field_split_literal_text` (the -W string is data) |
|
||||||
|
| `brush-shell/tests/cases/compat/ifs.yaml` | removed `known_failure` from 3 IFS tests the D1 fix repairs |
|
||||||
|
|
||||||
|
Verification (this container): brush compat suite `2067 ran: 1684 succeeded, 6
|
||||||
|
failed, 377 known to fail, 38 skipped` — the 6 failures are identical to a
|
||||||
|
pristine `brush-shell-v0.4.0` baseline run (environmental: ANSI-C quote and
|
||||||
|
`echo -e` oracle differences, syntax-error formatting, missing `hexdump`), i.e.
|
||||||
|
zero fork-caused regressions; +3 successes vs baseline are the repaired IFS
|
||||||
|
tests. nash corpus: 94/94.
|
||||||
|
|
||||||
Planned (M1, per docs/NASH.md §4.2): `brush-core` `Gate` trait (verdict-shaped
|
Planned (M1, per docs/NASH.md §4.2): `brush-core` `Gate` trait (verdict-shaped
|
||||||
observation hooks) — the only substantial divergence from upstream.
|
observation hooks) — the only substantial divergence from upstream.
|
||||||
|
|||||||
@@ -308,6 +308,8 @@ impl Spec {
|
|||||||
// and field splitting but NOT pathname expansion (globbing).
|
// and field splitting but NOT pathname expansion (globbing).
|
||||||
let options = crate::expansion::ExpanderOptions {
|
let options = crate::expansion::ExpanderOptions {
|
||||||
pathname_expand: false,
|
pathname_expand: false,
|
||||||
|
// nash: the -W string is data — its literal text is split too.
|
||||||
|
field_split_literal_text: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let words = crate::expansion::full_expand_and_split_word_with_options(
|
let words = crate::expansion::full_expand_and_split_word_with_options(
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ pub(crate) struct ExpanderOptions {
|
|||||||
/// Whether to perform pathname expansion (globbing). If disabled, glob patterns
|
/// Whether to perform pathname expansion (globbing). If disabled, glob patterns
|
||||||
/// are returned as literal strings.
|
/// are returned as literal strings.
|
||||||
pub pathname_expand: bool,
|
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 {
|
impl Default for ExpanderOptions {
|
||||||
@@ -46,6 +51,7 @@ impl Default for ExpanderOptions {
|
|||||||
brace_expand: true,
|
brace_expand: true,
|
||||||
execute_command_substitutions: true,
|
execute_command_substitutions: true,
|
||||||
pathname_expand: true,
|
pathname_expand: true,
|
||||||
|
field_split_literal_text: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,6 +339,15 @@ impl ExpansionPiece {
|
|||||||
Self::LiteralText(s) => Self::Unsplittable(s),
|
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 {
|
enum ParameterState {
|
||||||
@@ -532,6 +547,8 @@ struct WordExpander<'a, SE: extensions::ShellExtensions> {
|
|||||||
in_double_quotes: bool,
|
in_double_quotes: bool,
|
||||||
/// Whether to use heredoc expansion semantics (literal quotes, no brace expansion).
|
/// Whether to use heredoc expansion semantics (literal quotes, no brace expansion).
|
||||||
heredoc_mode: bool,
|
heredoc_mode: bool,
|
||||||
|
// nash: see `ExpanderOptions::field_split_literal_text`.
|
||||||
|
field_split_literal_text: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
|
impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
|
||||||
@@ -547,6 +564,7 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
|
|||||||
disable_pathname_expansion: false,
|
disable_pathname_expansion: false,
|
||||||
in_double_quotes: false,
|
in_double_quotes: false,
|
||||||
heredoc_mode: 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,
|
disable_pathname_expansion: !options.pathname_expand,
|
||||||
in_double_quotes: false,
|
in_double_quotes: false,
|
||||||
heredoc_mode: 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.
|
// Go through the fields we have so far.
|
||||||
for existing_field in expansion.fields {
|
for existing_field in expansion.fields {
|
||||||
for piece in existing_field.0 {
|
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 {
|
match piece {
|
||||||
// nash: literal text is never field-split (only expansion
|
// nash: literal text is never field-split (only expansion
|
||||||
// results are), but stays a distinct piece so its glob
|
// 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)?),
|
ExpansionPiece::Unsplittable(self.expand_tilde_expression(&tilde_expr)?),
|
||||||
),
|
),
|
||||||
brush_parser::word::WordPiece::ParameterExpansion(p) => {
|
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::BackquotedCommandSubstitution(s)
|
||||||
| brush_parser::word::WordPiece::CommandSubstitution(s) => {
|
| brush_parser::word::WordPiece::CommandSubstitution(s) => {
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ cases:
|
|||||||
echo "After: [$IFS]"
|
echo "After: [$IFS]"
|
||||||
|
|
||||||
- name: "IFS does not affect for loop literal words"
|
- name: "IFS does not affect for loop literal words"
|
||||||
known_failure: true
|
# nash: fixed by ExpansionPiece::LiteralText (literal words are not field-split)
|
||||||
stdin: |
|
stdin: |
|
||||||
IFS=':'
|
IFS=':'
|
||||||
for word in a:b:c; do
|
for word in a:b:c; do
|
||||||
@@ -249,7 +249,7 @@ cases:
|
|||||||
echo "Quoted: $var"
|
echo "Quoted: $var"
|
||||||
|
|
||||||
- name: "IFS with partially quoted expansion"
|
- name: "IFS with partially quoted expansion"
|
||||||
known_failure: true
|
# nash: fixed by ExpansionPiece::LiteralText (literal words are not field-split)
|
||||||
stdin: |
|
stdin: |
|
||||||
IFS=':'
|
IFS=':'
|
||||||
var="a:b:c"
|
var="a:b:c"
|
||||||
@@ -309,7 +309,7 @@ cases:
|
|||||||
echo "[$1]"
|
echo "[$1]"
|
||||||
|
|
||||||
- name: "IFS with glob pattern literal"
|
- name: "IFS with glob pattern literal"
|
||||||
known_failure: true
|
# nash: fixed by ExpansionPiece::LiteralText (literal words are not field-split)
|
||||||
stdin: |
|
stdin: |
|
||||||
IFS=':'
|
IFS=':'
|
||||||
# Glob patterns as literals shouldn't split
|
# Glob patterns as literals shouldn't split
|
||||||
|
|||||||
Reference in New Issue
Block a user