diff --git a/CHANGELOG.md b/CHANGELOG.md index 71a6060..5934227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +## v0.2.0 - 2024/06/06 + +- Added: `Print` struct. `Output` is now deprecated, use `Print` instead (https://github.com/schneems/bullet_stream/pull/5) +- Fix: Missing `must_use` attributes (https://github.com/schneems/bullet_stream/pull/5) + ## v0.1.1 - 2024/06/03 - Fix double newlines for headers (https://github.com/schneems/bullet_stream/pull/2) diff --git a/Cargo.lock b/Cargo.lock index ecbac8e..7a91473 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,7 +54,7 @@ dependencies = [ [[package]] name = "bullet_stream" -version = "0.1.1" +version = "0.2.0" dependencies = [ "ascii_table", "fun_run", diff --git a/Cargo.toml b/Cargo.toml index 5558bb8..60fdf43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bullet_stream" -version = "0.1.1" +version = "0.2.0" edition = "2021" license = "MIT" description = "Bulletproof printing for bullet point text" diff --git a/README.md b/README.md index f3091a6..e251e0b 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,13 @@ Add bullet_stream to your project: $ cargo add bullet_stream ``` -Now use [`Output`] to output structured text as a script/buildpack executes. The output +Now use [`Print`] to output structured text as a script/buildpack executes. The output is intended to be read by the end user. ```rust -use bullet_stream::Output; +use bullet_stream::Print; -let mut output = Output::new(std::io::stdout()) +let mut output = Print::new(std::io::stdout()) .h2("Example Buildpack") .warning("No Gemfile.lock found"); diff --git a/examples/style_guide.rs b/examples/style_guide.rs index fdc6e81..399039c 100644 --- a/examples/style_guide.rs +++ b/examples/style_guide.rs @@ -1,7 +1,6 @@ -// use commons::output::fmt::{self, DEBUG_INFO, HELP}; use ascii_table::AsciiTable; #[allow(clippy::wildcard_imports)] -use bullet_stream::{style, Output}; +use bullet_stream::{style, Print}; use fun_run::CommandWithName; use indoc::formatdoc; use std::io::stdout; @@ -10,7 +9,7 @@ use std::process::Command; #[allow(clippy::too_many_lines)] fn main() { { - let mut log = Output::new(stdout()).h1("Living build output style guide"); + let mut log = Print::new(stdout()).h1("Living build output style guide"); log = log.h2("Bullet section features"); log = log .bullet("Bullet example") @@ -66,7 +65,7 @@ fn main() { #[allow(clippy::unwrap_used)] let cmd_error = Command::new("iDoNotExist").named_output().err().unwrap(); - let mut log = Output::new(stdout()).h2("Error and warnings"); + let mut log = Print::new(stdout()).h2("Error and warnings"); log = log .bullet("Debug information") .sub_bullet("Should go above errors in section/step format") @@ -106,7 +105,7 @@ fn main() { } { - let log = Output::new(stdout()).h2("Formatting helpers"); + let log = Print::new(stdout()).h2("Formatting helpers"); log.bullet("The fmt module") .sub_bullet(formatdoc! {" Formatting helpers can be used to enhance log output: diff --git a/src/lib.rs b/src/lib.rs index 2fc7e19..6918c58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,13 +16,13 @@ pub mod style; mod util; mod write; -/// Use [`Output`] to output structured text as a buildpack/script executes. The output +/// Use [`Print`] to output structured text as a buildpack/script executes. The output /// is intended to be read by the application user. /// /// ```rust -/// use bullet_stream::Output; +/// use bullet_stream::Print; /// -/// let mut output = Output::new(std::io::stdout()) +/// let mut output = Print::new(std::io::stdout()) /// .h2("Example Buildpack") /// .warning("No Gemfile.lock found"); /// @@ -34,14 +34,20 @@ mod write; /// ``` #[allow(clippy::module_name_repetitions)] #[derive(Debug)] -pub struct Output { +pub struct Print { pub(crate) started: Option, pub(crate) state: T, } -/// Various states for [`Output`] to contain. +#[deprecated( + since = "0.2.0", + note = "bullet_stream::Output conflicts with std::io::Output, prefer Print" +)] +pub type Output = Print; + +/// Various states for [`Print`] to contain. /// -/// The [`Output`] struct acts as an output state machine. These structs +/// The [`Print`] struct acts as an output state machine. These structs /// represent the various states. See struct documentation for more details. pub mod state { use crate::background_printer::PrintGuard; @@ -62,15 +68,15 @@ pub mod state { /// Example: /// /// ```rust - /// use bullet_stream::{Output, state::{Bullet, Header}}; + /// use bullet_stream::{Print, state::{Bullet, Header}}; /// use std::io::Write; /// - /// let mut not_started = Output::new(std::io::stdout()); + /// let mut not_started = Print::new(std::io::stdout()); /// let output = start_buildpack(not_started); /// /// output.bullet("Ruby version").sub_bullet("Installing Ruby").done(); /// - /// fn start_buildpack(mut output: Output>) -> Output> + /// fn start_buildpack(mut output: Print>) -> Print> /// where W: Write + Send + Sync + 'static { /// output.h2("Example Buildpack") ///} @@ -87,15 +93,15 @@ pub mod state { /// Example: /// /// ```rust - /// use bullet_stream::{Output, state::{Bullet, Header, SubBullet}}; + /// use bullet_stream::{Print, state::{Bullet, Header, SubBullet}}; /// use std::io::Write; /// - /// let mut output = Output::new(std::io::stdout()) + /// let mut output = Print::new(std::io::stdout()) /// .h2("Example Buildpack"); /// /// output = install_ruby(output).done(); /// - /// fn install_ruby(mut output: Output>) -> Output> + /// fn install_ruby(mut output: Print>) -> Print> /// where W: Write + Send + Sync + 'static { /// let out = output.bullet("Ruby version") /// .sub_bullet("Installing Ruby"); @@ -117,16 +123,16 @@ pub mod state { /// Example: /// /// ```rust - /// use bullet_stream::{Output, state::{Bullet, SubBullet}}; + /// use bullet_stream::{Print, state::{Bullet, SubBullet}}; /// use std::io::Write; /// - /// let mut output = Output::new(std::io::stdout()) + /// let mut output = Print::new(std::io::stdout()) /// .h2("Example Buildpack") /// .bullet("Ruby version"); /// /// install_ruby(output).done(); /// - /// fn install_ruby(mut output: Output>) -> Output> + /// fn install_ruby(mut output: Print>) -> Print> /// where W: Write + Send + Sync + 'static { /// let output = output.sub_bullet("Installing Ruby"); /// // ... @@ -142,20 +148,20 @@ pub mod state { /// This state is intended for streaming output from a process to the end user. It is /// started from a `state::SubBullet` and finished back to a `state::SubBullet`. /// - /// The `Output>` implements [`std::io::Write`], so you can stream + /// The `Print>` implements [`std::io::Write`], so you can stream /// from anything that accepts a [`std::io::Write`]. /// /// ```rust - /// use bullet_stream::{Output, state::{Bullet, SubBullet}}; + /// use bullet_stream::{Print, state::{Bullet, SubBullet}}; /// use std::io::Write; /// - /// let mut output = Output::new(std::io::stdout()) + /// let mut output = Print::new(std::io::stdout()) /// .h2("Example Buildpack") /// .bullet("Ruby version"); /// /// install_ruby(output).done(); /// - /// fn install_ruby(mut output: Output>) -> Output> + /// fn install_ruby(mut output: Print>) -> Print> /// where W: Write + Send + Sync + 'static { /// let mut stream = output.sub_bullet("Installing Ruby") /// .start_stream("Streaming stuff"); @@ -177,16 +183,16 @@ pub mod state { /// This state is started from a [`SubBullet`] and finished back to a [`SubBullet`]. /// /// ```rust - /// use bullet_stream::{Output, state::{Bullet, SubBullet}}; + /// use bullet_stream::{Print, state::{Bullet, SubBullet}}; /// use std::io::Write; /// - /// let mut output = Output::new(std::io::stdout()) + /// let mut output = Print::new(std::io::stdout()) /// .h2("Example Buildpack") /// .bullet("Ruby version"); /// /// install_ruby(output).done(); /// - /// fn install_ruby(mut output: Output>) -> Output> + /// fn install_ruby(mut output: Print>) -> Print> /// where W: Write + Send + Sync + 'static { /// let mut timer = output.sub_bullet("Installing Ruby") /// .start_timer("Installing"); @@ -236,7 +242,7 @@ where /// Used for announcements such as warning and error states #[allow(private_bounds)] -impl Output +impl Print where S: AnnounceSupportedState, { @@ -256,7 +262,8 @@ where /// you are certain that the error is transient. /// /// If you detect something problematic but not bad enough to halt buildpack execution, consider - /// using a [`Output::warning`] instead. + /// using a [`Print::warning`] instead. + /// pub fn error(mut self, s: impl AsRef) { self.write_paragraph(&ANSI::Red, s); } @@ -274,12 +281,12 @@ where /// Warnings should often come with some disabling mechanism, if possible. If the user can turn /// off the warning, that information should be included in the warning message. If you're /// confident that the user should not be able to turn off a warning, consider using a - /// [`Output::error`] instead. + /// [`Print::error`] instead. /// /// Warnings will be output in a multi-line paragraph style. A warning can be emitted from any /// state except for [`state::Header`]. #[must_use] - pub fn warning(mut self, s: impl AsRef) -> Output { + pub fn warning(mut self, s: impl AsRef) -> Print { self.write_paragraph(&ANSI::Yellow, s); self } @@ -293,9 +300,9 @@ where /// /// Important messages should be used sparingly and only for things the user should be aware of /// but not necessarily act on. If the message is actionable, consider using a - /// [`Output::warning`] instead. + /// [`Print::warning`] instead. #[must_use] - pub fn important(mut self, s: impl AsRef) -> Output { + pub fn important(mut self, s: impl AsRef) -> Print { self.write_paragraph(&ANSI::BoldCyan, s); self } @@ -328,13 +335,13 @@ where } } -impl Output> +impl Print> where W: Write, { /// Create a buildpack output struct, but do not announce the buildpack's start. /// - /// See the [`Output::h1`] and [`Output::h2`] methods for more details. + /// See the [`Print::h1`] and [`Print::h2`] methods for more details. #[must_use] pub fn new(io: W) -> Self { Self { @@ -358,7 +365,7 @@ where /// /// This function will transition your buildpack output to [`state::Bullet`]. #[must_use] - pub fn h1(mut self, buildpack_name: impl AsRef) -> Output> { + pub fn h1(mut self, buildpack_name: impl AsRef) -> Print> { writeln_now( &mut self.state.write, ansi_escape::wrap_ansi_escape_each_line( @@ -383,7 +390,7 @@ where /// /// This function will transition your buildpack output to [`state::Bullet`]. #[must_use] - pub fn h2(mut self, buildpack_name: impl AsRef) -> Output> { + pub fn h2(mut self, buildpack_name: impl AsRef) -> Print> { if !self.state.write.was_paragraph { writeln_now(&mut self.state.write, ""); } @@ -401,8 +408,8 @@ where /// Start a buildpack output without announcing the name. #[must_use] - pub fn without_header(self) -> Output> { - Output { + pub fn without_header(self) -> Print> { + Print { started: Some(Instant::now()), state: state::Bullet { write: self.state.write, @@ -411,7 +418,7 @@ where } } -impl Output> +impl Print> where W: Write + Send + Sync + 'static, { @@ -433,10 +440,10 @@ where /// /// This function will transition your buildpack output to [`state::SubBullet`]. #[must_use] - pub fn bullet(mut self, s: impl AsRef) -> Output> { + pub fn bullet(mut self, s: impl AsRef) -> Print> { writeln_now(&mut self.state.write, Self::style(s)); - Output { + Print { started: self.started, state: state::SubBullet { write: self.state.write, @@ -446,7 +453,7 @@ where /// Outputs an H2 header #[must_use] - pub fn h2(mut self, buildpack_name: impl AsRef) -> Output> { + pub fn h2(mut self, buildpack_name: impl AsRef) -> Print> { if !self.state.write.was_paragraph { writeln_now(&mut self.state.write, ""); } @@ -479,7 +486,7 @@ where } } -impl Output> +impl Print> where W: Write + Send + Sync + 'static, { @@ -488,7 +495,7 @@ where /// Once you're finished with your long running task, calling this function /// finalizes the timer's output and transitions back to a [`state::SubBullet`]. #[must_use] - pub fn done(self) -> Output> { + pub fn done(self) -> Print> { let duration = self.state.started.elapsed(); let mut io = match self.state.write.stop() { Ok(io) => io, @@ -498,14 +505,14 @@ where }; writeln_now(&mut io, style::details(duration_format::human(&duration))); - Output { + Print { started: self.started, state: state::SubBullet { write: io }, } } } -impl Output> +impl Print> where W: Write + Send + Sync + 'static, { @@ -541,7 +548,7 @@ where /// /// Multiple steps are allowed within a section. This function returns to the same [`state::SubBullet`]. #[must_use] - pub fn sub_bullet(mut self, s: impl AsRef) -> Output> { + pub fn sub_bullet(mut self, s: impl AsRef) -> Print> { writeln_now(&mut self.state.write, Self::style(s)); self } @@ -552,17 +559,17 @@ where /// end user. Streaming lets the end user know that something is happening and provides them with /// the output of the process. /// - /// The result of this function is a `Output>` which implements [`std::io::Write`]. + /// The result of this function is a `Print>` which implements [`std::io::Write`]. /// /// If you do not wish the end user to view the output of the process, consider using a `step` instead. /// /// This function will transition your buildpack output to [`state::Stream`]. #[must_use] - pub fn start_stream(mut self, s: impl AsRef) -> Output> { + pub fn start_stream(mut self, s: impl AsRef) -> Print> { writeln_now(&mut self.state.write, Self::style(s)); writeln_now(&mut self.state.write, ""); - Output { + Print { started: self.started, state: state::Stream { started: Instant::now(), @@ -592,7 +599,8 @@ where /// /// This function will transition your buildpack output to [`state::Background`]. #[allow(clippy::missing_panics_doc)] - pub fn start_timer(mut self, s: impl AsRef) -> Output> { + #[must_use] + pub fn start_timer(mut self, s: impl AsRef) -> Print> { // Do not emit a newline after the message write!(self.state.write, "{}", Self::style(s)).expect("Output error: UI writer closed"); self.state @@ -600,7 +608,7 @@ where .flush() .expect("Output error: UI writer closed"); - Output { + Print { started: self.started, state: state::Background { started: Instant::now(), @@ -644,11 +652,11 @@ where /// /// /// ```no_run - /// use bullet_stream::{style, Output}; + /// use bullet_stream::{style, Print}; /// use fun_run::CommandWithName; /// use std::process::Command; /// - /// let mut output = Output::new(std::io::stdout()) + /// let mut output = Print::new(std::io::stdout()) /// .h2("Example Buildpack") /// .bullet("Streaming"); /// @@ -709,8 +717,9 @@ where } /// Finish a section and transition back to [`state::Bullet`]. - pub fn done(self) -> Output> { - Output { + #[must_use] + pub fn done(self) -> Print> { + Print { started: self.started, state: state::Bullet { write: self.state.write, @@ -719,7 +728,7 @@ where } } -impl Output> +impl Print> where W: Write + Send + Sync + 'static, { @@ -727,10 +736,11 @@ where /// /// Once you're finished streaming to the output, calling this function /// finalizes the stream's output and transitions back to a [`state::Bullet`]. - pub fn done(self) -> Output> { + #[must_use] + pub fn done(self) -> Print> { let duration = self.state.started.elapsed(); - let mut output = Output { + let mut output = Print { started: self.started, state: state::SubBullet { write: self.state.write.unwrap(), @@ -748,7 +758,7 @@ where } } -impl Write for Output> +impl Write for Print> where W: Write, { @@ -780,7 +790,7 @@ mod test { #[test] fn double_h2_h2_newlines() { let writer = Vec::new(); - let output = Output::new(writer).h2("Header 2").h2("Header 2"); + let output = Print::new(writer).h2("Header 2").h2("Header 2"); let io = output.done(); let expected = formatdoc! {" @@ -801,7 +811,7 @@ mod test { #[test] fn double_h1_h2_newlines() { let writer = Vec::new(); - let output = Output::new(writer).h1("Header 1").h2("Header 2"); + let output = Print::new(writer).h1("Header 1").h2("Header 2"); let io = output.done(); let expected = formatdoc! {" @@ -822,7 +832,7 @@ mod test { #[test] fn stream_with() { let writer = Vec::new(); - let mut output = Output::new(writer) + let mut output = Print::new(writer) .h2("Example Buildpack") .bullet("Streaming"); let mut cmd = std::process::Command::new("echo"); @@ -855,7 +865,7 @@ mod test { #[test] fn background_timer() { - let io = Output::new(Vec::new()) + let io = Print::new(Vec::new()) .without_header() .bullet("Background") .start_timer("Installing") @@ -887,7 +897,7 @@ mod test { #[test] fn write_paragraph_empty_lines() { - let io = Output::new(Vec::new()) + let io = Print::new(Vec::new()) .h1("Example Buildpack\n\n") .warning("\n\nhello\n\n\t\t\nworld\n\n") .bullet("Version\n\n") @@ -921,7 +931,7 @@ mod test { let tmpdir = tempfile::tempdir().unwrap(); let path = tmpdir.path().join("output.txt"); - Output::new(File::create(&path).unwrap()) + Print::new(File::create(&path).unwrap()) .h1("Buildpack Header is Bold Purple") .important("Important is bold cyan") .warning("Warnings are yellow") @@ -945,7 +955,7 @@ mod test { #[test] fn test_important() { let writer = Vec::new(); - let io = Output::new(writer) + let io = Print::new(writer) .h1("Heroku Ruby Buildpack") .important("This is important") .done(); @@ -970,7 +980,7 @@ mod test { let tmpdir = tempfile::tempdir().unwrap(); let path = tmpdir.path().join("output.txt"); - Output::new(File::create(&path).unwrap()) + Print::new(File::create(&path).unwrap()) .h1("Heroku Ruby Buildpack") .error("This is an error"); @@ -991,7 +1001,7 @@ mod test { #[test] fn test_captures() { let writer = Vec::new(); - let mut first_stream = Output::new(writer) + let mut first_stream = Print::new(writer) .h1("Heroku Ruby Buildpack") .bullet("Ruby version `3.1.3` from `Gemfile.lock`") .done() @@ -1041,7 +1051,7 @@ mod test { #[test] fn test_streaming_a_command() { let writer = Vec::new(); - let mut stream = Output::new(writer) + let mut stream = Print::new(writer) .h1("Streaming buildpack demo") .bullet("Command streaming") .start_stream("Streaming stuff"); @@ -1065,7 +1075,7 @@ mod test { #[test] fn warning_after_buildpack() { let writer = Vec::new(); - let io = Output::new(writer) + let io = Print::new(writer) .h1("RCT") .warning("It's too crowded here\nI'm tired") .bullet("Guest thoughts") @@ -1096,7 +1106,7 @@ mod test { #[test] fn warning_step_padding() { let writer = Vec::new(); - let io = Output::new(writer) + let io = Print::new(writer) .h1("RCT") .bullet("Guest thoughts") .sub_bullet("The scenery here is wonderful") @@ -1130,7 +1140,7 @@ mod test { #[test] fn double_warning_step_padding() { let writer = Vec::new(); - let output = Output::new(writer) + let output = Print::new(writer) .h1("RCT") .bullet("Guest thoughts") .sub_bullet("The scenery here is wonderful");