Skip to main content

use_stdout/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::io::{self, Write};
5
6/// Commonly used stdout primitives.
7pub mod prelude {
8    pub use crate::{
9        NewlineBehavior, StdoutDestination, apply_newline_behavior, write_line, write_stdout,
10        write_stdout_line, write_text,
11    };
12}
13
14/// Marker type for the process standard output stream.
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
16pub struct StdoutDestination;
17
18impl StdoutDestination {
19    /// Creates a stdout destination marker.
20    #[must_use]
21    pub const fn new() -> Self {
22        Self
23    }
24}
25
26/// Primitive newline policy for text output.
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub enum NewlineBehavior {
29    /// Preserve the input exactly.
30    Preserve,
31    /// Ensure the output ends with `\n`.
32    EnsureTrailingNewline,
33    /// Remove trailing `\r` and `\n` characters.
34    StripTrailingNewline,
35}
36
37/// Applies a newline behavior to text and returns an owned string.
38#[must_use]
39pub fn apply_newline_behavior(text: &str, behavior: NewlineBehavior) -> String {
40    match behavior {
41        NewlineBehavior::Preserve => text.to_owned(),
42        NewlineBehavior::EnsureTrailingNewline => ensure_trailing_newline(text),
43        NewlineBehavior::StripTrailingNewline => text.trim_end_matches(['\r', '\n']).to_owned(),
44    }
45}
46
47/// Writes text to process stdout.
48///
49/// # Errors
50///
51/// Returns any I/O error reported while writing to stdout.
52pub fn write_stdout(text: &str) -> io::Result<()> {
53    let stdout = io::stdout();
54    write_text(stdout.lock(), text)
55}
56
57/// Writes text plus one newline to process stdout.
58///
59/// # Errors
60///
61/// Returns any I/O error reported while writing to stdout.
62pub fn write_stdout_line(text: &str) -> io::Result<()> {
63    let stdout = io::stdout();
64    write_line(stdout.lock(), text)
65}
66
67/// Writes text to a generic writer.
68///
69/// # Errors
70///
71/// Returns any I/O error reported by `writer`.
72pub fn write_text(mut writer: impl Write, text: &str) -> io::Result<()> {
73    writer.write_all(text.as_bytes())
74}
75
76/// Writes text plus one newline to a generic writer.
77///
78/// # Errors
79///
80/// Returns any I/O error reported by `writer`.
81pub fn write_line(mut writer: impl Write, text: &str) -> io::Result<()> {
82    writer.write_all(text.as_bytes())?;
83    writer.write_all(b"\n")
84}
85
86fn ensure_trailing_newline(text: &str) -> String {
87    if text.ends_with('\n') {
88        text.to_owned()
89    } else {
90        let mut output = String::with_capacity(text.len() + 1);
91        output.push_str(text);
92        output.push('\n');
93        output
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::{
100        NewlineBehavior, StdoutDestination, apply_newline_behavior, write_line, write_text,
101    };
102
103    #[test]
104    fn marker_is_copyable() {
105        let destination = StdoutDestination::new();
106        let copied = destination;
107
108        assert_eq!(destination, copied);
109    }
110
111    #[test]
112    fn applies_newline_behavior() {
113        assert_eq!(
114            apply_newline_behavior("ready", NewlineBehavior::Preserve),
115            "ready"
116        );
117        assert_eq!(
118            apply_newline_behavior("ready", NewlineBehavior::EnsureTrailingNewline),
119            "ready\n"
120        );
121        assert_eq!(
122            apply_newline_behavior("ready\r\n", NewlineBehavior::StripTrailingNewline),
123            "ready"
124        );
125    }
126
127    #[test]
128    fn writes_to_generic_writer() -> Result<(), std::io::Error> {
129        let mut buffer = Vec::new();
130        write_text(&mut buffer, "hello")?;
131        write_line(&mut buffer, " world")?;
132
133        assert_eq!(buffer, b"hello world\n");
134        Ok(())
135    }
136}