1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//! mapfile/readarray builtin — read lines from stdin into an array.
//!
//! Mutates arrays via [`BuiltinSideEffect::SetIndexedArray`](super::BuiltinSideEffect).
use async_trait::async_trait;
use super::{Builtin, BuiltinSideEffect, Context};
use crate::error::Result;
use crate::interpreter::ExecResult;
/// `mapfile`/`readarray` builtin — read lines from stdin into an indexed array.
///
/// Usage: mapfile [-t] [ARRAY]
///
/// - `-t` — strip trailing newlines from each line
/// - Default array name is `MAPFILE`
pub struct Mapfile;
#[async_trait]
impl Builtin for Mapfile {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
let mut trim_trailing = false; // -t: strip trailing newlines
let mut array_name = "MAPFILE".to_string();
let mut positional = Vec::new();
for arg in ctx.args {
match arg.as_str() {
"-t" => trim_trailing = true,
a if a.starts_with('-') => {} // skip unknown flags
_ => positional.push(arg.clone()),
}
}
if let Some(name) = positional.first() {
array_name = name.clone();
}
let input = ctx.stdin.unwrap_or("");
let mut result = ExecResult::ok(String::new());
// Always remove existing array first
result
.side_effects
.push(BuiltinSideEffect::RemoveArray(array_name.clone()));
// Split into lines and populate array
if !input.is_empty() {
let entries: Vec<(usize, String)> = input
.lines()
.enumerate()
.map(|(idx, line)| {
let value = if trim_trailing {
line.to_string()
} else {
format!("{}\n", line)
};
(idx, value)
})
.collect();
if !entries.is_empty() {
result
.side_effects
.push(BuiltinSideEffect::SetIndexedArray {
name: array_name,
entries,
});
}
}
Ok(result)
}
}