Add new type of action and general improvements
This commit is contained in:
parent
79f730b9c6
commit
5d347a260c
20 changed files with 554 additions and 66 deletions
27
backend/Cargo.lock
generated
27
backend/Cargo.lock
generated
|
@ -2,6 +2,15 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.0.0"
|
||||
|
@ -416,6 +425,7 @@ dependencies = [
|
|||
"async-trait",
|
||||
"clap",
|
||||
"log",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simplelog",
|
||||
|
@ -664,6 +674,23 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
|
|
|
@ -20,6 +20,8 @@ async-trait = "0.1.57"
|
|||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
regex = { version = "1" }
|
||||
|
||||
# logging
|
||||
log = "0.4"
|
||||
simplelog = "0.12"
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
use std::sync::mpsc::{TryRecvError, Receiver};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::mpsc::{RecvError, Receiver};
|
||||
|
||||
/// Receive on a blocking channel in an async manner (by polling conservatively)
|
||||
pub async fn channel_recv<T>(rx: Receiver<T>) -> Result<T, TryRecvError> {
|
||||
let sleep_duration = std::time::Duration::from_millis(10);
|
||||
let receiver = Mutex::new(rx);
|
||||
loop {
|
||||
let received = receiver.lock().unwrap().try_recv();
|
||||
match received {
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
return Err(TryRecvError::Disconnected);
|
||||
},
|
||||
Err(_) => {},
|
||||
Ok(x) => return Ok(x),
|
||||
}
|
||||
tokio::time::sleep(sleep_duration).await;
|
||||
}
|
||||
/// Receive on a blocking channel in an async manner
|
||||
pub async fn channel_recv<T: Send + 'static>(rx: Receiver<T>) -> Result<T, RecvError> {
|
||||
tokio::task::spawn_blocking(move || rx.recv()).await.map_err(|e| {
|
||||
log::error!("Async JoinError while receiving from sync channel: {}", e);
|
||||
RecvError
|
||||
})?
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::sync::{Mutex, mpsc::{Sender, channel, self}};
|
||||
use std::sync::{Mutex, mpsc::{Sender, channel}};
|
||||
|
||||
use usdpl_back::core::serdes::Primitive;
|
||||
use usdpl_back::AsyncCallable;
|
||||
|
@ -44,15 +44,14 @@ impl AsyncCallable for GetDisplayEndpoint {
|
|||
);
|
||||
match send_result {
|
||||
Ok(_) => {
|
||||
// TODO: don't poll for response
|
||||
log::info!("waiting for display for item #{}", index);
|
||||
match super::async_utils::channel_recv(receiver).await {
|
||||
Err(mpsc::TryRecvError::Disconnected) => {
|
||||
Err(_) => {
|
||||
let msg = format!("Failed to response for get_display for #{}", index);
|
||||
log::warn!("{}", msg);
|
||||
return vec![ApiDisplayResult::failure(msg, "receiving channel disconnected").to_primitive()];
|
||||
},
|
||||
Err(_) => return vec![], // impossible
|
||||
//Err(_) => return vec![], // impossible
|
||||
Ok(x) => {
|
||||
log::debug!("got display for item #{}", index);
|
||||
return vec![ApiDisplayResult::success(x).to_primitive()];
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde_json::Value;
|
|||
use usdpl_back::core::serdes::Primitive;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "element")]
|
||||
#[serde(tag = "result")]
|
||||
pub enum ApiDisplayResult {
|
||||
#[serde(rename = "value")]
|
||||
Value(ApiValue),
|
||||
|
|
|
@ -5,9 +5,16 @@ use serde::{Serialize, Deserialize};
|
|||
pub enum ActionConfig {
|
||||
#[serde(rename = "command")]
|
||||
Command(CommandAction),
|
||||
#[serde(rename = "transform")]
|
||||
Transform(super::TransformAction),
|
||||
#[serde(rename = "mirror")]
|
||||
Mirror(MirrorAction),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct CommandAction {
|
||||
pub run: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct MirrorAction;
|
||||
|
|
|
@ -21,12 +21,18 @@ impl BaseConfig {
|
|||
let reader = std::io::BufReader::new(file);
|
||||
match serde_json::from_reader(reader) {
|
||||
Ok(conf) => return conf,
|
||||
Err(e) => log::error!("Failed to deserialize {}: {}", path.display(), e),
|
||||
Err(e) => {
|
||||
log::error!("Failed to deserialize {}: {}", path.display(), e);
|
||||
panic!("Failed to deserialize {}: {}", path.display(), e)
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(e) => log::error!("Failed to open {}: {}", path.display(), e),
|
||||
Err(e) => {
|
||||
log::error!("Failed to open {}: {}", path.display(), e);
|
||||
panic!("Failed to open {}: {}", path.display(), e);
|
||||
}
|
||||
panic!("Cannot open {}", path.display())
|
||||
}
|
||||
//panic!("Cannot open {}", path.display())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -7,9 +7,10 @@ mod reading;
|
|||
mod result_display;
|
||||
mod slider;
|
||||
mod toggle;
|
||||
mod transformer;
|
||||
|
||||
pub use about::AboutConfig;
|
||||
pub use action::{ActionConfig, CommandAction};
|
||||
pub use action::{ActionConfig, CommandAction, MirrorAction};
|
||||
pub use base::BaseConfig;
|
||||
pub use button::ButtonConfig;
|
||||
pub use element::ElementConfig;
|
||||
|
@ -17,6 +18,7 @@ pub use reading::ReadingConfig;
|
|||
pub use result_display::ResultDisplayConfig;
|
||||
pub use slider::SliderConfig;
|
||||
pub use toggle::ToggleConfig;
|
||||
pub use transformer::{TransformAction, TransformTypeAction, ReplaceTransformAction, ExpandTransformAction, LogTransformAction, LogLevel, PatternConfig};
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
|
69
backend/src/config/transformer.rs
Normal file
69
backend/src/config/transformer.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
//! Transformers, robots in disguise! (or value-based transformations)
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::ActionConfig;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct TransformAction {
|
||||
pub target: Box<ActionConfig>,
|
||||
pub transformer: TransformTypeAction,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "rule")]
|
||||
pub enum TransformTypeAction {
|
||||
#[serde(rename = "pre-replace")]
|
||||
PreReplace(ReplaceTransformAction),
|
||||
#[serde(rename = "post-replace")]
|
||||
PostReplace(ReplaceTransformAction),
|
||||
#[serde(rename = "pre-expand")]
|
||||
PreExpand(ExpandTransformAction),
|
||||
#[serde(rename = "post-expand")]
|
||||
PostExpand(ExpandTransformAction),
|
||||
#[serde(rename = "log")]
|
||||
Log(LogTransformAction),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ReplaceTransformAction {
|
||||
/// Regex
|
||||
pub patterns: Vec<PatternConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct PatternConfig {
|
||||
/// Regex
|
||||
pub pattern: String,
|
||||
/// Formatting info https://docs.rs/regex/latest/regex/struct.Regex.html#replacement-string-syntax
|
||||
pub format: String,
|
||||
// Regex case_insensitive flags
|
||||
pub i: Option<bool>,
|
||||
// Regex multi_line flags
|
||||
pub m: Option<bool>,
|
||||
// Regex dot_matches_new_line flags
|
||||
pub s: Option<bool>,
|
||||
// Regex swap_greed flags
|
||||
#[serde(rename = "U")]
|
||||
pub u: Option<bool>,
|
||||
// Regex ignore_whitespace flags
|
||||
pub x: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ExpandTransformAction {
|
||||
pub format: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct LogTransformAction {
|
||||
pub level: LogLevel,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub enum LogLevel {
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
}
|
|
@ -5,11 +5,11 @@ use crate::config::{ElementConfig, ActionConfig};
|
|||
pub type ActError = String;
|
||||
|
||||
/// Something capable of performing an action.
|
||||
pub trait Act: Sized {
|
||||
pub trait Act<'a>: Sized + 'a {
|
||||
type Param;
|
||||
type Config: ?Sized;
|
||||
type Config: ?Sized + 'a;
|
||||
type Return;
|
||||
fn build(config: &Self::Config, parameter: Self::Param) -> Result<Self, ActError>;
|
||||
fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError>;
|
||||
fn run(self) -> Self::Return;
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,12 @@ pub struct Actor {
|
|||
index: usize,
|
||||
}
|
||||
|
||||
impl Act for Actor {
|
||||
impl<'a> Act<'a> for Actor {
|
||||
type Param = (usize, Primitive);
|
||||
type Config = ElementConfig;
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &ElementConfig, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
fn build(config: &'a ElementConfig, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
let a_type = match config {
|
||||
ElementConfig::Button(b) => ActorType::build(&b.on_click, parameter.1),
|
||||
ElementConfig::Toggle(t) => ActorType::build(&t.on_toggle, parameter.1),
|
||||
|
@ -48,23 +48,31 @@ impl Act for Actor {
|
|||
|
||||
pub enum ActorType {
|
||||
Command(super::CommandActor),
|
||||
Transform(super::TransformActor),
|
||||
Mirror(Primitive),
|
||||
}
|
||||
|
||||
impl Act for ActorType {
|
||||
impl<'a> Act<'a> for ActorType {
|
||||
type Param = Primitive;
|
||||
type Config = ActionConfig;
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
Ok(match config {
|
||||
ActionConfig::Command(c) =>
|
||||
Self::Command(super::CommandActor::build(c, parameter)?),
|
||||
ActionConfig::Transform(t) =>
|
||||
Self::Transform(super::TransformActor::build(t, parameter)?),
|
||||
ActionConfig::Mirror(_) =>
|
||||
Self::Mirror(parameter)
|
||||
})
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
match self {
|
||||
Self::Command(c) => c.run().into(),
|
||||
Self::Transform(t) => t.run(),
|
||||
Self::Mirror(p) => p,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,8 +5,6 @@ use usdpl_back::core::serdes::Primitive;
|
|||
use crate::config::CommandAction;
|
||||
use super::{Act, ActError};
|
||||
|
||||
const VALUE_ENV_VAR: &str = "KAYLON_VALUE";
|
||||
|
||||
/// Runs a CLI command in Bash
|
||||
pub struct CommandActor {
|
||||
shell: String,
|
||||
|
@ -31,12 +29,12 @@ impl CommandActor {
|
|||
}
|
||||
}
|
||||
|
||||
impl Act for CommandActor {
|
||||
impl<'a> Act<'a> for CommandActor {
|
||||
type Param = Primitive;
|
||||
type Config = CommandAction;
|
||||
type Return = String;
|
||||
|
||||
fn build(config: &CommandAction, parameter: Primitive) -> Result<Self, ActError> {
|
||||
fn build(config: &'a CommandAction, parameter: Primitive) -> Result<Self, ActError> {
|
||||
Ok(
|
||||
Self {
|
||||
shell: "bash".to_owned(),
|
||||
|
@ -49,14 +47,14 @@ impl Act for CommandActor {
|
|||
fn run(self) -> Self::Return {
|
||||
let output = Command::new(&self.shell)
|
||||
.args(["-c", &self.run])
|
||||
.env(VALUE_ENV_VAR, &self.variable)
|
||||
.env(super::VALUE_VAR, &self.variable)
|
||||
.output()
|
||||
.expect(&format!("Cannot run `{}`", &self.run));
|
||||
if !output.stderr.is_empty() {
|
||||
log::error!("Error running `{}`: {}", &self.run, String::from_utf8(output.stderr).unwrap_or_else(|_| "<non utf-8 stderr output>".to_owned()))
|
||||
}
|
||||
let result = String::from_utf8(output.stdout).expect(&format!("Cannot parse stdout from `{}` as UTF-8", self.run));
|
||||
log::debug!("CommandActor ran `{}` (${}=\"{}\") -> `{}`", &self.run, VALUE_ENV_VAR, &self.variable, &result);
|
||||
log::debug!("CommandActor ran `{}` (${}=\"{}\") -> `{}`", &self.run, super::VALUE_VAR, &self.variable, &result);
|
||||
result
|
||||
}
|
||||
}
|
11
backend/src/runtime/actors/mod.rs
Normal file
11
backend/src/runtime/actors/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
mod actor;
|
||||
mod command_actor;
|
||||
mod periodic_actor;
|
||||
mod transform_actor;
|
||||
|
||||
pub use actor::{Actor, Act, ActError, ActorType};
|
||||
pub use command_actor::CommandActor;
|
||||
pub use periodic_actor::PeriodicActor;
|
||||
pub use transform_actor::TransformActor;
|
||||
|
||||
pub const VALUE_VAR: &str = "KAYLON_VALUE";
|
|
@ -4,7 +4,8 @@ use std::time::Duration;
|
|||
use usdpl_back::core::serdes::Primitive;
|
||||
|
||||
use crate::config::ReadingConfig;
|
||||
use super::{Act, ActError, ActorType, RouterCommand};
|
||||
use super::{Act, ActError, ActorType};
|
||||
use crate::runtime::RouterCommand;
|
||||
|
||||
/// Runs an action periodically
|
||||
pub struct PeriodicActor {
|
||||
|
@ -13,12 +14,12 @@ pub struct PeriodicActor {
|
|||
index: usize,
|
||||
}
|
||||
|
||||
impl Act for PeriodicActor {
|
||||
impl<'a> Act<'a> for PeriodicActor {
|
||||
type Param = (usize, Sender<RouterCommand>);
|
||||
type Config = ReadingConfig;
|
||||
type Return = ();
|
||||
|
||||
fn build(config: &Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
ActorType::build(&config.on_period, Primitive::Empty)?;
|
||||
Ok(
|
||||
Self {
|
320
backend/src/runtime/actors/transform_actor.rs
Normal file
320
backend/src/runtime/actors/transform_actor.rs
Normal file
|
@ -0,0 +1,320 @@
|
|||
use regex::{Regex, RegexBuilder};
|
||||
use usdpl_back::core::serdes::Primitive;
|
||||
|
||||
use crate::runtime::primitive_utils;
|
||||
|
||||
use crate::config::{TransformAction, ActionConfig, TransformTypeAction, ReplaceTransformAction, ExpandTransformAction, LogTransformAction, LogLevel, PatternConfig};
|
||||
use super::{Act, ActError, ActorType};
|
||||
|
||||
/// Changes the output or input of an act
|
||||
pub enum TransformActor {
|
||||
PreReplace(PreReplaceTransformActor),
|
||||
PostReplace(PostReplaceTransformActor),
|
||||
PreExpand(PreExpandTransformActor),
|
||||
PostExpand(PostExpandTransformActor),
|
||||
Log(LogTransformActor),
|
||||
}
|
||||
|
||||
impl<'a> Act<'a> for TransformActor {
|
||||
type Param = Primitive;
|
||||
type Config = TransformAction;
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &'a TransformAction, parameter: Primitive) -> Result<Self, ActError> {
|
||||
let result = Ok(match &config.transformer {
|
||||
TransformTypeAction::PreReplace(x) =>
|
||||
Self::PreReplace(PreReplaceTransformActor::build(&(x, &config.target), parameter)?),
|
||||
TransformTypeAction::PostReplace(x) =>
|
||||
Self::PostReplace(PostReplaceTransformActor::build(&(x, &config.target), parameter)?),
|
||||
TransformTypeAction::PreExpand(x) =>
|
||||
Self::PreExpand(PreExpandTransformActor::build(&(x, &config.target), parameter)?),
|
||||
TransformTypeAction::PostExpand(x) =>
|
||||
Self::PostExpand(PostExpandTransformActor::build(&(x, &config.target), parameter)?),
|
||||
TransformTypeAction::Log(x) =>
|
||||
Self::Log(LogTransformActor::build(&(x, &config.target), parameter)?),
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
match self {
|
||||
Self::PreReplace(x) => x.run(),
|
||||
Self::PostReplace(x) => x.run(),
|
||||
Self::PreExpand(x) => x.run(),
|
||||
Self::PostExpand(x) => x.run(),
|
||||
Self::Log(x) => x.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct TransformPostActor {
|
||||
op_fn: Box<dyn (FnOnce(Primitive) -> Primitive) + Send>,
|
||||
actor: Box<ActorType>,
|
||||
}
|
||||
|
||||
impl<'a> Act<'a> for TransformPostActor {
|
||||
type Param = (Primitive, Box<dyn (FnOnce(Primitive) -> Primitive) + Send>);
|
||||
type Config = ActionConfig;
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
Ok(
|
||||
Self {
|
||||
op_fn: parameter.1,
|
||||
actor: Box::new(ActorType::build(config, parameter.0)?),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
(self.op_fn)(self.actor.run())
|
||||
}
|
||||
}
|
||||
|
||||
/// executes op_fn and ActionConfig::build in Actor::build()
|
||||
/// this blocks the main execution thread,
|
||||
/// but an Err() from ActionConfig::build will be be propogated correctly
|
||||
pub(super) struct TransformEagerPreActor {
|
||||
actor: Box<ActorType>,
|
||||
}
|
||||
|
||||
impl<'a> Act<'a> for TransformEagerPreActor {
|
||||
type Param = (Primitive, Box<dyn (FnOnce(Primitive) -> Primitive) + Send>);
|
||||
type Config = ActionConfig;
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
let primitive = (parameter.1)(parameter.0);
|
||||
Ok(
|
||||
Self {
|
||||
actor: Box::new(ActorType::build(config, primitive)?),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
self.actor.run()
|
||||
}
|
||||
}
|
||||
|
||||
/// executes op_fn and ActionConfig::build in Actor.run()
|
||||
/// this doesn't block the main execution thread,
|
||||
/// but an Err() from ActionConfig::build will produce an empty result
|
||||
pub(super) struct TransformLazyPreActor {
|
||||
op_fn: Box<dyn (FnOnce(Primitive) -> Primitive) + Send>,
|
||||
action: ActionConfig,
|
||||
primitive: Primitive,
|
||||
}
|
||||
|
||||
impl<'a> Act<'a> for TransformLazyPreActor {
|
||||
type Param = (Primitive, Box<dyn (FnOnce(Primitive) -> Primitive) + Send>);
|
||||
type Config = ActionConfig;
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
Ok(
|
||||
Self {
|
||||
op_fn: parameter.1,
|
||||
action: config.to_owned(),
|
||||
primitive: parameter.0,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
let primitive = (self.op_fn)(self.primitive);
|
||||
match ActorType::build(&self.action, primitive) {
|
||||
Ok(action) => action.run(),
|
||||
Err(e) => {
|
||||
log::error!("Failed to lazily build action for pre-transformer: {}", e);
|
||||
Primitive::Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PatternRule {
|
||||
pattern: Regex,
|
||||
format: String,
|
||||
}
|
||||
|
||||
impl PatternRule {
|
||||
#[inline]
|
||||
fn from_config(config: &PatternConfig) -> Result<Self, ActError> {
|
||||
let re = RegexBuilder::new(&config.pattern)
|
||||
.case_insensitive(config.i.unwrap_or(false))
|
||||
.multi_line(config.m.unwrap_or(false))
|
||||
.dot_matches_new_line(config.s.unwrap_or(false))
|
||||
.swap_greed(config.u.unwrap_or(false))
|
||||
.ignore_whitespace(config.x.unwrap_or(false))
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to compile regex `{}`: {}", config.pattern, e))?;
|
||||
Ok(Self {
|
||||
pattern: re,
|
||||
format: config.format.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_fn(config: &ReplaceTransformAction) -> Result<impl FnOnce(Primitive) -> Primitive, ActError> {
|
||||
let mut patterns: Vec<PatternRule> = Vec::with_capacity(config.patterns.len());
|
||||
for pattern in config.patterns.iter() {
|
||||
patterns.push(PatternRule::from_config(pattern)?);
|
||||
}
|
||||
|
||||
Ok(move |p| {
|
||||
let mut stringy = primitive_utils::display(p);
|
||||
for pattern in patterns {
|
||||
stringy = pattern.pattern.replace(&stringy, pattern.format).into_owned();
|
||||
}
|
||||
stringy.into()
|
||||
})
|
||||
}
|
||||
|
||||
pub struct PreReplaceTransformActor {
|
||||
transformer: TransformEagerPreActor,
|
||||
}
|
||||
|
||||
impl<'a> Act<'a> for PreReplaceTransformActor {
|
||||
type Param = Primitive;
|
||||
type Config = (&'a ReplaceTransformAction, &'a ActionConfig);
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
|
||||
Ok(
|
||||
Self {
|
||||
transformer: TransformEagerPreActor::build(
|
||||
config.1,
|
||||
(parameter,
|
||||
Box::new(replace_fn(config.0)?)
|
||||
))?,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
self.transformer.run()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PostReplaceTransformActor {
|
||||
transformer: TransformPostActor,
|
||||
}
|
||||
|
||||
impl<'a> Act<'a> for PostReplaceTransformActor {
|
||||
type Param = Primitive;
|
||||
type Config = (&'a ReplaceTransformAction, &'a ActionConfig);
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
|
||||
|
||||
Ok(
|
||||
Self {
|
||||
transformer: TransformPostActor::build(
|
||||
config.1,
|
||||
(parameter,
|
||||
Box::new(replace_fn(config.0)?)
|
||||
))?,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
self.transformer.run()
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_fn(config: &ExpandTransformAction) -> Result<impl FnOnce(Primitive) -> Primitive, ActError> {
|
||||
let format = config.format.clone();
|
||||
Ok(move |p| {
|
||||
let stringy = primitive_utils::display(p);
|
||||
let pattern1 = format!("${}", super::VALUE_VAR);
|
||||
let pattern2 = format!("${{{}}}", super::VALUE_VAR);
|
||||
format.replace(&pattern1, &pattern2).replace(&pattern2, &stringy).into()
|
||||
})
|
||||
}
|
||||
|
||||
pub struct PreExpandTransformActor {
|
||||
transformer: TransformLazyPreActor,
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Act<'a> for PreExpandTransformActor {
|
||||
type Param = Primitive;
|
||||
type Config = (&'a ExpandTransformAction, &'a ActionConfig);
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
|
||||
Ok(
|
||||
Self {
|
||||
transformer: TransformLazyPreActor::build(
|
||||
config.1,
|
||||
(parameter,
|
||||
Box::new(expand_fn(config.0)?)
|
||||
))?,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
self.transformer.run()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PostExpandTransformActor {
|
||||
transformer: TransformPostActor,
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Act<'a> for PostExpandTransformActor {
|
||||
type Param = Primitive;
|
||||
type Config = (&'a ExpandTransformAction, &'a ActionConfig);
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
|
||||
Ok(
|
||||
Self {
|
||||
transformer: TransformPostActor::build(
|
||||
config.1,
|
||||
(parameter,
|
||||
Box::new(expand_fn(config.0)?)
|
||||
))?,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
self.transformer.run()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LogTransformActor {
|
||||
generic: TransformPostActor,
|
||||
}
|
||||
|
||||
impl<'a> Act<'a> for LogTransformActor {
|
||||
type Param = Primitive;
|
||||
type Config = (&'a LogTransformAction, &'a ActionConfig);
|
||||
type Return = Primitive;
|
||||
|
||||
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
|
||||
Ok(
|
||||
Self {
|
||||
generic: TransformPostActor::build(
|
||||
config.1,
|
||||
(parameter,
|
||||
match config.0.level {
|
||||
LogLevel::DEBUG => Box::new(|p| {log::debug!("{}", primitive_utils::debug(&p));p}),
|
||||
LogLevel::INFO => Box::new(|p| {log::info!("{}", primitive_utils::debug(&p));p}),
|
||||
LogLevel::WARN => Box::new(|p| {log::warn!("{}", primitive_utils::debug(&p));p}),
|
||||
LogLevel::ERROR => Box::new(|p| {log::error!("{}", primitive_utils::debug(&p));p}),
|
||||
}
|
||||
))?,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn run(self) -> Self::Return {
|
||||
self.generic.run()
|
||||
}
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
mod actor;
|
||||
mod command_actor;
|
||||
mod actors;
|
||||
mod communication;
|
||||
mod executor;
|
||||
mod periodic_actor;
|
||||
mod primitive_utils;
|
||||
mod result_router;
|
||||
|
||||
pub use actor::{Actor, Act, ActError, ActorType};
|
||||
pub use command_actor::CommandActor;
|
||||
pub use actors::*;
|
||||
pub use communication::{QueueItem, QueueAction};
|
||||
pub use executor::RuntimeExecutor;
|
||||
pub use periodic_actor::PeriodicActor;
|
||||
pub use result_router::{ResultRouter, RouterCommand};
|
||||
|
|
|
@ -51,7 +51,7 @@ pub fn debug(primitive: &Primitive) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
/*#[inline]
|
||||
#[inline]
|
||||
pub fn display(primitive: Primitive) -> String {
|
||||
match primitive {
|
||||
Primitive::Empty => "".to_owned(),
|
||||
|
@ -65,7 +65,7 @@ pub fn display(primitive: Primitive) -> String {
|
|||
Primitive::Bool(x) => x.to_string(),
|
||||
Primitive::Json(x) => x,
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clone(primitive: &Primitive) -> Primitive {
|
||||
|
|
|
@ -40,12 +40,12 @@ impl ResultRouter {
|
|||
}
|
||||
}
|
||||
|
||||
impl Act for ResultRouter {
|
||||
impl<'a> Act<'a> for ResultRouter {
|
||||
type Param = usize;
|
||||
type Config = ();
|
||||
type Return = Sender<RouterCommand>;
|
||||
|
||||
fn build(_config: &Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
fn build(_config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mut cache_vec = Vec::with_capacity(parameter);
|
||||
for _ in 0..parameter {
|
||||
|
|
33
kaylon.json
33
kaylon.json
|
@ -54,8 +54,41 @@
|
|||
"title": "Fan Speed",
|
||||
"period_ms": 1000,
|
||||
"on_period": {
|
||||
"action": "transform",
|
||||
"target": {
|
||||
"action": "command",
|
||||
"run": "cat /sys/class/hwmon/hwmon5/fan1_input"
|
||||
},
|
||||
"transformer": {
|
||||
"rule": "post-expand",
|
||||
"format": "$KAYLON_VALUE RPM"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"element": "toggle",
|
||||
"title": "Lighthouses",
|
||||
"description": "Turn Valve Index Lighthouses on",
|
||||
"on_toggle": {
|
||||
"action": "transform",
|
||||
"target": {
|
||||
"action": "transform",
|
||||
"target": {
|
||||
"action": "command",
|
||||
"run": "python3 ./bin/lighthouse_ctrl.py ${KAYLON_VALUE}"
|
||||
},
|
||||
"transformer": {
|
||||
"rule": "post-replace",
|
||||
"patterns": [{"pattern": ".*", "format": "Done", "s": true}]
|
||||
}
|
||||
},
|
||||
"transformer": {
|
||||
"rule": "pre-replace",
|
||||
"patterns": [
|
||||
{"pattern": "TRUE", "format": "on", "i": true},
|
||||
{"pattern": "FALSE", "format": "off", "i": true}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -67,13 +67,13 @@ export type CResultDisplay = {
|
|||
export type CElement = CButton | CToggle | CSlider | CReading | CResultDisplay;
|
||||
|
||||
export type CErrorResult = {
|
||||
element: string; // "error"
|
||||
result: string; // "error"
|
||||
message: string;
|
||||
exception: string;
|
||||
}
|
||||
|
||||
export type CValueResult = {
|
||||
element: string; // "value"
|
||||
result: string; // "value"
|
||||
value: any;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,10 +32,12 @@ let about: backend.CAbout | null = null;
|
|||
|
||||
let update = () => {};
|
||||
|
||||
let updateTasks: (() => void)[] = [];
|
||||
|
||||
function displayCallback(index: number) {
|
||||
return (newVal: backend.CDisplayResponse) => {
|
||||
if (newVal != null) {
|
||||
switch (newVal.element) {
|
||||
switch (newVal.result) {
|
||||
case "value":
|
||||
let val = newVal as backend.CValueResult;
|
||||
console.log("KAYLON: Got display for " + index.toString(), val);
|
||||
|
@ -52,11 +54,18 @@ function displayCallback(index: number) {
|
|||
} else {
|
||||
console.warn("KAYLON: Ignoring null display result for " + index.toString());
|
||||
}
|
||||
backend.resolve(backend.getDisplay(index), displayCallback(index));
|
||||
updateTasks.push(() => backend.resolve(backend.getDisplay(index), displayCallback(index)));
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
function onGetElements() {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
console.log("KAYLON: req display for item #" + i.toString());
|
||||
backend.resolve(backend.getDisplay(i), displayCallback(i));
|
||||
}
|
||||
}
|
||||
|
||||
// init USDPL WASM frontend
|
||||
// this is required to interface with the backend
|
||||
(async () => {
|
||||
|
@ -68,11 +77,8 @@ function displayCallback(index: number) {
|
|||
let result = await elements_promise;
|
||||
console.log("KAYLON: got elements", result);
|
||||
if (result != null) {
|
||||
items = await backend.getElements();
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
console.log("KAYLON: req display for item #" + i.toString());
|
||||
backend.resolve(backend.getDisplay(i), displayCallback(i));
|
||||
}
|
||||
items = result;
|
||||
onGetElements();
|
||||
} else {
|
||||
console.warn("KAYLON: backend connection failed");
|
||||
}
|
||||
|
@ -90,6 +96,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
|||
update();
|
||||
}
|
||||
|
||||
// perform tasks (like updating display elements) only while rendering the plugin
|
||||
let taskItem = updateTasks.pop();
|
||||
while (taskItem != undefined) {
|
||||
taskItem();
|
||||
taskItem = updateTasks.pop();
|
||||
}
|
||||
|
||||
return (
|
||||
<PanelSection>
|
||||
{items.map(
|
||||
|
@ -106,6 +119,12 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
|||
(reload_items: backend.CElement[]) => {
|
||||
items = reload_items;
|
||||
console.log("KAYLON: got elements", reload_items);
|
||||
if (reload_items != null) {
|
||||
items = reload_items;
|
||||
onGetElements();
|
||||
} else {
|
||||
console.warn("KAYLON: backend connection failed");
|
||||
}
|
||||
update();
|
||||
});
|
||||
backend.resolve(backend.getAbout(),
|
||||
|
|
Reference in a new issue