Skip to content

Commit

Permalink
Add support for writing YAML in CliOutput.
Browse files Browse the repository at this point in the history
  • Loading branch information
azriel91 committed Oct 13, 2022
1 parent ad9684a commit 6af5ce1
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 41 deletions.
2 changes: 1 addition & 1 deletion crate/rt_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Re-exports
pub use fn_graph::{self, FnRef, FnRefMut};
pub use peace_rt_model_core::OutputWrite;
pub use peace_rt_model_core::{OutputFormat, OutputWrite};

pub use crate::{
cmd_context::CmdContext, cmd_context_builder::CmdContextBuilder,
Expand Down
3 changes: 2 additions & 1 deletion crate/rt_model_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// Re-exports
pub use async_trait::async_trait;

pub use crate::output_write::OutputWrite;
pub use crate::{output_format::OutputFormat, output_write::OutputWrite};

mod output_format;
mod output_write;
10 changes: 10 additions & 0 deletions crate/rt_model_core/src/output_format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// How to format command output -- human readable or machine parsable.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputFormat {
/// Human readable text.
Text,
/// The [YAML] format.
///
/// [YAML]: https://yaml.org/
Yaml,
}
2 changes: 1 addition & 1 deletion crate/rt_model_native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ keywords = ["automation"]
license = "MIT OR Apache-2.0"

[lib]
doctest = false
doctest = true
test = false

[dependencies]
Expand Down
141 changes: 126 additions & 15 deletions crate/rt_model_native/src/cli_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use peace_resources::{
},
type_reg::untagged::BoxDtDisplay,
};
use peace_rt_model_core::{async_trait, OutputWrite};
use peace_rt_model_core::{async_trait, OutputFormat, OutputWrite};
use serde::Serialize;
use tokio::io::{AsyncWrite, AsyncWriteExt, Stdout};

use crate::Error;
Expand All @@ -19,6 +20,8 @@ use crate::Error;
pub struct CliOutput<W> {
/// Output stream to write to.
writer: W,
/// How to format command output -- human readable or machine parsable.
format: OutputFormat,
/// Whether output should be colorized.
#[cfg(feature = "output_colorized")]
colorized: bool,
Expand All @@ -38,23 +41,59 @@ where
W: AsyncWrite + std::marker::Unpin,
{
/// Returns a new `CliOutput` using `io::stdout()` as the output stream.
///
/// # Examples
///
/// ```rust
/// # use peace_rt_model_native::CliOutput;
/// // use peace::rt_model::CliOutput;
///
/// let mut buffer = Vec::<u8>::new();
/// let cli_output = CliOutput::new_with_writer(&mut buffer);
/// ```
pub fn new_with_writer(writer: W) -> Self {
Self {
writer,
format: OutputFormat::Text,
#[cfg(feature = "output_colorized")]
colorized: false,
}
}

/// Returns a new `CliOutput` using `io::stdout()` as the output stream.
/// Sets the output format for this `CliOutput`.
///
/// # Examples
///
/// ```rust
/// # use peace_rt_model_core::OutputFormat;
/// # use peace_rt_model_native::CliOutput;
/// // use peace::rt_model::{CliOutput, OutputFormat};
///
/// let cli_output = CliOutput::new().output_format(OutputFormat::Yaml);
/// ```
pub fn output_format(mut self, output_format: OutputFormat) -> Self {
self.format = output_format;
self
}

/// Enables colorized output.
///
/// # Examples
///
/// ```rust
/// # use peace_rt_model_native::CliOutput;
/// // use peace::rt_model::CliOutput;
///
/// let cli_output = CliOutput::new().colorized();
/// ```
#[cfg(feature = "output_colorized")]
pub fn colorized(mut self) -> Self {
self.colorized = true;
self
}

#[cfg(not(feature = "output_colorized"))]
async fn output_any_display<'f, E, I>(&mut self, iter: I) -> Result<(), E>
async fn output_display<'f, E, I>(&mut self, iter: I) -> Result<(), E>
where
E: std::error::Error + From<Error>,
I: Iterator<Item = (&'f ItemSpecId, &'f BoxDtDisplay)>,
Expand All @@ -81,7 +120,7 @@ where
}

#[cfg(feature = "output_colorized")]
async fn output_any_display<'f, E, I>(&mut self, iter: I) -> Result<(), E>
async fn output_display<'f, E, I>(&mut self, iter: I) -> Result<(), E>
where
E: std::error::Error + From<Error>,
I: Iterator<Item = (&'f ItemSpecId, &'f BoxDtDisplay)>,
Expand Down Expand Up @@ -116,12 +155,29 @@ where
.map_err(Error::StdoutWrite)?;
Ok(())
}

async fn output_yaml<'f, E, T, F>(&mut self, t: &T, fn_error: F) -> Result<(), E>
where
E: std::error::Error + From<Error>,
T: Serialize,
F: FnOnce(serde_yaml::Error) -> Error,
{
let t_serialized = serde_yaml::to_string(t).map_err(fn_error)?;

self.writer
.write_all(t_serialized.as_bytes())
.await
.map_err(Error::StdoutWrite)?;

Ok(())
}
}

