Skip to main content

ctl_core/
format.rs

1//! The output representation: `pretty` for people, or `json` for machines.
2//!
3//! [`OutputFormat`] parses from those two lowercase names, and `Display`
4//! prints them back. With the `cli` feature it is a Clap value enum, so
5//! `OutputArgs` parses `--format` into it.
6//!
7//! ```
8//! use ctl_core::format::OutputFormat;
9//!
10//! assert_eq!("json".parse::<OutputFormat>(), Ok(OutputFormat::Json));
11//! assert!(OutputFormat::Json.is_json());
12//! assert_eq!(OutputFormat::default(), OutputFormat::Pretty);
13//! ```
14
15use std::fmt;
16use std::str::FromStr;
17
18/// Hosts `JsonSchema`. With `schema`, schemars expands `concat!`; this module
19/// is the allow, and only that feature needs it.
20mod data {
21    #![cfg_attr(
22        feature = "schema",
23        expect(
24            clippy::disallowed_macros,
25            reason = "schemars' JsonSchema derive expands concat!"
26        )
27    )]
28
29    /// The output representation a view writes a result in.
30    ///
31    /// A format, such as YAML, can join the set in a minor release. So a match
32    /// on it outside ctl-core ends with a wildcard arm.
33    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
34    #[non_exhaustive]
35    #[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
36    #[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))]
37    #[cfg_attr(feature = "json", serde(rename_all = "lowercase"))]
38    #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
39    pub enum OutputFormat {
40        #[default]
41        /// Human text, optionally coloured.
42        Pretty,
43        /// Machine JSON. Never contains ANSI.
44        Json,
45    }
46}
47
48pub use data::OutputFormat;
49
50impl OutputFormat {
51    #[must_use]
52    /// Whether this is [`Self::Json`], so the output is for machines.
53    pub fn is_json(self) -> bool {
54        self == Self::Json
55    }
56}
57
58impl FromStr for OutputFormat {
59    type Err = ParseFormatError;
60
61    fn from_str(value: &str) -> Result<Self, Self::Err> {
62        match value {
63            "pretty" => Ok(Self::Pretty),
64            "json" => Ok(Self::Json),
65            _ => Err(ParseFormatError),
66        }
67    }
68}
69
70impl fmt::Display for OutputFormat {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        f.write_str(match self {
73            Self::Pretty => "pretty",
74            Self::Json => "json",
75        })
76    }
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80/// The error [`OutputFormat`]'s `FromStr` returns for text that is not
81/// `pretty` or `json`. `Display` prints `expected pretty or json`.
82pub struct ParseFormatError;
83
84impl fmt::Display for ParseFormatError {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.write_str("expected pretty or json")
87    }
88}
89
90impl std::error::Error for ParseFormatError {}
91
92#[cfg(test)]
93mod tests {
94    use super::OutputFormat;
95
96    #[test]
97    fn json_predicate() {
98        assert!(OutputFormat::Json.is_json());
99        assert!(!OutputFormat::Pretty.is_json());
100        assert_eq!("json".parse::<OutputFormat>().unwrap(), OutputFormat::Json);
101    }
102}