1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use std::{
7 fs::File,
8 io::{self, BufRead, BufReader, Write},
9 path::PathBuf,
10 process::ExitCode,
11};
12
13#[derive(Parser)]
15#[command(name = "rev", version, about)]
16pub struct Args {
17 #[arg(short = '0', long)]
19 zero: bool,
20
21 files: Vec<PathBuf>,
23}
24
25pub fn run(args: Args) -> ExitCode {
26 let stdout = io::stdout();
27 let mut writer = stdout.lock();
28
29 if args.files.is_empty() {
30 let stdin = io::stdin();
31 let reader = stdin.lock();
32 if let Err(e) = rev(reader, &mut writer, args.zero) {
33 eprintln!("rev: {e}");
34 return ExitCode::FAILURE;
35 }
36 } else {
37 for path in &args.files {
38 let file = match File::open(path) {
39 Ok(f) => f,
40 Err(e) => {
41 eprintln!("rev: {}: {e}", path.display());
42 return ExitCode::FAILURE;
43 }
44 };
45 let reader = BufReader::new(file);
46 if let Err(e) = rev(reader, &mut writer, args.zero) {
47 eprintln!("rev: {}: {e}", path.display());
48 return ExitCode::FAILURE;
49 }
50 }
51 }
52
53 ExitCode::SUCCESS
54}
55
56pub fn rev(
60 mut reader: impl BufRead,
61 writer: &mut impl Write,
62 zero: bool,
63) -> io::Result<()> {
64 let delimiter = if zero { b'\0' } else { b'\n' };
65 let mut buf = Vec::new();
66
67 loop {
68 buf.clear();
69 let bytes_read = reader.read_until(delimiter, &mut buf)?;
70 if bytes_read == 0 {
71 break;
72 }
73
74 let has_delimiter = buf.last() == Some(&delimiter);
75 let line = if has_delimiter {
76 &buf[..buf.len() - 1]
77 } else {
78 &buf
79 };
80
81 let s = String::from_utf8_lossy(line);
82 let reversed: String = s.chars().rev().collect();
83 writer.write_all(reversed.as_bytes())?;
84
85 if has_delimiter {
86 writer.write_all(&[delimiter])?;
87 }
88 }
89
90 Ok(())
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn reverse_simple_lines() {
99 let input = b"hello\nworld\n";
100 let mut output = Vec::new();
101 rev(&input[..], &mut output, false).unwrap();
102 assert_eq!(output, b"olleh\ndlrow\n");
103 }
104
105 #[test]
106 fn reverse_no_trailing_newline() {
107 let input = b"hello\nworld";
108 let mut output = Vec::new();
109 rev(&input[..], &mut output, false).unwrap();
110 assert_eq!(output, b"olleh\ndlrow");
111 }
112
113 #[test]
114 fn reverse_empty_input() {
115 let input = b"";
116 let mut output = Vec::new();
117 rev(&input[..], &mut output, false).unwrap();
118 assert_eq!(output, b"");
119 }
120
121 #[test]
122 fn reverse_empty_lines() {
123 let input = b"\n\n\n";
124 let mut output = Vec::new();
125 rev(&input[..], &mut output, false).unwrap();
126 assert_eq!(output, b"\n\n\n");
127 }
128
129 #[test]
130 fn reverse_zero_terminated() {
131 let input = b"hello\0world\0";
132 let mut output = Vec::new();
133 rev(&input[..], &mut output, true).unwrap();
134 assert_eq!(output, b"olleh\0dlrow\0");
135 }
136
137 #[test]
138 fn reverse_unicode() {
139 let input = "café\n".as_bytes();
140 let mut output = Vec::new();
141 rev(&input[..], &mut output, false).unwrap();
142 assert_eq!(output, "éfac\n".as_bytes());
143 }
144}