Skip to content

Commit 818ae17

Browse files
committed
Fix redirects on compound commands and negation handling
Parser fixes: - Add 'redirects' field to compound commands (For, While, Until, If, Case, Select, Group, Subshell) to capture redirects like: 'while read line; do echo $line; done < input.txt' - Fix negation on simple commands: '! cmd' now correctly produces a negated Pipeline, and '! cmd1 && cmd2' correctly shows cmd1 as negated to_bash fixes: - Output redirects after compound command closers (done, fi, esac, etc.) - Handle redirects on Group and Subshell commands Tests: - Add tests/compound_redirects.rs with 16 tests for compound redirects and negation handling - Regenerate all snapshots with new redirects field
1 parent f914d72 commit 818ae17

File tree

59 files changed

+511
-108
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+511
-108
lines changed

src/ast.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ pub enum Command {
4747
#[serde(skip_serializing_if = "Option::is_none")]
4848
words: Option<Vec<String>>,
4949
body: Box<Self>,
50+
#[serde(skip_serializing_if = "Vec::is_empty", default)]
51+
redirects: Vec<Redirect>,
5052
},
5153

5254
/// While loop: `while test; do ...; done`
@@ -55,6 +57,8 @@ pub enum Command {
5557
line: Option<u32>,
5658
test: Box<Self>,
5759
body: Box<Self>,
60+
#[serde(skip_serializing_if = "Vec::is_empty", default)]
61+
redirects: Vec<Redirect>,
5862
},
5963

6064
/// Until loop: `until test; do ...; done`
@@ -63,6 +67,8 @@ pub enum Command {
6367
line: Option<u32>,
6468
test: Box<Self>,
6569
body: Box<Self>,
70+
#[serde(skip_serializing_if = "Vec::is_empty", default)]
71+
redirects: Vec<Redirect>,
6672
},
6773

6874
/// If statement: `if test; then ...; [elif ...; then ...;] [else ...;] fi`
@@ -73,6 +79,8 @@ pub enum Command {
7379
then_branch: Box<Self>,
7480
#[serde(skip_serializing_if = "Option::is_none")]
7581
else_branch: Option<Box<Self>>,
82+
#[serde(skip_serializing_if = "Vec::is_empty", default)]
83+
redirects: Vec<Redirect>,
7684
},
7785

7886
/// Case statement: `case word in pattern) ...;; esac`
@@ -81,6 +89,8 @@ pub enum Command {
8189
line: Option<u32>,
8290
word: String,
8391
clauses: Vec<CaseClause>,
92+
#[serde(skip_serializing_if = "Vec::is_empty", default)]
93+
redirects: Vec<Redirect>,
8494
},
8595

8696
/// Select statement: `select var in list; do ...; done`
@@ -91,20 +101,26 @@ pub enum Command {
91101
#[serde(skip_serializing_if = "Option::is_none")]
92102
words: Option<Vec<String>>,
93103
body: Box<Self>,
104+
#[serde(skip_serializing_if = "Vec::is_empty", default)]
105+
redirects: Vec<Redirect>,
94106
},
95107

96108
/// Brace group: `{ ...; }`
97109
Group {
98110
#[serde(skip_serializing_if = "Option::is_none")]
99111
line: Option<u32>,
100112
body: Box<Self>,
113+
#[serde(skip_serializing_if = "Vec::is_empty", default)]
114+
redirects: Vec<Redirect>,
101115
},
102116

103117
/// Subshell: `( ... )`
104118
Subshell {
105119
#[serde(skip_serializing_if = "Option::is_none")]
106120
line: Option<u32>,
107121
body: Box<Self>,
122+
#[serde(skip_serializing_if = "Vec::is_empty", default)]
123+
redirects: Vec<Redirect>,
108124
},
109125

110126
/// Function definition: `name() { ...; }`

src/convert/mod.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,25 @@ mod convert_impl {
182182
Some(assignments.into_iter().map(|w| w.word).collect())
183183
};
184184

185-
Some(Command::Simple {
185+
let simple_cmd = Command::Simple {
186186
line: line_or_none(eff_line),
187187
words: command_words,
188188
redirects,
189189
assignments,
190-
})
190+
};
191+
192+
// Check if this command is negated with !
193+
// If so, wrap it in a negated pipeline (since ! only applies to pipelines in bash)
194+
let negated = (cmd.flags & CMD_INVERT_RETURN) != 0;
195+
if negated {
196+
Some(Command::Pipeline {
197+
line: None,
198+
commands: vec![simple_cmd],
199+
negated: true,
200+
})
201+
} else {
202+
Some(simple_cmd)
203+
}
191204
}
192205

