1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::io::{self, Write};
5
6pub 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
16pub struct StdoutDestination;
17
18impl StdoutDestination {
19 #[must_use]
21 pub const fn new() -> Self {
22 Self
23 }
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub enum NewlineBehavior {
29 Preserve,
31 EnsureTrailingNewline,
33 StripTrailingNewline,
35}
36
37#[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
47pub fn write_stdout(text: &str) -> io::Result<()> {
53 let stdout = io::stdout();
54 write_text(stdout.lock(), text)
55}
56
57pub fn write_stdout_line(text: &str) -> io::Result<()> {
63 let stdout = io::stdout();
64 write_line(stdout.lock(), text)
65}
66
67pub fn write_text(mut writer: impl Write, text: &str) -> io::Result<()> {
73 writer.write_all(text.as_bytes())
74}
75
76pub 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}