Skip to main content

linuxutils_text/
rev.rs

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/// Reverse lines characterwise.
14#[derive(Parser)]
15#[command(name = "rev", version, about)]
16pub struct Args {
17    /// Use '\0' as the line separator instead of newline.
18    #[arg(short = '0', long)]
19    zero: bool,
20
21    /// Files to read. If none are given, reads from standard input.
22    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
56/// Reverse each line from `reader` and write to `writer`.
57///
58/// If `zero` is true, lines are delimited by '\0' instead of '\n'.
59pub 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}