193206
unsafe fn convert_connection(
@@ -296,12 +309,14 @@ mod convert_impl {
296309
let variable = cstr_to_string((*for_cmd.name).word);
297310
let words = convert_word_list_to_strings(for_cmd.map_list);
298311
let body = convert_command_with_depth(for_cmd.action, depth + 1)?;
312+
let redirects = convert_redirects(cmd.redirects);
299313

300314
Some(Command::For {
301315
line: line_or_none(eff_line),
302316
variable,
303317
words,
304318
body: Box::new(body),
319+
redirects,
305320
})
306321
}
307322

@@ -310,11 +325,13 @@ mod convert_impl {
310325

311326
let test = convert_command_with_depth(while_cmd.test, depth + 1)?;
312327
let body = convert_command_with_depth(while_cmd.action, depth + 1)?;
328+
let redirects = convert_redirects(cmd.redirects);
313329

314330
Some(Command::While {
315331
line: line_or_none(line),
316332
test: Box::new(test),
317333
body: Box::new(body),
334+
redirects,
318335
})
319336
}
320337

@@ -324,11 +341,13 @@ mod convert_impl {
324341

325342
let test = convert_command_with_depth(while_cmd.test, depth + 1)?;
326343
let body = convert_command_with_depth(while_cmd.action, depth + 1)?;
344+
let redirects = convert_redirects(cmd.redirects);
327345

328346
Some(Command::Until {
329347
line: line_or_none(line),
330348
test: Box::new(test),
331349
body: Box::new(body),
350+
redirects,
332351
})
333352
}
334353

@@ -345,12 +364,14 @@ mod convert_impl {
345364
depth + 1,
346365
)?))
347366
};
367+
let redirects = convert_redirects(cmd.redirects);
348368

349369
Some(Command::If {
350370
line: line_or_none(line),
351371
condition: Box::new(condition),
352372
then_branch: Box::new(then_branch),
353373
else_branch,
374+
redirects,
354375
})
355376
}
356377

@@ -360,11 +381,13 @@ mod convert_impl {
360381
let eff_line = effective_line(case_cmd.line, line);
361382
let word = cstr_to_string((*case_cmd.word).word);
362383
let clauses = convert_pattern_list(case_cmd.clauses, depth);
384+
let redirects = convert_redirects(cmd.redirects);
363385

364386
Some(Command::Case {
365387
line: line_or_none(eff_line),
366388
word,
367389
clauses,
390+
redirects,
368391
})
369392
}
370393

@@ -415,34 +438,40 @@ mod convert_impl {
415438
let variable = cstr_to_string((*select_cmd.name).word);
416439
let words = convert_word_list_to_strings(select_cmd.map_list);
417440
let body = convert_command_with_depth(select_cmd.action, depth + 1)?;
441+
let redirects = convert_redirects(cmd.redirects);
418442

419443
Some(Command::Select {
420444
line: line_or_none(eff_line),
421445
variable,
422446
words,
423447
body: Box::new(body),
448+
redirects,
424449
})
425450
}
426451

427452
unsafe fn convert_group(cmd: &ffi::COMMAND, line: u32, depth: usize) -> Option<Command> {
428453
let group_cmd = &*cmd.value.Group;
429454

430455
let body = convert_command_with_depth(group_cmd.command, depth + 1)?;
456+
let redirects = convert_redirects(cmd.redirects);
431457

432458
Some(Command::Group {
433459
line: line_or_none(line),
434460
body: Box::new(body),
461+
redirects,
435462
})
436463
}
437464

438465
unsafe fn convert_subshell(cmd: &ffi::COMMAND, line: u32, depth: usize) -> Option<Command> {
439466
let subshell_cmd = &*cmd.value.Subshell;
440467
let eff_line = effective_line(subshell_cmd.line, line);
441468
let body = convert_command_with_depth(subshell_cmd.command, depth + 1)?;
469+
let redirects = convert_redirects(cmd.redirects);
442470

443471
Some(Command::Subshell {
444472
line: line_or_none(eff_line),
445473
body: Box::new(body),
474+
redirects,
446475
})
447476
}
448477

0 commit comments

Comments
 (0)