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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! cat builtin command
use async_trait::async_trait;
use std::path::Path;
use super::{Builtin, Context, read_text_file};
use crate::error::Result;
use crate::interpreter::ExecResult;
/// The cat builtin command.
pub struct Cat;
#[async_trait]
impl Builtin for Cat {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
let mut output = String::new();
let mut show_nonprinting = false;
let mut number_lines = false;
let mut files: Vec<&str> = Vec::new();
// Parse flags
for arg in ctx.args {
if arg.starts_with('-') && arg.len() > 1 && !arg.starts_with("--") {
for ch in arg[1..].chars() {
match ch {
'v' => show_nonprinting = true,
'n' => number_lines = true,
'e' => show_nonprinting = true, // -e implies -v + show $ at EOL (simplified)
't' => show_nonprinting = true, // -t implies -v + show ^I for tabs (simplified)
_ => {}
}
}
} else {
files.push(arg);
}
}
let mut raw = String::new();
if files.is_empty() {
if let Some(stdin) = ctx.stdin {
raw.push_str(stdin);
}
} else {
for file in &files {
if *file == "-" {
if let Some(stdin) = ctx.stdin {
raw.push_str(stdin);
}
} else {
let path = if Path::new(file).is_absolute() {
file.to_string()
} else {
ctx.cwd.join(file).to_string_lossy().to_string()
};
let text = match read_text_file(&*ctx.fs, Path::new(&path), "cat").await {
Ok(t) => t,
Err(e) => return Ok(e),
};
raw.push_str(&text);
}
}
}
if show_nonprinting {
for ch in raw.chars() {
match ch {
'\n' | '\t' => output.push(ch), // pass through newline and tab
c if (c as u32) < 32 => {
// Control characters: ^@, ^A, ..., ^Z, ^[, ^\, ^], ^^, ^_
output.push('^');
output.push((c as u8 + 64) as char);
}
'\x7f' => {
output.push('^');
output.push('?');
}
c => output.push(c),
}
}
} else {
output = raw;
}
if number_lines {
let lines: Vec<&str> = output.split('\n').collect();
let mut numbered = String::new();
for (i, line) in lines.iter().enumerate() {
if i < lines.len() - 1 || !line.is_empty() {
numbered.push_str(&format!(" {}\t{}", i + 1, line));
if i < lines.len() - 1 {
numbered.push('\n');
}
}
}
output = numbered;
}
Ok(ExecResult::ok(output))
}
}