summaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
authorMarcel Müller <m.mueller@ifm.com>2022-05-03 18:10:42 +0200
committerMarcel Müller <m.mueller@ifm.com>2022-05-05 15:07:19 +0200
commitec704c858e39d17ea25d5f5931a68ad044819e84 (patch)
treea9f74e7305d4c4d045e1256c798878fae700ec4e /crates
parent8536c84d6e16c3520014e709ca57608bf41b934b (diff)
Add config printing
Signed-off-by: Marcel Müller <m.mueller@ifm.com>
Diffstat (limited to 'crates')
-rw-r--r--crates/core/tedge_api/Cargo.toml4
-rw-r--r--crates/core/tedge_api/src/config.rs191
2 files changed, 171 insertions, 24 deletions
diff --git a/crates/core/tedge_api/Cargo.toml b/crates/core/tedge_api/Cargo.toml
index 78c1d1e5..7fdf5508 100644
--- a/crates/core/tedge_api/Cargo.toml
+++ b/crates/core/tedge_api/Cargo.toml
@@ -15,6 +15,10 @@ tokio = { version = "1.16.1", features = ["sync", "time"] }
tokio-util = "0.7.0"
toml = "0.5.8"
serde = { version = "1.0.136", features = ["derive"] }
+pretty = { version = "0.11.3", features = ["termcolor"] }
+termcolor = "1.1.3"
+termimad = "0.20.1"
+nu-ansi-term = "0.45.1"
[dev-dependencies]
static_assertions = "1.1.0"
diff --git a/crates/core/tedge_api/src/config.rs b/crates/core/tedge_api/src/config.rs
index 21c24305..9ae1d532 100644
--- a/crates/core/tedge_api/src/config.rs
+++ b/crates/core/tedge_api/src/config.rs
@@ -1,35 +1,31 @@
use std::collections::HashMap;
+use nu_ansi_term::Color;
+use pretty::{Arena, Doc, DocAllocator, Pretty, RefDoc};
use serde::Serialize;
+use termimad::MadSkin;
use crate::message::MessageType;
/// Generic config that represents what kind of config a plugin wishes to accept
#[derive(Debug, Serialize)]
pub struct Config {
+ name: String,
kind: ConfigKind,
- doc: Option<String>,
+ doc: Option<&'static str>,
}
impl Config {
/// Construct a new generic config explanation
#[must_use]
- pub fn new(kind: ConfigKind, doc: Option<String>) -> Self {
- Self { kind, doc }
- }
-
- /// Construct a new generic config explanation from a single kind
- ///
- /// This leaves the documentation set to [`None`]
- #[must_use]
- pub fn from_kind(kind: ConfigKind) -> Self {
- Self { kind, doc: None }
+ pub fn new(name: String, kind: ConfigKind, doc: Option<&'static str>) -> Self {
+ Self { name, kind, doc }
}
/// Get a reference to the config's documentation.
#[must_use]
- pub fn doc(&self) -> Option<&str> {
- self.doc.as_deref()
+ pub fn doc(&self) -> Option<&'static str> {
+ self.doc
}
/// Get a reference to the config's kind.
@@ -40,10 +36,16 @@ impl Config {
/// Set or replace the documentation of this [`Config`]
#[must_use]
- pub fn with_doc(mut self, doc: Option<String>) -> Self {
+ pub fn with_doc(mut self, doc: Option<&'static str>) -> Self {
self.doc = doc;
self
}
+
+ /// Get the config's name.
+ #[must_use]
+ pub fn name(&self) -> &str {
+ &self.name
+ }
}
/// The specific kind a [`Config`] represents
@@ -94,37 +96,104 @@ pub trait AsConfig {
impl<T: AsConfig> AsConfig for Vec<T> {
fn as_config() -> Config {
- Config::from_kind(ConfigKind::Array(Box::new(T::as_config())))
+ Config::new(
+ format!("Array of '{}'s", T::as_config().name()),
+ ConfigKind::Array(Box::new(T::as_config())),
+ None,
+ )
}
}
impl<V: AsConfig> AsConfig for HashMap<String, V> {
fn as_config() -> Config {
- Config::from_kind(ConfigKind::HashMap(Box::new(V::as_config())))
+ Config::new(
+ format!("Table of '{}'s", V::as_config().name()),
+ ConfigKind::HashMap(Box::new(V::as_config())),
+ None,
+ )
}
}
macro_rules! impl_config_kind {
- ($kind:expr => $($name:ty),+) => {
+ ($kind:expr; $name:expr; $doc:expr => $($typ:ty),+) => {
$(
- impl AsConfig for $name {
+ impl AsConfig for $typ {
fn as_config() -> Config {
- Config::from_kind($kind)
+ Config::new({$name}.into(), $kind, Some($doc))
}
}
)+
};
}
-impl_config_kind!(ConfigKind::Integer => u64, i64);
-impl_config_kind!(ConfigKind::Float => f64);
-impl_config_kind!(ConfigKind::Bool => bool);
-impl_config_kind!(ConfigKind::String => String);
+impl_config_kind!(ConfigKind::Integer; "Integer"; "A signed integer with 64 bits" => u64, i64);
+impl_config_kind!(ConfigKind::Float; "Float"; "A floating point value with 64 bits" => f64);
+impl_config_kind!(ConfigKind::Bool; "Boolean"; "A boolean representing either true or false" => bool);
+impl_config_kind!(ConfigKind::String; "String"; "An UTF-8 encoded string of characters" => String);
+
+/******Pretty Printing of Configs******/
+
+impl Config {
+ /// Get a [`RcDoc`](pretty::RcDoc) which can be used to write the documentation of this
+ pub fn as_terminal_doc<'a>(&'a self, arena: &'a Arena<'a>) -> RefDoc<'a> {
+ let mut doc = arena
+ .nil()
+ .append(Color::LightBlue.bold().paint(self.name()).to_string())
+ .append(arena.hardline());
+
+ if let Some(conf_doc) = self.doc() {
+ let skin = MadSkin::default_dark();
+ let rendered = skin.text(&conf_doc, None).to_string();
+ doc = doc.append(arena.intersperse(
+ rendered.split("\n").map(|t| {
+ arena.intersperse(
+ t.split(char::is_whitespace).map(|t| t.to_string()),
+ arena.softline(),
+ )
+ }),
+ arena.hardline(),
+ ));
+ }
+
+ match self.kind() {
+ ConfigKind::Array(conf) => {
+ doc = doc.append(Pretty::pretty(conf.as_terminal_doc(arena), arena))
+ }
+ ConfigKind::Struct(stc) => {
+ doc = doc
+ .append(arena.hardline())
+ .append(Color::Blue.paint("[Members]").to_string())
+ .append(arena.hardline())
+ .append(arena.intersperse(
+ stc.iter().map(|(member_name, member_conf)| {
+ arena
+ .text(Color::Blue.bold().paint(member_name).to_string())
+ .append(": ")
+ .append(
+ Pretty::pretty(member_conf.as_terminal_doc(arena), arena)
+ .nest(4),
+ )
+ }),
+ Doc::hardline(),
+ ))
+ }
+ ConfigKind::HashMap(conf) => {
+ doc = doc.append(Pretty::pretty(conf.as_terminal_doc(arena), arena))
+ }
+ _ => (),
+ };
+
+ doc.into_doc()
+ }
+}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
+ use nu_ansi_term::Color;
+ use pretty::Arena;
+
use crate::config::{AsConfig, Config, ConfigKind};
#[test]
@@ -133,7 +202,8 @@ mod tests {
Vec::<f64>::as_config(),
Config {
doc: None,
- kind: ConfigKind::Array(x)
+ kind: ConfigKind::Array(x),
+ ..
} if matches!(x.kind(), ConfigKind::Float)
));
@@ -144,4 +214,77 @@ mod tests {
matches!(complex_config.kind(), ConfigKind::HashMap(map) if matches!(map.kind(), ConfigKind::Array(arr) if matches!(arr.kind(), ConfigKind::HashMap(inner_map) if matches!(inner_map.kind(), ConfigKind::String))))
);
}
+
+ struct Port(u64);
+
+ impl AsConfig for Port {
+ fn as_config() -> Config {
+ Config::new(
+ String::from("Integer"),
+ ConfigKind::Integer,
+ Some("A TCP port number is an integer between 0 and 65535"),
+ )
+ }
+ }
+
+ struct VHost;
+
+ impl AsConfig for VHost {
+ fn as_config() -> Config {
+ Config::new(
+ String::from("VHost"),
+ ConfigKind::Struct(HashMap::from([(String::from("name"), String::as_config())])),
+ Some("A virtual host definition"),
+ )
+ }
+ }
+
+ #[test]
+ fn check_terminal_output() {
+ let arena = Arena::new();
+
+ let doc = Vec::<String>::as_config();
+ let rendered_doc = doc.as_terminal_doc(&arena);
+
+ let mut output = String::new();
+
+ rendered_doc.render_fmt(80, &mut output).unwrap();
+
+ println!("{}", output);
+ println!("-------");
+
+ let arena = Arena::new();
+
+ let doc = Config::new(
+ String::from("ServerConfig"),
+ ConfigKind::Struct(HashMap::from([
+ (String::from("port"), Port::as_config()),
+ (String::from("interface"), String::as_config()),
+ (String::from("virtual_hosts"), Vec::<VHost>::as_config()),
+ (String::from("headers"), HashMap::<String, String>::as_config()),
+ ])),
+ Some("Specify how the server should be started\n\n## Note\n\nThis is a reallly really loooooooooooooooooong loooooooooooooooooooong new *line*."),
+ );
+ let rendered_doc = doc.as_terminal_doc(&arena);
+
+ let mut output = String::new();
+
+ rendered_doc.render_fmt(80, &mut output).unwrap();
+
+ println!(
+ "Configuration for {} plugin kinds",
+ Color::White.bold().paint(doc.name())
+ );
+ println!(
+ "{}",
+ Color::White.dimmed().bold().paint(format!(
+ "=================={}=============",
+ std::iter::repeat('=')
+ .take(doc.name().len())
+ .collect::<String>()
+ ))
+ );
+ println!("{}", output);
+ println!("-------");
+ }
}