Skip to content

Commit

Permalink
update display opts
Browse files Browse the repository at this point in the history
  • Loading branch information
kariy committed Apr 14, 2024
1 parent c7a17c1 commit 228c2ae
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 0 deletions.
79 changes: 79 additions & 0 deletions crates/args/src/opts/display/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use std::marker::PhantomData;

use clap::Args;
use eyre::{ensure, eyre, Result};
use serde::Serialize;
use serde_json::Value;

use super::RawDisplay;

#[derive(Debug, Args)]
pub struct JsonDisplay<T: Serialize> {
#[arg(short, long)]
#[arg(help = "Display the output in its raw JSON format")]
json: bool,

#[arg(long)]
#[arg(help = "Display only the specified field")]
pub field: Option<String>,

#[arg(skip)]
_phantom: PhantomData<T>,
}

#[derive(Debug, thiserror::Error)]
#[error("Field '{field}' doesn't exist.\nAvailable fields: {}", available_fields.join(", "))]
pub struct FieldNotFoundError {
/// The field that was not found.
field: String,
/// The available fields in the object.
available_fields: Vec<String>,
}

impl<T: Serialize> JsonDisplay<T> {
fn display_json(&self, value: T) -> Result<String> {
if let Some(ref field) = self.field {
let value = serde_json::to_value(&value)?;

ensure!(
value.is_object(),
"Unable to extract field '{field}'. Value is not an object."
);

// TODO: allow specifying nested fields using dot notation (e.g. "block.number") in the cli
match value.get(field) {
Some(field) => Ok(colored_json::to_colored_json_auto(field)?),

None => {
return Err(eyre!(FieldNotFoundError {
field: field.clone(),
available_fields: keys(&value)
}));
}
}
} else {
Ok(colored_json::to_colored_json_auto(&value)?)
}
}
}

impl<T: Serialize> RawDisplay for JsonDisplay<T> {
type Value = T;

fn display_raw(&self, value: Self::Value) -> Result<impl std::fmt::Display> {
#[cfg(windows)]
let _ = colored_json::enable_ansi_support();
Ok(self.display_json(value)?)
}

fn is_raw(&self) -> bool {
self.json || self.field.is_some()
}
}

fn keys(value: &Value) -> Vec<String> {
value
.as_object()
.map(|obj| obj.keys().map(|s| s.to_string()).collect::<Vec<_>>())
.unwrap_or_default()
}
46 changes: 46 additions & 0 deletions crates/args/src/opts/display/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
mod json;

use clap::Args;
use eyre::Result;

pub use self::json::JsonDisplay;
use crate::fmt::Pretty;

#[derive(Debug, Args)]
pub struct DisplayOptions<T>
where
T: Args + RawDisplay,
{
#[command(flatten)]
pub raw_format: T,
}

impl<T> DisplayOptions<T>
where
T: RawDisplay + Args,
{
pub fn print(&self, value: <T as RawDisplay>::Value) -> Result<()>
where
<T as RawDisplay>::Value: Pretty,
{
if self.raw_format.is_raw() {
println!("{}", self.raw_format.display_raw(value)?);
} else {
println!("{}", value.prettify());
}
Ok(())
}
}

/// Trait for displaying a value in its intended raw format.
pub trait RawDisplay {
/// The type of the value to be displayed.
type Value;

/// Returns the [Display](std::fmt::Display) implementation for the raw value.
fn display_raw(&self, value: Self::Value) -> Result<impl std::fmt::Display>;

fn is_raw(&self) -> bool {
false
}
}

0 comments on commit 228c2ae

Please sign in to comment.