diff options
28 files changed, 689 insertions, 391 deletions
@@ -1373,6 +1373,12 @@ dependencies = [ ] [[package]] +name = "indoc" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" + +[[package]] name = "inotify" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2186,7 +2192,6 @@ name = "plugin_avg" version = "0.1.0" dependencies = [ "async-trait", - "humantime-serde", "miette", "serde", "tedge_api", @@ -2239,6 +2244,7 @@ name = "plugin_log" version = "0.1.0" dependencies = [ "async-trait", + "indoc", "log", "miette", "serde", @@ -2254,6 +2260,7 @@ name = "plugin_measurement_filter" version = "0.1.0" dependencies = [ "async-trait", + "indoc", "miette", "serde", "serde_with", @@ -3317,6 +3324,7 @@ dependencies = [ "clap 3.1.6", "env_logger 0.9.0", "miette", + "nu-ansi-term", "plugin_avg", "plugin_httpstop", "plugin_inotify", @@ -3326,9 +3334,13 @@ dependencies = [ "plugin_mqtt_measurement_bridge", "plugin_notification", "plugin_sysstat", + "pretty", "tedge_api", "tedge_core", "tedge_lib", + "term_size", + "termcolor", + "termimad", "tokio", "toml", "tracing", @@ -3388,14 +3400,10 @@ dependencies = [ "downcast-rs", "futures", "miette", - "nu-ansi-term", - "pretty", "pretty_assertions", "serde", "static_assertions", "tedge_config_derive", - "termcolor", - "termimad", "thiserror", "tokio", "tokio-util 0.7.0", @@ -3478,6 +3486,8 @@ version = "0.1.0" dependencies = [ "async-trait", "futures", + "humantime-serde", + "indoc", "log", "serde", "tedge_api", @@ -3563,6 +3573,16 @@ dependencies = [ ] [[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + +[[package]] name = "termcolor" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/crates/core/tedge_api/Cargo.toml b/crates/core/tedge_api/Cargo.toml index 619e769e..2f316c2f 100644 --- a/crates/core/tedge_api/Cargo.toml +++ b/crates/core/tedge_api/Cargo.toml @@ -15,10 +15,6 @@ 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" tedge_config_derive = { version = "0.1.0", path = "tedge_config_derive" } [dev-dependencies] diff --git a/crates/core/tedge_api/examples/print_config.rs b/crates/core/tedge_api/examples/print_config.rs deleted file mode 100644 index ba6e7a6b..00000000 --- a/crates/core/tedge_api/examples/print_config.rs +++ /dev/null @@ -1,149 +0,0 @@ -use std::collections::HashMap; - -use nu_ansi_term::Color; -use pretty::Arena; -use tedge_api::{ - config::{AsConfig, ConfigDescription, ConfigKind}, - Config, -}; -struct Port(u64); - -impl AsConfig for Port { - fn as_config() -> ConfigDescription { - ConfigDescription::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() -> ConfigDescription { - ConfigDescription::new( - String::from("VHost"), - ConfigKind::Struct(vec![("name", None, String::as_config())]), - Some("A virtual host definition"), - ) - } -} - -fn main() { - 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 for {}", - std::any::type_name::<Vec<String>>() - ); - println!("{}", output); - - let arena = Arena::new(); - - let doc = ConfigDescription::new( - String::from("ServerConfig"), - ConfigKind::Struct(vec![ - ("port", None, Port::as_config()), - ("interface", None, String::as_config()), - ("virtual_hosts", None, Vec::<VHost>::as_config()), - ("headers", None, 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 for ServerConfig"); - println!("{}", output); - let arena = Arena::new(); - - #[derive(Config)] - #[config(tag = "type")] - /// An Nginx virtual host - /// - /// # Note - /// - /// This is an example and as such is nonsense - enum NginxVHost { - /// A simple host consisting of a string - Simple(String), - /// A more complex host that can also specify its port - Complex { - /// the name of the VHost - name: String, - port: Port, - }, - UndocumentedComplex { - num: u16, - foo: f32, - }, - } - - #[derive(Config)] - #[config(untagged)] - enum DebugLevel { - /// Enables debug output - /// - /// And info of course - Debug, - /// Only pertinent information will be logged - Info, - /// A custom debug level - Custom(String), - } - - #[derive(Config)] - struct NginxConfig { - vhosts: Vec<NginxVHost>, - debug_level: DebugLevel, - allow_priv_ports: bool, - } - - let doc = NginxConfig::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 for NginxConfig"); - 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!("-------"); -} diff --git a/crates/core/tedge_api/src/config.rs b/crates/core/tedge_api/src/config.rs index 90bee595..ff25e1b4 100644 --- a/crates/core/tedge_api/src/config.rs +++ b/crates/core/tedge_api/src/config.rs @@ -1,9 +1,6 @@ use std::collections::HashMap; -use nu_ansi_term::Color; -use pretty::{Arena, Doc, DocAllocator, Pretty, RefDoc}; use serde::Serialize; -use termimad::MadSkin; /// Generic config that represents what kind of config a plugin wishes to accept #[derive(Debug, Serialize, PartialEq)] @@ -131,6 +128,16 @@ pub trait AsConfig { fn as_config() -> ConfigDescription; } +impl<T: AsConfig> AsConfig for Option<T> { + fn as_config() -> ConfigDescription { + ConfigDescription::new( + format!("An optional '{}'", T::as_config().name()), + ConfigKind::Wrapped(Box::new(T::as_config())), + None, + ) + } +} + impl<T: AsConfig> AsConfig for Vec<T> { fn as_config() -> ConfigDescription { ConfigDescription::new( @@ -151,6 +158,16 @@ impl<V: AsConfig> AsConfig for HashMap<String, V> { } } +impl<V: AsConfig> AsConfig for HashMap<std::path::PathBuf, V> { + fn as_config() -> ConfigDescription { + ConfigDescription::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:expr; $doc:expr => $($typ:ty),+) => { $( @@ -181,176 +198,9 @@ impl_config_kind!(ConfigKind::Float; "Float"; "A floating point value with 32 bi impl_config_kind!(ConfigKind::Bool; "Boolean"; "A boolean" => bool); impl_config_kind!(ConfigKind::String; "String"; "An UTF-8 string" => String); -/******Pretty Printing of Configs******/ - -impl ConfigDescription { - /// 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(); - - if !matches!(self.kind(), ConfigKind::Wrapped(_)) && self.doc().is_none() { - doc = doc - .append(Color::LightBlue.bold().paint(self.name()).to_string()) - .append(arena.space()) - .append(match self.kind() { - ConfigKind::Bool - | ConfigKind::Integer - | ConfigKind::Float - | ConfigKind::String - | ConfigKind::Wrapped(_) - | ConfigKind::Array(_) - | ConfigKind::HashMap(_) => arena.nil(), - ConfigKind::Struct(_) => { - arena.text(Color::Blue.dimmed().paint("[Table]").to_string()) - } - ConfigKind::Enum(_, _) => { - arena.text(Color::Green.dimmed().paint("[Enum]").to_string()) - } - }) - .append(arena.hardline()); - } - - let skin = MadSkin::default_dark(); - let render_markdown = |text: &str| { - let rendered = skin.text(text, None).to_string(); - arena.intersperse( - rendered.split("\n").map(|t| { - arena.intersperse( - t.split(char::is_whitespace).map(|t| t.to_string()), - arena.softline(), - ) - }), - arena.hardline(), - ) - }; - - if let Some(conf_doc) = self.doc() { - doc = doc.append(render_markdown(&conf_doc)); - } - - match self.kind() { - ConfigKind::Bool | ConfigKind::Integer | ConfigKind::Float | ConfigKind::String => (), - 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_doc, member_conf)| { - let mut doc = arena.nil(); - - if let Some(member_doc) = member_doc { - doc = doc.append(render_markdown(&member_doc)); - } - doc.append( - 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::Enum(enum_kind, variants) => { - doc = doc - .append(arena.hardline()) - .append(Color::Green.paint("One of:").to_string()) - .append(arena.space()) - .append(match enum_kind { - ConfigEnumKind::Tagged(tag) => arena.text( - Color::White - .dimmed() - .paint(format!( - "[Tagged with {}]", - Color::LightGreen - .italic() - .dimmed() - .paint(format!("'{}'", tag)) - )) - .to_string(), - ), - ConfigEnumKind::Untagged => { - arena.text(Color::White.dimmed().paint("[Untagged]").to_string()) - } - }) - .append(arena.hardline()) - .append( - arena.intersperse( - variants - .iter() - .map(|(member_name, member_doc, member_conf)| { - arena.text("-").append(arena.space()).append({ - let mut doc = arena - .nil() - .append(match member_conf { - EnumVariantRepresentation::String(_) => arena.text( - Color::Green - .bold() - .paint(&format!( - "{:?}", - member_name.to_lowercase() - )) - .to_string(), - ), - EnumVariantRepresentation::Wrapped(_) => arena - .text( - Color::Green - .bold() - .paint(*member_name) - .to_string(), - ), - }) - .append(": "); - - if let Some(member_doc) = member_doc { - doc = doc.append(render_markdown(&member_doc)); - } - - doc.append( - Pretty::pretty( - match member_conf { - EnumVariantRepresentation::String(_) => { - arena.nil().into_doc() - } - - EnumVariantRepresentation::Wrapped( - member_conf, - ) => arena - .text( - Color::LightRed - .paint("Is a: ") - .to_string(), - ) - .append(member_conf.as_terminal_doc(arena)) - .into_doc(), - }, - arena, - ) - .nest(4), - ) - .nest(2) - }) - }), - Doc::hardline(), - ), - ); - } - ConfigKind::Array(conf) => { - doc = doc - .append(Color::LightRed.paint("Many of:").to_string()) - .append(arena.space()) - .append(conf.as_terminal_doc(arena)); - } - ConfigKind::HashMap(conf) | ConfigKind::Wrapped(conf) => { - doc = doc.append(conf.as_terminal_doc(arena)); - } - }; - - doc.into_doc() - } -} +impl_config_kind!(ConfigKind::String; "String"; "A socket address" => std::net::SocketAddr); +impl_config_kind!(ConfigKind::String; "String"; "An IPv4 socket address" => std::net::SocketAddrV4); +impl_config_kind!(ConfigKind::String; "String"; "An IPv6 socket address" => std::net::SocketAddrV6); #[cfg(test)] mod tests { diff --git a/crates/core/tedge_lib/Cargo.toml b/crates/core/tedge_lib/Cargo.toml index 083779ea..5c95662a 100644 --- a/crates/core/tedge_lib/Cargo.toml +++ b/crates/core/tedge_lib/Cargo.toml @@ -14,5 +14,7 @@ thiserror = "1.0.30" tokio = { version = "1.16.1", features = ["macros", "rt", "sync", "time"] } toml = "0.5.8" tracing = "0.1" +humantime-serde = "1" +indoc = "1.0.6" tedge_api = { path = "../tedge_api" } diff --git a/crates/core/tedge_lib/src/config.rs b/crates/core/tedge_lib/src/config.rs new file mode 100644 index 00000000..b06ef3ff --- /dev/null +++ b/crates/core/tedge_lib/src/config.rs @@ -0,0 +1,56 @@ +#[derive(Copy, Clone, Debug, serde::Deserialize)] +#[serde(transparent)] +pub struct Humantime(#[serde(with = "humantime_serde")] std::time::Duration); + +impl Humantime { + pub fn into_duration(self) -> std::time::Duration { + self.0 + } +} + +impl tedge_api::AsConfig for Humantime { + fn as_config() -> tedge_api::ConfigDescription { + tedge_api::ConfigDescription::new( + "Duration-representing String".to_string(), + tedge_api::ConfigKind::String, + Some(indoc::indoc!{r#" + A String that represents a duration + + ## Examples + + A duration of one minute: + + ```toml + "1min" + ``` + + A duration of 5 minutes and 2 nanoseconds: + + ```toml + "5min 2ns" + ``` + + ## More information + + For more information have a look at the documentation of + [the humantime crate](https://docs.rs/humantime). + "#}), + ) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_humantime_deser() { + let ts = r#"t = "1sec""#; + + #[derive(serde::Deserialize)] + struct T { + t: super::Humantime, + } + + let ht: T = toml::from_str(ts).unwrap(); + assert_eq!(ht.t.0, std::time::Duration::from_secs(1)); + } +} diff --git a/crates/core/tedge_lib/src/lib.rs b/crates/core/tedge_lib/src/lib.rs index 279aad2b..4f52bd8a 100644 --- a/crates/core/tedge_lib/src/lib.rs +++ b/crates/core/tedge_lib/src/lib.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod iter; pub mod mainloop; pub mod measurement; diff --git a/plugins/plugin_avg/Cargo.toml b/plugins/plugin_avg/Cargo.toml index 0d1cc156..db7884ab 100644 --- a/plugins/plugin_avg/Cargo.toml +++ b/plugins/plugin_avg/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] async-trait = "0.1" -humantime-serde = "1" miette = "4.4" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1" diff --git a/plugins/plugin_avg/src/lib.rs b/plugins/plugin_avg/src/lib.rs index 47ff0aa4..45202c9b 100644 --- a/plugins/plugin_avg/src/lib.rs +++ b/plugins/plugin_avg/src/lib.rs @@ -21,11 +21,12 @@ use tracing::Instrument; pub struct AvgPluginBuilder; -#[derive(serde::Deserialize, Debug)] +#[derive(serde::Deserialize, Debug, tedge_api::Config)] struct AvgConfig { - #[serde(with = "humantime_serde")] - timeframe: std::time::Duration, + /// The duration of the time window to calculate the average for + timeframe: tedge_lib::config::Humantime, + /// The name of the plugin to send the result to target: String, /// Whether to report a 0 (zero) if there are zero measurements in the timeframe @@ -47,6 +48,10 @@ impl<PD: PluginDirectory> PluginBuilder<PD> for AvgPluginBuilder { "avg" } + fn kind_configuration() -> Option<tedge_api::ConfigDescription> { + Some(<AvgConfig as tedge_api::AsConfig>::as_config()) + } + async fn verify_configuration( &self, config: &PluginConfiguration, @@ -121,7 +126,7 @@ impl Plugin for AvgPlugin { values: self.values.clone(), }; let (stopper, mainloop) = - tedge_lib::mainloop::Mainloop::ticking_every(self.config.timeframe, state); + tedge_lib::mainloop::Mainloop::ticking_every(self.config.timeframe.into_duration(), state); self.stopper = Some(stopper); let _ = tokio::spawn( diff --git a/plugins/plugin_httpstop/src/lib.rs b/plugins/plugin_httpstop/src/lib.rs index 24eacc70..c109f826 100644 --- a/plugins/plugin_httpstop/src/lib.rs +++ b/plugins/plugin_httpstop/src/lib.rs @@ -9,8 +9,9 @@ use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; use tracing::{debug, error, Instrument}; -#[derive(serde::Deserialize, Debug)] +#[derive(serde::Deserialize, Debug, tedge_api::Config)] struct HttpStopConfig { + /// The address to listen on bind: std::net::SocketAddr, } @@ -37,6 +38,10 @@ where "httpstop" } + fn kind_configuration() -> Option<tedge_api::ConfigDescription> { + Some(<HttpStopConfig as tedge_api::AsConfig>::as_config()) + } + fn kind_message_types() -> HandleTypes where Self: Sized, diff --git a/plugins/plugin_inotify/src/config.rs b/plugins/plugin_inotify/src/config.rs index 0b201687..6859b4e9 100644 --- a/plugins/plugin_inotify/src/config.rs +++ b/plugins/plugin_inotify/src/config.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::path::PathBuf; -#[derive(serde::Deserialize, Debug)] +#[derive(serde::Deserialize, Debug, tedge_api::Config)] pub struct InotifyConfig { /// Target to send notifications to pub(crate) target: String, @@ -21,7 +21,8 @@ fn fail_on_err_default() -> bool { true } -#[derive(serde::Deserialize, Copy, Clone, Debug)] +#[derive(serde::Deserialize, Copy, Clone, Debug, tedge_api::Config)] +#[config(untagged)] #[allow(non_camel_case_types)] pub enum Watchmode { /// File was accessed diff --git a/plugins/plugin_inotify/src/lib.rs b/plugins/plugin_inotify/src/lib.rs index 68687b0b..2b1800d7 100644 --- a/plugins/plugin_inotify/src/lib.rs +++ b/plugins/plugin_inotify/src/lib.rs @@ -43,6 +43,10 @@ impl<PD: PluginDirectory> PluginBuilder<PD> for InotifyPluginBuilder { "inotify" } + fn kind_configuration() -> Option<tedge_api::ConfigDescription> { + Some(<InotifyConfig as tedge_api::AsConfig>::as_config()) + } + fn kind_message_types() -> HandleTypes where Self: Sized, diff --git a/plugins/plugin_log/Cargo.toml b/plugins/plugin_log/Cargo.toml index 2a66dd0a..ed93598f 100644 --- a/plugins/plugin_log/Cargo.toml +++ b/plugins/plugin_log/Cargo.toml @@ -14,5 +14,6 @@ tracing = "0.1" tokio-util = "0.7.0" thiserror = "1" toml = "0.5" +indoc = "1.0.6" tedge_api = { path = "../../crates/core/tedge_api" } diff --git a/plugins/plugin_log/src/lib.rs b/plugins/plugin_log/src/lib.rs index a2302395..bd081626 100644 --- a/plugins/plugin_log/src/lib.rs +++ b/plugins/plugin_log/src/lib.rs @@ -31,9 +31,55 @@ impl<MB: MessageBundle> LogPluginBuilder<MB> { } } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, serde::Deserialize)] +#[serde(transparent)] +struct LogLevel(log::Level); + +impl tedge_api::AsConfig for LogLevel { + fn as_config() -> tedge_api::ConfigDescription { + tedge_api::ConfigDescription::new( + String::from("Lo |