use crate::objects::connection::{Connection, ConnectionType};
use crate::objects::node::{ActionType, Node};
use crate::syntax::{CoreSyntaxFunctions, FlowDirection, SyntaxConfigFile};
use enum_as_inner::EnumAsInner;
use strum::EnumProperty;
use strum_macros::EnumProperty;
#[derive(EnumProperty, Debug)]
pub enum Shape {
#[strum(props(Left = "((", Right = "))"))]
Circle,
#[strum(props(Left = "{{", Right = "}}"))]
Hexagon,
#[strum(props(Left = "[", Right = "]"))]
Rectangle,
#[strum(props(Left = ">", Right = "]"))]
Flag,
}
#[derive(EnumProperty, Debug)]
pub enum LineType {
#[strum(props(Complete = "--", Addition = "-"))]
Solid,
#[strum(props(Left = "-.", Right = ".-", Addition = "."))]
Dashed,
}
#[derive(AsRefStr, EnumProperty, Debug, Clone, Copy)]
pub enum ArrowType {
#[strum(props(Left = "<", Right = ">"))]
Standard,
#[strum(props(Left = "x", Right = "x"))]
X,
#[strum(props(Left = "o", Right = "o"))]
O,
}
#[derive(AsRefStr, Debug)]
pub enum ArrowDirection {
BiDirectional,
Left,
Right,
None,
}
#[derive(EnumProperty, EnumAsInner, Debug)]
pub enum ObjectConfig<'a> {
NodeConfig(NodeConfig<'a>),
ConnectionConfig(ConnectionConfig),
}
#[derive(Debug)]
pub struct NodeConfig<'a> {
pub id: &'a str,
pub class: Option<String>,
pub shape: Shape,
pub inner_text: &'a str,
}
#[derive(Debug)]
pub struct ConnectionConfig {
pub line_type: LineType,
pub arrow_type: ArrowType,
pub arrow_direction: ArrowDirection,
pub extra_length_num: Option<u8>,
}
pub struct FlowChart {
data: String,
}
impl FlowChart {
fn add_dashed_line(
&mut self,
extra_length_num: Option<u8>,
) {
self.data
.push_str(LineType::Dashed.get_str("Left").unwrap());
if let Some(extra_length_num) = extra_length_num {
for _ in 0..extra_length_num {
self.data
.push_str(LineType::Dashed.get_str("Addition").unwrap());
}
}
self.data
.push_str(LineType::Dashed.get_str("Right").unwrap());
}
fn add_solid_line(
&mut self,
extra_length_num: Option<u8>,
) {
self.data
.push_str(LineType::Solid.get_str("Complete").unwrap());
if let Some(extra_length_num) = extra_length_num {
for _ in 0..extra_length_num {
self.data
.push_str(LineType::Solid.get_str("Addition").unwrap());
}
}
}
fn add_line(
&mut self,
line_type: LineType,
extra_length_num: Option<u8>,
) {
match line_type {
LineType::Solid => self.add_solid_line(extra_length_num),
LineType::Dashed => self.add_dashed_line(extra_length_num),
}
}
fn add_arrow(
&mut self,
arrow_type: ArrowType,
arrow_direction: ArrowDirection,
) {
self.data
.push_str(arrow_type.get_str(arrow_direction.as_ref()).unwrap())
}
fn get_shape_from_node(
&self,
node: &Node,
) -> Shape {
match node.action {
ActionType::Mutation => Shape::Hexagon,
ActionType::View => Shape::Circle,
ActionType::Process => Shape::Rectangle,
ActionType::Event => Shape::Flag,
ActionType::None => Shape::Rectangle,
}
}
fn get_line_and_arrow_type_from_connection(
&self,
connection: &Connection,
) -> (LineType, ArrowType, ArrowDirection) {
match connection.connection_type {
ConnectionType::DirectConnection => {
(LineType::Solid, ArrowType::Standard, ArrowDirection::Right)
}
ConnectionType::CrossContractConnection => {
(LineType::Dashed, ArrowType::Standard, ArrowDirection::Right)
}
ConnectionType::Emission => {
(LineType::Dashed, ArrowType::O, ArrowDirection::None)
}
}
}
}
impl CoreSyntaxFunctions for FlowChart {
fn new(direction: FlowDirection) -> Self {
let mut schema_root = "flowchart ".to_string();
schema_root.push_str(direction.as_ref());
let mut result = FlowChart { data: schema_root };
result.add_linebreak(None);
result
}
fn add_node(
&mut self,
node_config: SyntaxConfigFile,
) {
let node_config: NodeConfig = node_config
.into_flow_chart()
.unwrap()
.into_node_config()
.unwrap();
self.data.push_str(node_config.id);
self.data
.push_str(node_config.shape.get_str("Left").unwrap());
self.data.push_str(node_config.inner_text);
self.data
.push_str(node_config.shape.get_str("Right").unwrap());
if let Some(class) = node_config.class {
self.data.push_str(":::");
self.data.push_str(class.as_str());
}
}
fn add_connection(
&mut self,
connection_config: SyntaxConfigFile,
) {
let connection_config: ConnectionConfig = connection_config
.into_flow_chart()
.unwrap()
.into_connection_config()
.unwrap();
self.data.push(' ');
match connection_config.arrow_direction {
ArrowDirection::BiDirectional => {
self.add_arrow(connection_config.arrow_type, ArrowDirection::Left);
self.add_line(
connection_config.line_type,
connection_config.extra_length_num,
);
self.add_arrow(connection_config.arrow_type, ArrowDirection::Right)
}
ArrowDirection::Left => {
self.add_arrow(
connection_config.arrow_type,
connection_config.arrow_direction,
);
self.add_line(
connection_config.line_type,
connection_config.extra_length_num,
)
}
ArrowDirection::Right => {
self.add_line(
connection_config.line_type,
connection_config.extra_length_num,
);
self.add_arrow(
connection_config.arrow_type,
connection_config.arrow_direction,
)
}
ArrowDirection::None => {
self.add_line(
connection_config.line_type,
connection_config.extra_length_num,
);
}
}
self.data.push(' ');
}
fn add_linebreak(
&mut self,
num_of_indents: Option<u8>,
) {
let number_of_indents = num_of_indents.unwrap_or(1);
self.data += "\n";
for _ in 0..number_of_indents {
self.data += "\t";
}
}
fn build_node_config<'a>(
&self,
node: &'a Node,
id: Option<&'a str>,
) -> SyntaxConfigFile<'a> {
if let Some(id) = id {
SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id,
class: Some(format!("{}-{}",node.scope.as_ref(),node.action.as_ref())),
shape: self.get_shape_from_node(node),
inner_text: &node.name,
}))
} else {
SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: &node.name,
class: Some(format!("{}-{}",node.scope.as_ref(),node.action.as_ref())),
shape: self.get_shape_from_node(node),
inner_text: &node.name,
}))
}
}
fn build_connection_config<'a>(
&self,
connection: &'a Connection,
extra_length_num: Option<u8>,
) -> SyntaxConfigFile<'a> {
let (line_type, arrow_type, arrow_direction) =
self.get_line_and_arrow_type_from_connection(connection);
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type,
arrow_type,
arrow_direction,
extra_length_num,
}))
}
fn return_schema(&self) -> String {
self.data.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_creates_a_circle() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Circle,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A((inner text))";
println!("{}", flow_chart.data);
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_creates_a_rectangle() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Rectangle,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A[inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_creates_a_hexagon() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Hexagon,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A{{inner text}}";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_creates_a_flag() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_right_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::Standard,
arrow_direction: ArrowDirection::Right,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] -..-> B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_no_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::Standard,
arrow_direction: ArrowDirection::None,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] -..- B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_left_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::Standard,
arrow_direction: ArrowDirection::Left,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] <-..- B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_bidirectional_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::Standard,
arrow_direction: ArrowDirection::BiDirectional,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] <-..-> B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_right_o_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::O,
arrow_direction: ArrowDirection::Right,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] -..-o B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_left_o_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::O,
arrow_direction: ArrowDirection::Left,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] o-..- B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_bidirectional_o_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::O,
arrow_direction: ArrowDirection::BiDirectional,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] o-..-o B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_right_x_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::X,
arrow_direction: ArrowDirection::Right,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] -..-x B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_left_x_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::X,
arrow_direction: ArrowDirection::Left,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] x-..- B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_bidirectional_x_arrow() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::X,
arrow_direction: ArrowDirection::BiDirectional,
extra_length_num: None,
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] x-..-x B>inner text]";
assert_eq!(flow_chart.data, expected);
}
#[test]
fn it_adds_a_dashed_line_with_bidirectional_x_arrow_and_extra_length() {
let mut flow_chart = FlowChart::new(FlowDirection::TD);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "A",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let connection_config =
SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig {
line_type: LineType::Dashed,
arrow_type: ArrowType::X,
arrow_direction: ArrowDirection::BiDirectional,
extra_length_num: Some(1),
}));
flow_chart.add_connection(connection_config);
let node_config = SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig {
id: "B",
class: None,
shape: Shape::Flag,
inner_text: "inner text",
}));
flow_chart.add_node(node_config);
let expected = r"flowchart TD
A>inner text] x-...-x B>inner text]";
assert_eq!(flow_chart.data, expected);
}
}