impl Default for CliOutput<Stdout> {
fn default() -> Self {
Self {
writer: tokio::io::stdout(),
format: OutputFormat::Text,
#[cfg(feature = "output_colorized")]
colorized: false,
}
Expand All @@ -138,44 +194,99 @@ where
W: AsyncWrite + std::marker::Unpin,
{
async fn write_states_current(&mut self, states_current: &StatesCurrent) -> Result<(), E> {
self.output_any_display(states_current.iter()).await
match self.format {
OutputFormat::Text => self.output_display(states_current.iter()).await,
OutputFormat::Yaml => {
self.output_yaml(states_current, Error::StatesCurrentSerialize)
.await
}
}
}

async fn write_states_desired(&mut self, states_desired: &StatesDesired) -> Result<(), E> {
self.output_any_display(states_desired.iter()).await
match self.format {
OutputFormat::Text => self.output_display(states_desired.iter()).await,
OutputFormat::Yaml => {
self.output_yaml(states_desired, Error::StatesDesiredSerialize)
.await
}
}
}

async fn write_state_diffs(&mut self, state_diffs: &StateDiffs) -> Result<(), E> {
self.output_any_display(state_diffs.iter()).await
match self.format {
OutputFormat::Text => self.output_display(state_diffs.iter()).await,
OutputFormat::Yaml => {
self.output_yaml(state_diffs, Error::StateDiffsSerialize)
.await
}
}
}

async fn write_states_ensured_dry(
&mut self,
states_ensured_dry: &StatesEnsuredDry,
) -> Result<(), E> {
self.output_any_display(states_ensured_dry.iter()).await
match self.format {
OutputFormat::Text => self.output_display(states_ensured_dry.iter()).await,
OutputFormat::Yaml => {
self.output_yaml(states_ensured_dry, Error::StatesEnsuredDrySerialize)
.await
}
}
}

async fn write_states_ensured(&mut self, states_ensured: &StatesEnsured) -> Result<(), E> {
self.output_any_display(states_ensured.iter()).await
match self.format {
OutputFormat::Text => self.output_display(states_ensured.iter()).await,
OutputFormat::Yaml => {
self.output_yaml(states_ensured, Error::StatesEnsuredSerialize)
.await
}
}
}

async fn write_states_cleaned_dry(
&mut self,
states_cleaned_dry: &StatesCleanedDry,
) -> Result<(), E> {
self.output_any_display(states_cleaned_dry.iter()).await
match self.format {
OutputFormat::Text => self.output_display(states_cleaned_dry.iter()).await,
OutputFormat::Yaml => {
self.output_yaml(states_cleaned_dry, Error::StatesCleanedDrySerialize)
.await
}
}
}

async fn write_states_cleaned(&mut self, states_cleaned: &StatesCleaned) -> Result<(), E> {
self.output_any_display(states_cleaned.iter()).await
match self.format {
OutputFormat::Text => self.output_display(states_cleaned.iter()).await,
OutputFormat::Yaml => {
self.output_yaml(states_cleaned, Error::StatesCleanedSerialize)
.await
}
}
}

async fn write_err(&mut self, error: &E) -> Result<(), E> {
self.writer
.write_all(format!("{error}\n").as_bytes())
.await
.map_err(Error::StdoutWrite)?;
match self.format {
OutputFormat::Text => {
self.writer
.write_all(format!("{error}\n").as_bytes())
.await
.map_err(Error::StdoutWrite)?;
}
OutputFormat::Yaml => {
// TODO: proper parsable structure with error code.
let error_serialized =
serde_yaml::to_string(&format!("{error}")).map_err(Error::ErrorSerialize)?;
self.writer
.write_all(error_serialized.as_bytes())
.await
.map_err(Error::StdoutWrite)?;
}
}

Ok(())
}
Expand Down
8 changes: 6 additions & 2 deletions crate/rt_model_native/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// Remember to add common variants to `rt_model_web/src/error.rs`.

use std::{ffi::OsString, path::PathBuf, sync::Mutex};

// Remember to add common variants to `rt_model_web/src/error.rs`.

/// Peace runtime errors.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Failed to serialize error.
#[error("Failed to deserialize error.")]
ErrorSerialize(#[source] serde_yaml::Error),

/// Failed to deserialize current states.
#[error("Failed to deserialize current states.")]
StatesCurrentDeserialize(#[source] serde_yaml::Error),
Expand Down
4 changes: 4 additions & 0 deletions crate/rt_model_web/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use std::path::PathBuf;
/// Peace web support errors.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Failed to serialize error.
#[error("Failed to deserialize error.")]
ErrorSerialize(#[source] serde_yaml::Error),

/// Failed to deserialize current states.
#[error("Failed to deserialize current states.")]
StatesCurrentDeserialize(#[source] serde_yaml::Error),
Expand Down
Loading

0 comments on commit 6af5ce1

Please sign in to comment.