diff --git a/quake_core/src/parser/ast.rs b/quake_core/src/parser/ast.rs index 91dd0ebb..3fa241df 100644 --- a/quake_core/src/parser/ast.rs +++ b/quake_core/src/parser/ast.rs @@ -130,11 +130,11 @@ pub struct Parameter { #[derive(Clone, Debug, Eq, PartialEq, Default)] pub struct MapDecl { - pub streams: Vec, + pub streams: Vec, } #[derive(Clone, Debug, Eq, PartialEq, Default)] -pub struct MapStream { +pub struct MapExpr { pub(crate) source: String, pub(crate) target: String, pub(crate) pipes: Vec, @@ -142,6 +142,6 @@ pub struct MapStream { #[derive(Clone, Debug, Eq, PartialEq, Default)] pub struct MapPipe { - pub operators: String, + pub operator: String, pub params: Vec, } diff --git a/quake_core/src/parser/quake.rs b/quake_core/src/parser/quake.rs index b2fec50e..a0ba8ab3 100644 --- a/quake_core/src/parser/quake.rs +++ b/quake_core/src/parser/quake.rs @@ -1,8 +1,9 @@ use std::error::Error; +use std::fmt::{Display, Formatter}; use crate::helper::quake_time::string_date_to_unix; use crate::parser::ast::{ - SimpleLayoutDecl, SourceUnitPart, TransflowDecl, TransflowEnum, TransflowSource, + MapDecl, SimpleLayoutDecl, SourceUnitPart, TransflowDecl, TransflowEnum, TransflowSource, }; use crate::parser::errors::QuakeParserError; use crate::parser::quake_parser::parse; @@ -60,6 +61,8 @@ pub struct Route { pub to: String, #[serde(skip_serializing_if = "Option::is_none")] pub filter: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub map: Option>, #[serde(skip_serializing)] pub is_end_way: bool, } @@ -74,6 +77,36 @@ impl Route { } } +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)] +pub struct MapStream { + pub source: String, + pub target: String, + pub operators: Vec, +} + +impl Display for MapStream { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut str = String::new(); + for operator in &self.operators { + str.push_str(" | "); + str.push_str(operator.operator.as_str()); + if operator.params.len() > 0 { + str.push_str("("); + str.push_str(format!("{:}", operator.params.join(",")).as_str()); + str.push_str(")"); + } + } + + write!(f, "{:} -> {:}{:}", self.source, self.target, str) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)] +pub struct MapOperator { + pub operator: String, + pub params: Vec, +} + #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct QuakeActionNode { pub object: String, @@ -212,6 +245,9 @@ fn build_transflow(decl: TransflowDecl) -> QuakeTransflowNode { } route.filter = replace_rule(&way.filter); + if way.map.is_some() { + route.map = Some(streams_from_ast(way.map.as_ref().unwrap())); + } // build router rule route.naming(); @@ -229,6 +265,10 @@ fn build_transflow(decl: TransflowDecl) -> QuakeTransflowNode { _ => {} } + if way.map.is_some() { + route.map = Some(streams_from_ast(way.map.as_ref().unwrap())); + } + route.filter = replace_rule(&way.filter); // build router rule @@ -242,6 +282,27 @@ fn build_transflow(decl: TransflowDecl) -> QuakeTransflowNode { transflow } +fn streams_from_ast(map_decl: &MapDecl) -> Vec { + let mut streams = vec![]; + for stream in &map_decl.streams { + let mut map_stream = MapStream::default(); + map_stream.source = stream.source.clone(); + map_stream.target = stream.target.clone(); + + for pipe in &stream.pipes { + let mut operator = MapOperator::default(); + operator.operator = pipe.operator.clone(); + for param in &pipe.params { + operator.params.push(param.value.clone()); + } + map_stream.operators.push(operator); + } + streams.push(map_stream); + } + + streams +} + fn replace_rule(filter: &Option) -> Option { filter.as_ref().map(|str| string_date_to_unix(str)) } @@ -340,6 +401,20 @@ mod tests { ); } + #[test] + fn should_parse_filter_map() { + let define = "transflow show_calendar { + from('todo','blog').to() + .filter('created_date > 2021.01.01 AND created_date < 2021.12.31') + .map('blog.content => content | substring(1, 150)'); +}"; + let expr = QuakeTransflowNode::from_text(define).unwrap(); + assert_eq!( + format!("{:}", expr.routes[0].map.as_ref().unwrap()[0]), + "blog.content -> content | substring(1,150)" + ); + } + #[test] fn should_parse_layout() { let define = "layout Dashboard { diff --git a/quake_core/src/parser/quake_parser.rs b/quake_core/src/parser/quake_parser.rs index f1169963..e959cef1 100644 --- a/quake_core/src/parser/quake_parser.rs +++ b/quake_core/src/parser/quake_parser.rs @@ -4,9 +4,8 @@ use pest::iterators::Pair; use pest::Parser; use crate::parser::ast::{ - ActionDecl, Endway, FlowUrl, LayoutComponentNode, MapDecl, MapPipe, MapStream, Midway, - Parameter, SimpleLayoutDecl, SourceUnit, SourceUnitPart, TransflowDecl, TransflowEnum, - TransflowSource, + ActionDecl, Endway, FlowUrl, LayoutComponentNode, MapDecl, MapExpr, MapPipe, Midway, Parameter, + SimpleLayoutDecl, SourceUnit, SourceUnitPart, TransflowDecl, TransflowEnum, TransflowSource, }; use crate::parser::errors::QuakeParserError; @@ -273,8 +272,8 @@ fn map_decl(decl: Pair) -> MapDecl { map_decl } -fn map_expr(decl: Pair) -> MapStream { - let mut stream = MapStream::default(); +fn map_expr(decl: Pair) -> MapExpr { + let mut stream = MapExpr::default(); for pair in decl.into_inner() { match pair.as_rule() { Rule::source => { @@ -300,7 +299,7 @@ fn pipe_func(decl: Pair) -> MapPipe { for pair in decl.into_inner() { match pair.as_rule() { Rule::ident => { - pipe.operators = pair.as_str().to_string(); + pipe.operator = pair.as_str().to_string(); } Rule::parameters => { pipe.params = parameters(pair); @@ -404,7 +403,7 @@ pub fn replace_string_markers(input: &str) -> String { mod tests { use crate::parser::ast::TransflowSource::EntryTypes; use crate::parser::ast::{ - Endway, MapDecl, MapPipe, MapStream, Parameter, SourceUnit, SourceUnitPart, TransflowDecl, + Endway, MapDecl, MapExpr, MapPipe, Parameter, SourceUnit, SourceUnitPart, TransflowDecl, TransflowEnum, TransflowSource, }; use crate::parser::quake_parser::parse; @@ -502,16 +501,16 @@ mod tests { component: "quake-calendar".to_string(), filter: None, map: Some(MapDecl { - streams: vec![MapStream { + streams: vec![MapExpr { source: "blog.content".to_string(), target: "content".to_string(), pipes: vec![ MapPipe { - operators: "uppercase".to_string(), + operator: "uppercase".to_string(), params: vec![] }, MapPipe { - operators: "substring".to_string(), + operator: "substring".to_string(), params: vec![ Parameter { value: "1".to_string()