Added FSM rendering via graphviz
This commit is contained in:
@@ -1,35 +1,33 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::{fs};
|
||||
|
||||
use std::fs;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub enum UserConfigItem {
|
||||
String {
|
||||
name: String,
|
||||
max_length: u64
|
||||
max_length: u64,
|
||||
},
|
||||
IPv4 {
|
||||
name: String,
|
||||
},
|
||||
Boolean {
|
||||
name: String
|
||||
name: String,
|
||||
},
|
||||
Number {
|
||||
name: String,
|
||||
max: Option<i32>,
|
||||
min: Option<i32>
|
||||
min: Option<i32>,
|
||||
},
|
||||
Submenu(UserConfigData)
|
||||
Submenu(UserConfigData),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct UserConfigData {
|
||||
pub name: String,
|
||||
pub config_items: Vec<UserConfigItem>
|
||||
pub config_items: Vec<UserConfigItem>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ConfigotronError {
|
||||
#[error("IO Operation failed: {}", ._0.to_string())]
|
||||
@@ -39,16 +37,16 @@ pub enum ConfigotronError {
|
||||
YamlParsingError(#[from] serde_yml::Error),
|
||||
|
||||
#[error("Missing field {} (line {} col {}) {}", .missing_filed, .line, .col, .context)]
|
||||
MissingFieldError{
|
||||
MissingFieldError {
|
||||
col: i32,
|
||||
line: i32,
|
||||
missing_filed: Box<str>,
|
||||
context: Box<str>
|
||||
}
|
||||
context: Box<str>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn read_and_parse_yaml_file(input_file: &str) -> Result<UserConfigData, ConfigotronError> {
|
||||
let file_content = fs::read_to_string(&input_file)?;
|
||||
let config: UserConfigData = serde_yml::from_str(&file_content)?;
|
||||
return Ok(config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use std::{fmt::format, usize};
|
||||
use pad::PadStr;
|
||||
use std::usize;
|
||||
|
||||
use crate::configotron::{conf::{UserConfigData, UserConfigItem}, doc::grid};
|
||||
use crate::configotron::conf::{UserConfigData, UserConfigItem};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum DocGridCellType{
|
||||
pub enum DocGridCellType {
|
||||
Boarder,
|
||||
Empty,
|
||||
SubMenu { text: Vec<String> },
|
||||
Item { text: Vec<String> },
|
||||
ItemJoin
|
||||
Empty,
|
||||
SubMenu { text: Vec<String> },
|
||||
Item { text: Vec<String> },
|
||||
ItemJoin,
|
||||
}
|
||||
|
||||
impl DocGridCellType {
|
||||
@@ -19,44 +19,51 @@ impl DocGridCellType {
|
||||
DocGridCellType::Item { .. } => "I".to_string(),
|
||||
DocGridCellType::ItemJoin => "i".to_string(),
|
||||
DocGridCellType::SubMenu { .. } => "S".to_string(),
|
||||
DocGridCellType::Boarder => "B".to_string()
|
||||
DocGridCellType::Boarder => "B".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResultingGridCell {
|
||||
Grid(DocGrid),
|
||||
Cell(DocGridCellType)
|
||||
Cell(DocGridCellType),
|
||||
}
|
||||
|
||||
fn split_string(s: &String) -> Vec<String> {
|
||||
s.split('\n').map(|s| s.to_string()).collect::<Vec<String>>()
|
||||
}
|
||||
s.split('\n')
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
fn user_config_item_to_grid_cells(item: &UserConfigItem) -> ResultingGridCell {
|
||||
|
||||
match item {
|
||||
UserConfigItem::String { name, max_length } => {
|
||||
ResultingGridCell::Cell(DocGridCellType::Item { text: vec![
|
||||
name.to_string(), format!("[length: 0..{}]", max_length)
|
||||
]}
|
||||
)},
|
||||
UserConfigItem::IPv4 { name } => ResultingGridCell::Cell(DocGridCellType::Item { text: vec![
|
||||
name.to_string(), "[Address IPv4]".to_string()
|
||||
]}),
|
||||
UserConfigItem::Boolean { name } => ResultingGridCell::Cell(DocGridCellType::Item { text: vec![
|
||||
name.to_string(), "[Yes/No]".to_string()
|
||||
] }),
|
||||
UserConfigItem::Number { name, max, min } => ResultingGridCell::Cell(DocGridCellType::Item { text: vec![
|
||||
name.to_string(),
|
||||
match (min, max) {
|
||||
(None, None) => "[Integers]".to_string(),
|
||||
(None, Some(h)) => format!("[Integers: ..{}]", h),
|
||||
(Some(l), None) => format!("[Integers: {}..]", l),
|
||||
(Some(l), Some(h)) => format!("[Integers: {}..{}]", l, h),
|
||||
}
|
||||
] }),
|
||||
UserConfigItem::Submenu(user_config_data) => ResultingGridCell::Grid(DocGrid::from(user_config_data)),
|
||||
ResultingGridCell::Cell(DocGridCellType::Item {
|
||||
text: vec![name.to_string(), format!("[length: 0..{}]", max_length)],
|
||||
})
|
||||
}
|
||||
UserConfigItem::IPv4 { name } => ResultingGridCell::Cell(DocGridCellType::Item {
|
||||
text: vec![name.to_string(), "[Address IPv4]".to_string()],
|
||||
}),
|
||||
UserConfigItem::Boolean { name } => ResultingGridCell::Cell(DocGridCellType::Item {
|
||||
text: vec![name.to_string(), "[Yes/No]".to_string()],
|
||||
}),
|
||||
UserConfigItem::Number { name, max, min } => {
|
||||
ResultingGridCell::Cell(DocGridCellType::Item {
|
||||
text: vec![
|
||||
name.to_string(),
|
||||
match (min, max) {
|
||||
(None, None) => "[Integers]".to_string(),
|
||||
(None, Some(h)) => format!("[Integers: ..{}]", h),
|
||||
(Some(l), None) => format!("[Integers: {}..]", l),
|
||||
(Some(l), Some(h)) => format!("[Integers: {}..{}]", l, h),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
UserConfigItem::Submenu(user_config_data) => {
|
||||
ResultingGridCell::Grid(DocGrid::from(user_config_data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,12 +71,16 @@ fn user_config_item_to_grid_cells(item: &UserConfigItem) -> ResultingGridCell {
|
||||
pub struct DocGrid {
|
||||
rows: usize,
|
||||
cols: usize,
|
||||
array_cells: Vec<DocGridCellType>
|
||||
array_cells: Vec<DocGridCellType>,
|
||||
}
|
||||
|
||||
impl DocGrid {
|
||||
pub fn new() -> Self {
|
||||
Self { rows: 0, cols: 2, array_cells: Vec::new() }
|
||||
Self {
|
||||
rows: 0,
|
||||
cols: 2,
|
||||
array_cells: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
@@ -81,23 +92,25 @@ impl DocGrid {
|
||||
}
|
||||
|
||||
pub fn set_title(&mut self, cell: DocGridCellType) {
|
||||
self.array_cells.resize(self.len() + self.cols, DocGridCellType::Empty);
|
||||
self.array_cells
|
||||
.resize(self.len() + self.cols, DocGridCellType::Empty);
|
||||
self.array_cells[0] = cell;
|
||||
}
|
||||
|
||||
pub fn append_item(&mut self, item: ResultingGridCell) {
|
||||
match item {
|
||||
ResultingGridCell::Cell(cell_type) => {
|
||||
self.array_cells.resize(self.len() + self.cols, DocGridCellType::Empty);
|
||||
self.array_cells[self.rows*self.cols + 1] = cell_type;
|
||||
|
||||
self.array_cells
|
||||
.resize(self.len() + self.cols, DocGridCellType::Empty);
|
||||
self.array_cells[self.rows * self.cols + 1] = cell_type;
|
||||
|
||||
self.rows += 1;
|
||||
},
|
||||
}
|
||||
ResultingGridCell::Grid(grid) => {
|
||||
let new_col_size = std::cmp::max(self.cols, grid.cols + 1);
|
||||
let new_row_size = self.rows + grid.rows;
|
||||
self.array_cells.resize(new_col_size * new_row_size, DocGridCellType::Empty);
|
||||
|
||||
self.array_cells
|
||||
.resize(new_col_size * new_row_size, DocGridCellType::Empty);
|
||||
|
||||
let row_offset = self.rows;
|
||||
let old_cols = self.cols;
|
||||
@@ -121,10 +134,8 @@ impl DocGrid {
|
||||
// Copy in new stuff
|
||||
for row_index in 0..grid.rows {
|
||||
for col_index in 0..grid.cols {
|
||||
let (target_row_index, target_col_index) = (
|
||||
row_index + row_offset,
|
||||
col_index + 1
|
||||
);
|
||||
let (target_row_index, target_col_index) =
|
||||
(row_index + row_offset, col_index + 1);
|
||||
|
||||
let target_index = target_col_index + target_row_index * self.cols;
|
||||
let src_index = row_index * grid.cols + col_index;
|
||||
@@ -142,7 +153,10 @@ impl DocGrid {
|
||||
for row_index in 0..self.rows {
|
||||
s.push_str("| ");
|
||||
for col_index in 0..self.cols {
|
||||
s.push_str(&format!("{} ", self.array_cells[row_index * self.cols + col_index].to_debug_string()));
|
||||
s.push_str(&format!(
|
||||
"{} ",
|
||||
self.array_cells[row_index * self.cols + col_index].to_debug_string()
|
||||
));
|
||||
}
|
||||
s.push_str("|\n")
|
||||
}
|
||||
@@ -152,21 +166,19 @@ impl DocGrid {
|
||||
|
||||
fn row_size_max(&self, c: usize) -> usize {
|
||||
let max = (0..self.rows)
|
||||
.map(|r| { match &self.array_cells[r*self.cols + c] {
|
||||
.map(|r| match &self.array_cells[r * self.cols + c] {
|
||||
DocGridCellType::Boarder => 1,
|
||||
DocGridCellType::Empty => 1,
|
||||
DocGridCellType::SubMenu { text: name } => {
|
||||
name
|
||||
.iter()
|
||||
.map(|s| s.len()).max().unwrap_or(0)
|
||||
},
|
||||
name.iter().map(|s| s.len()).max().unwrap_or(0)
|
||||
}
|
||||
DocGridCellType::Item { text: name } => {
|
||||
name
|
||||
.iter()
|
||||
.map(|s| s.len()).max().unwrap_or(0)
|
||||
},
|
||||
name.iter().map(|s| s.len()).max().unwrap_or(0)
|
||||
}
|
||||
DocGridCellType::ItemJoin => 1,
|
||||
} }).max().unwrap_or(1);
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
|
||||
max
|
||||
}
|
||||
@@ -181,17 +193,15 @@ impl DocGrid {
|
||||
|
||||
fn col_size_max(&self, r: usize) -> usize {
|
||||
let max = (0..self.cols)
|
||||
.map(|c| { match &self.array_cells[r*self.cols + c] {
|
||||
.map(|c| match &self.array_cells[r * self.cols + c] {
|
||||
DocGridCellType::Boarder => 1,
|
||||
DocGridCellType::Empty => 1,
|
||||
DocGridCellType::SubMenu { text: name } => {
|
||||
name.len()
|
||||
},
|
||||
DocGridCellType::Item { text: name } => {
|
||||
name.len()
|
||||
},
|
||||
DocGridCellType::SubMenu { text: name } => name.len(),
|
||||
DocGridCellType::Item { text: name } => name.len(),
|
||||
DocGridCellType::ItemJoin => 1,
|
||||
} }).max().unwrap_or(1);
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
|
||||
max
|
||||
}
|
||||
@@ -208,7 +218,9 @@ impl DocGrid {
|
||||
impl From<&UserConfigData> for DocGrid {
|
||||
fn from(value: &UserConfigData) -> Self {
|
||||
let mut res = DocGrid::new();
|
||||
res.set_title(DocGridCellType::SubMenu { text: split_string(&value.name) });
|
||||
res.set_title(DocGridCellType::SubMenu {
|
||||
text: split_string(&value.name),
|
||||
});
|
||||
value.config_items.iter().for_each(|i| {
|
||||
res.append_item(user_config_item_to_grid_cells(i));
|
||||
});
|
||||
@@ -217,31 +229,34 @@ impl From<&UserConfigData> for DocGrid {
|
||||
for row_index in 0..res.rows {
|
||||
for col_index in 2..res.cols {
|
||||
let index = col_index + row_index * res.cols;
|
||||
match res.array_cells[col_index-1 + row_index * res.cols] {
|
||||
match res.array_cells[col_index - 1 + row_index * res.cols] {
|
||||
DocGridCellType::Item { .. } => {
|
||||
res.array_cells[index] = DocGridCellType::ItemJoin;
|
||||
},
|
||||
}
|
||||
DocGridCellType::ItemJoin => {
|
||||
res.array_cells[index] = DocGridCellType::ItemJoin;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DocGridLineType {
|
||||
NONE, VERTICAL, HORIZONTAL
|
||||
NONE,
|
||||
VERTICAL,
|
||||
HORIZONTAL,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DocGridLineCrossType {
|
||||
NONE, FULL, VERTICAL, HORIZONTAL
|
||||
NONE,
|
||||
FULL,
|
||||
VERTICAL,
|
||||
HORIZONTAL,
|
||||
}
|
||||
|
||||
pub struct DocGridLayout {
|
||||
@@ -251,18 +266,17 @@ pub struct DocGridLayout {
|
||||
lines_cross: Vec<DocGridLineCrossType>,
|
||||
}
|
||||
|
||||
|
||||
impl DocGridLayout {
|
||||
fn map_line_to_char(l: &DocGridLineType) -> String {
|
||||
match l {
|
||||
DocGridLineType::HORIZONTAL => "-".to_string(),
|
||||
DocGridLineType::NONE => " ".to_string(),
|
||||
DocGridLineType::VERTICAL => "|".to_string()
|
||||
DocGridLineType::VERTICAL => "|".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_cross_to_chr(c: &DocGridLineCrossType) -> String {
|
||||
match c {
|
||||
match c {
|
||||
DocGridLineCrossType::NONE => " ".to_string(),
|
||||
DocGridLineCrossType::FULL => "+".to_string(),
|
||||
DocGridLineCrossType::VERTICAL => "|".to_string(),
|
||||
@@ -270,7 +284,7 @@ impl DocGridLayout {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_vert_line (&self, r: i32, c: i32) -> &DocGridLineType {
|
||||
fn get_vert_line(&self, r: i32, c: i32) -> &DocGridLineType {
|
||||
if r < 0 || r >= (self.grid.rows as i32) || c < 0 || c > (self.grid.cols as i32) {
|
||||
&DocGridLineType::NONE
|
||||
} else {
|
||||
@@ -282,7 +296,7 @@ impl DocGridLayout {
|
||||
self.lines_vert[r as usize * (self.grid.cols + 1) + c as usize] = v;
|
||||
}
|
||||
|
||||
fn get_hort_line (&self, r: i32, c: i32) -> &DocGridLineType {
|
||||
fn get_hort_line(&self, r: i32, c: i32) -> &DocGridLineType {
|
||||
if r < 0 || r > (self.grid.rows as i32) || c < 0 || c >= (self.grid.cols as i32) {
|
||||
&DocGridLineType::NONE
|
||||
} else {
|
||||
@@ -294,8 +308,7 @@ impl DocGridLayout {
|
||||
self.lines_hort[r as usize * (self.grid.cols) + c as usize] = v;
|
||||
}
|
||||
|
||||
|
||||
fn get_cross (&self, r: i32, c: i32) -> &DocGridLineCrossType {
|
||||
fn get_cross(&self, r: i32, c: i32) -> &DocGridLineCrossType {
|
||||
if r < 0 || r > (self.grid.rows as i32) || c < 0 || c > (self.grid.cols as i32) {
|
||||
&DocGridLineCrossType::NONE
|
||||
} else {
|
||||
@@ -303,28 +316,42 @@ impl DocGridLayout {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cross (&mut self, r: usize, c: usize, v: DocGridLineCrossType) {
|
||||
fn set_cross(&mut self, r: usize, c: usize, v: DocGridLineCrossType) {
|
||||
self.lines_cross[r * (self.grid.cols + 1) + c] = v;
|
||||
}
|
||||
|
||||
pub fn new<FLineV, FLineH, FCross>(grid: &DocGrid, vertical_mapper: FLineV, horizontal_mapper: FLineH, cross_mapper: FCross) -> DocGridLayout
|
||||
where FLineV: Fn(&DocGridCellType, &DocGridCellType) -> DocGridLineType,
|
||||
pub fn new<FLineV, FLineH, FCross>(
|
||||
grid: &DocGrid,
|
||||
vertical_mapper: FLineV,
|
||||
horizontal_mapper: FLineH,
|
||||
cross_mapper: FCross,
|
||||
) -> DocGridLayout
|
||||
where
|
||||
FLineV: Fn(&DocGridCellType, &DocGridCellType) -> DocGridLineType,
|
||||
FLineH: Fn(&DocGridCellType, &DocGridCellType) -> DocGridLineType,
|
||||
FCross: Fn(&DocGridLineType, &DocGridLineType, &DocGridLineType, &DocGridLineType) -> DocGridLineCrossType
|
||||
FCross: Fn(
|
||||
&DocGridLineType,
|
||||
&DocGridLineType,
|
||||
&DocGridLineType,
|
||||
&DocGridLineType,
|
||||
) -> DocGridLineCrossType,
|
||||
{
|
||||
let mut lines_hort = Vec::new();
|
||||
let mut lines_vert = Vec::new();
|
||||
let mut crosses = Vec::new();
|
||||
|
||||
|
||||
lines_hort.resize(grid.cols * (1 + grid.rows), DocGridLineType::NONE);
|
||||
lines_vert.resize((grid.cols + 1) * grid.rows , DocGridLineType::NONE );
|
||||
crosses.resize((grid.rows + 1) * (grid.cols + 1), DocGridLineCrossType::NONE);
|
||||
|
||||
lines_vert.resize((grid.cols + 1) * grid.rows, DocGridLineType::NONE);
|
||||
crosses.resize(
|
||||
(grid.rows + 1) * (grid.cols + 1),
|
||||
DocGridLineCrossType::NONE,
|
||||
);
|
||||
|
||||
let mut res: DocGridLayout = DocGridLayout {
|
||||
grid: grid.clone(),
|
||||
lines_cross: crosses,
|
||||
lines_hort: lines_hort,
|
||||
lines_vert: lines_vert
|
||||
lines_vert: lines_vert,
|
||||
};
|
||||
|
||||
let get_cell = |r: i32, c: i32| -> &DocGridCellType {
|
||||
@@ -334,28 +361,26 @@ impl DocGridLayout {
|
||||
&grid.array_cells[r as usize * grid.cols + c as usize]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Calculating Lines Horizontal lines
|
||||
for row in 0..(grid.rows + 1) {
|
||||
for col in 0..grid.cols {
|
||||
let top = get_cell(row as i32 - 1 , col as i32);
|
||||
let bottom = get_cell(row as i32 , col as i32);
|
||||
let top = get_cell(row as i32 - 1, col as i32);
|
||||
let bottom = get_cell(row as i32, col as i32);
|
||||
res.set_hort_line(row, col, horizontal_mapper(top, bottom));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calculating Lines Vertical lines
|
||||
for row in 0..grid.get_row() {
|
||||
for col in 0..(grid.cols + 1) {
|
||||
let left = get_cell(row as i32, col as i32 - 1);
|
||||
let right = get_cell(row as i32 , col as i32);
|
||||
let right = get_cell(row as i32, col as i32);
|
||||
res.set_vert_line(row, col, vertical_mapper(left, right));
|
||||
}
|
||||
}
|
||||
// Calculating Crossings
|
||||
|
||||
|
||||
|
||||
for row in 0..(grid.rows + 1) {
|
||||
for col in 0..(grid.cols + 1) {
|
||||
let north = res.get_vert_line((row as i32) - 1, col as i32);
|
||||
@@ -376,30 +401,41 @@ impl DocGridLayout {
|
||||
|
||||
for r in 0..self.grid.rows {
|
||||
for c in 0..self.grid.cols {
|
||||
s.push_str(&format!("{}{}",
|
||||
&Self::map_cross_to_chr(self.get_cross(r as i32, c as i32)),
|
||||
s.push_str(&format!(
|
||||
"{}{}",
|
||||
&Self::map_cross_to_chr(self.get_cross(r as i32, c as i32)),
|
||||
&Self::map_line_to_char(self.get_hort_line(r as i32, c as i32))
|
||||
));
|
||||
}
|
||||
s.push_str(&format!("{}\n", &Self::map_cross_to_chr(self.get_cross(r as i32, self.grid.cols as i32))));
|
||||
s.push_str(&format!(
|
||||
"{}\n",
|
||||
&Self::map_cross_to_chr(self.get_cross(r as i32, self.grid.cols as i32))
|
||||
));
|
||||
for c in 0..self.grid.cols {
|
||||
s.push_str(&format!("{}{}",
|
||||
s.push_str(&format!(
|
||||
"{}{}",
|
||||
&Self::map_line_to_char(self.get_vert_line(r as i32, c as i32)),
|
||||
&self.grid.array_cells[self.grid.cols * r + c].to_debug_string()
|
||||
));
|
||||
}
|
||||
s.push_str(&format!("{}\n",
|
||||
&Self::map_line_to_char(self.get_vert_line(r as i32, self.grid.cols as i32))));
|
||||
};
|
||||
s.push_str(&format!(
|
||||
"{}\n",
|
||||
&Self::map_line_to_char(self.get_vert_line(r as i32, self.grid.cols as i32))
|
||||
));
|
||||
}
|
||||
|
||||
for c in 0..self.grid.cols {
|
||||
s.push_str(&format!("{}{}",
|
||||
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, c as i32)),
|
||||
s.push_str(&format!(
|
||||
"{}{}",
|
||||
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, c as i32)),
|
||||
&Self::map_line_to_char(self.get_hort_line(self.grid.rows as i32, c as i32))
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str(&format!("{}\n", &Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, self.grid.cols as i32))));
|
||||
s.push_str(&format!(
|
||||
"{}\n",
|
||||
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, self.grid.cols as i32))
|
||||
));
|
||||
|
||||
s
|
||||
}
|
||||
@@ -415,71 +451,88 @@ impl DocGridLayout {
|
||||
match cell {
|
||||
DocGridCellType::Boarder => empty_string.clone(),
|
||||
DocGridCellType::Empty => empty_string.clone(),
|
||||
DocGridCellType::SubMenu { text } => text.get(*sub_c).cloned().unwrap_or(empty_string.clone()),
|
||||
DocGridCellType::Item { text } => text.get(*sub_c).cloned().unwrap_or(empty_string.clone()),
|
||||
DocGridCellType::SubMenu { text } => {
|
||||
text.get(*sub_c).cloned().unwrap_or(empty_string.clone())
|
||||
}
|
||||
DocGridCellType::Item { text } => {
|
||||
text.get(*sub_c).cloned().unwrap_or(empty_string.clone())
|
||||
}
|
||||
DocGridCellType::ItemJoin => empty_string.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
for r in 0..self.grid.rows {
|
||||
for c in 0..self.grid.cols {
|
||||
s.push_str(&format!("{}{}",
|
||||
&Self::map_cross_to_chr(self.get_cross(r as i32, c as i32)),
|
||||
&Self::map_line_to_char(self.get_hort_line(r as i32, c as i32)).repeat(row_sizes[c] + 2)
|
||||
s.push_str(&format!(
|
||||
"{}{}",
|
||||
&Self::map_cross_to_chr(self.get_cross(r as i32, c as i32)),
|
||||
&Self::map_line_to_char(self.get_hort_line(r as i32, c as i32))
|
||||
.repeat(row_sizes[c] + 2)
|
||||
));
|
||||
}
|
||||
s.push_str(&format!("{}\n", &Self::map_cross_to_chr(self.get_cross(r as i32, self.grid.cols as i32))));
|
||||
s.push_str(&format!(
|
||||
"{}\n",
|
||||
&Self::map_cross_to_chr(self.get_cross(r as i32, self.grid.cols as i32))
|
||||
));
|
||||
|
||||
for sub_c in 0..col_sizes[r] {
|
||||
for c in 0..self.grid.cols {
|
||||
s.push_str(&format!("{} {} ",
|
||||
s.push_str(&format!(
|
||||
"{} {} ",
|
||||
&Self::map_line_to_char(self.get_vert_line(r as i32, c as i32)),
|
||||
&map_cell(&self.grid.array_cells[self.grid.cols * r + c], &sub_c).pad_to_width(row_sizes[c])
|
||||
&map_cell(&self.grid.array_cells[self.grid.cols * r + c], &sub_c)
|
||||
.pad_to_width(row_sizes[c])
|
||||
));
|
||||
}
|
||||
s.push_str(&format!("{}\n",
|
||||
&Self::map_line_to_char(self.get_vert_line(r as i32, self.grid.cols as i32))));
|
||||
s.push_str(&format!(
|
||||
"{}\n",
|
||||
&Self::map_line_to_char(self.get_vert_line(r as i32, self.grid.cols as i32))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
for c in 0..self.grid.cols {
|
||||
s.push_str(&format!("{}{}",
|
||||
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, c as i32)),
|
||||
&Self::map_line_to_char(self.get_hort_line(self.grid.rows as i32, c as i32)).repeat(row_sizes[c] + 2)
|
||||
s.push_str(&format!(
|
||||
"{}{}",
|
||||
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, c as i32)),
|
||||
&Self::map_line_to_char(self.get_hort_line(self.grid.rows as i32, c as i32))
|
||||
.repeat(row_sizes[c] + 2)
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str(&format!("{}\n", &Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, self.grid.cols as i32))));
|
||||
s.push_str(&format!(
|
||||
"{}\n",
|
||||
&Self::map_cross_to_chr(self.get_cross(self.grid.rows as i32, self.grid.cols as i32))
|
||||
));
|
||||
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
mod doc_grid_layout_mappers {
|
||||
use super::{DocGridLineType, DocGridLineCrossType, DocGridCellType};
|
||||
use super::{DocGridCellType, DocGridLineCrossType, DocGridLineType};
|
||||
|
||||
pub fn vert_line_ascii(left: &DocGridCellType, right: &DocGridCellType) -> DocGridLineType {
|
||||
use DocGridCellType::{ItemJoin, Item};
|
||||
use DocGridCellType::{Item, ItemJoin};
|
||||
match (left, right) {
|
||||
(ItemJoin, Item { .. }) => DocGridLineType::NONE,
|
||||
(Item { .. }, ItemJoin) => DocGridLineType::NONE,
|
||||
|
||||
(_, _) => DocGridLineType::VERTICAL
|
||||
(_, _) => DocGridLineType::VERTICAL,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hort_line_ascii(top: &DocGridCellType, bottom: &DocGridCellType) -> DocGridLineType {
|
||||
use DocGridCellType::{Boarder, Empty, ItemJoin, Item, SubMenu};
|
||||
use DocGridCellType::{Boarder, Empty, Item, ItemJoin, SubMenu};
|
||||
match (top, bottom) {
|
||||
(Empty, Empty) => DocGridLineType::NONE,
|
||||
|
||||
// Sub Menu "Joins"
|
||||
// Sub Menu "Joins"
|
||||
(Empty, SubMenu { .. }) => DocGridLineType::HORIZONTAL,
|
||||
(SubMenu { .. }, Empty) => DocGridLineType::NONE,
|
||||
(SubMenu { .. }, SubMenu { .. }) => DocGridLineType::HORIZONTAL,
|
||||
|
||||
// Horizontal Rules
|
||||
|
||||
(Boarder, _) => DocGridLineType::HORIZONTAL,
|
||||
(_, Boarder) => DocGridLineType::HORIZONTAL,
|
||||
|
||||
@@ -491,8 +544,13 @@ mod doc_grid_layout_mappers {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cross_ascii(n: &DocGridLineType, e: &DocGridLineType, s: &DocGridLineType, w: &DocGridLineType) -> DocGridLineCrossType{
|
||||
use DocGridLineType::{NONE, HORIZONTAL, VERTICAL};
|
||||
pub fn cross_ascii(
|
||||
n: &DocGridLineType,
|
||||
e: &DocGridLineType,
|
||||
s: &DocGridLineType,
|
||||
w: &DocGridLineType,
|
||||
) -> DocGridLineCrossType {
|
||||
use DocGridLineType::{HORIZONTAL, NONE, VERTICAL};
|
||||
match (n, e, s, w) {
|
||||
(VERTICAL, NONE, VERTICAL, NONE) => DocGridLineCrossType::VERTICAL,
|
||||
(NONE, HORIZONTAL, NONE, HORIZONTAL) => DocGridLineCrossType::HORIZONTAL,
|
||||
@@ -502,20 +560,22 @@ mod doc_grid_layout_mappers {
|
||||
}
|
||||
|
||||
pub fn create_ascii_debug_string(grid: &DocGrid) -> String {
|
||||
let layout = DocGridLayout::new(grid,
|
||||
let layout = DocGridLayout::new(
|
||||
grid,
|
||||
doc_grid_layout_mappers::vert_line_ascii,
|
||||
doc_grid_layout_mappers::hort_line_ascii,
|
||||
doc_grid_layout_mappers::cross_ascii
|
||||
doc_grid_layout_mappers::cross_ascii,
|
||||
);
|
||||
|
||||
return layout.to_debug_string();
|
||||
}
|
||||
|
||||
pub fn create_ascii_doc_string(grid: &DocGrid) -> String {
|
||||
let layout = DocGridLayout::new(grid,
|
||||
let layout = DocGridLayout::new(
|
||||
grid,
|
||||
doc_grid_layout_mappers::vert_line_ascii,
|
||||
doc_grid_layout_mappers::hort_line_ascii,
|
||||
doc_grid_layout_mappers::cross_ascii
|
||||
doc_grid_layout_mappers::cross_ascii,
|
||||
);
|
||||
|
||||
return layout.to_ascii_doc_string();
|
||||
@@ -523,44 +583,63 @@ pub fn create_ascii_doc_string(grid: &DocGrid) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::configotron::{conf::{UserConfigData, UserConfigItem}};
|
||||
use super::{DocGrid, DocGridCellType, ResultingGridCell};
|
||||
use crate::configotron::conf::{UserConfigData, UserConfigItem};
|
||||
|
||||
// Helper functino for creating elements for testing
|
||||
fn create_item() -> UserConfigItem {
|
||||
UserConfigItem::Boolean { name: "Test".to_string() }
|
||||
UserConfigItem::Boolean {
|
||||
name: "Test".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_submenu(v: Vec<UserConfigItem>) -> UserConfigItem {
|
||||
UserConfigItem::Submenu(UserConfigData { name: "SubMenu".to_string(), config_items: v })
|
||||
UserConfigItem::Submenu(UserConfigData {
|
||||
name: "SubMenu".to_string(),
|
||||
config_items: v,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_menu(v: Vec<UserConfigItem>) -> UserConfigData {
|
||||
UserConfigData { name: "TestMenu".to_string(), config_items: v }
|
||||
UserConfigData {
|
||||
name: "TestMenu".to_string(),
|
||||
config_items: v,
|
||||
}
|
||||
}
|
||||
|
||||
// Actuall Test
|
||||
#[test]
|
||||
fn grid_extend() {
|
||||
let mut grid = DocGrid::new();
|
||||
|
||||
grid.set_title(DocGridCellType::SubMenu { text: vec!["name".to_string()] });
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item { text: vec!["name".to_string()] }));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item { text: vec!["name".to_string()] }));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item { text: vec!["name".to_string()] }));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::SubMenu { text: vec!["name".to_string()] }));
|
||||
|
||||
assert_eq!(grid.to_debug_string(), r#"| S I |
|
||||
grid.set_title(DocGridCellType::SubMenu {
|
||||
text: vec!["name".to_string()],
|
||||
});
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item {
|
||||
text: vec!["name".to_string()],
|
||||
}));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item {
|
||||
text: vec!["name".to_string()],
|
||||
}));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item {
|
||||
text: vec!["name".to_string()],
|
||||
}));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin));
|
||||
grid.append_item(ResultingGridCell::Cell(DocGridCellType::SubMenu {
|
||||
text: vec!["name".to_string()],
|
||||
}));
|
||||
|
||||
assert_eq!(
|
||||
grid.to_debug_string(),
|
||||
r#"| S I |
|
||||
| E i |
|
||||
| E I |
|
||||
| E I |
|
||||
| E i |
|
||||
| E S |
|
||||
"#);
|
||||
|
||||
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -568,19 +647,17 @@ mod tests {
|
||||
let data = create_menu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_submenu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item(),
|
||||
]),
|
||||
create_submenu(vec![create_item(), create_item(), create_item()]),
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item()
|
||||
]);
|
||||
|
||||
let grid = DocGrid::from(&data);
|
||||
|
||||
assert_eq!(grid.to_debug_string(), r#"| S I i |
|
||||
assert_eq!(
|
||||
grid.to_debug_string(),
|
||||
r#"| S I i |
|
||||
| E I i |
|
||||
| E S I |
|
||||
| E E I |
|
||||
@@ -588,61 +665,55 @@ mod tests {
|
||||
| E I i |
|
||||
| E I i |
|
||||
| E I i |
|
||||
"#);
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_deep_assembly_from_data() {
|
||||
let data = create_menu(vec![
|
||||
create_submenu(vec![
|
||||
create_submenu(vec![
|
||||
create_submenu(vec![
|
||||
create_item()
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
let data = create_menu(vec![create_submenu(vec![create_submenu(vec![
|
||||
create_submenu(vec![create_item()]),
|
||||
])])]);
|
||||
|
||||
let grid = DocGrid::from(&data);
|
||||
|
||||
assert_eq!(grid.to_debug_string(), r#"| S S S S I |
|
||||
"#);
|
||||
assert_eq!(
|
||||
grid.to_debug_string(),
|
||||
r#"| S S S S I |
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_complex_assembly_from_data() {
|
||||
let data = create_menu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_submenu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_submenu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_submenu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_submenu(vec![create_item(), create_item()]),
|
||||
create_item(),
|
||||
]),
|
||||
create_item(),
|
||||
]),
|
||||
create_item(),
|
||||
create_submenu(vec![
|
||||
create_submenu(vec![create_item(), create_item()]),
|
||||
create_item(),
|
||||
create_item(),
|
||||
]),
|
||||
create_item(),
|
||||
create_item(),
|
||||
]),
|
||||
create_item(),
|
||||
create_item(),
|
||||
]);
|
||||
|
||||
let grid = DocGrid::from(&data);
|
||||
|
||||
println!("{}", grid.to_debug_string());
|
||||
|
||||
assert_eq!(grid.to_debug_string(), r#"| S I i i i |
|
||||
assert_eq!(
|
||||
grid.to_debug_string(),
|
||||
r#"| S I i i i |
|
||||
| E I i i i |
|
||||
| E S I i i |
|
||||
| E E I i i |
|
||||
@@ -658,37 +729,38 @@ mod tests {
|
||||
| E E I i i |
|
||||
| E I i i i |
|
||||
| E I i i i |
|
||||
"#);
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
mod ascii_docs {
|
||||
use crate::configotron::{conf::{UserConfigData, UserConfigItem}, doc::grid::create_ascii_debug_string, doc::grid::create_ascii_doc_string};
|
||||
use super::{DocGrid, create_item, create_menu, create_submenu};
|
||||
use super::{create_item, create_menu, create_submenu, DocGrid};
|
||||
use crate::configotron::{
|
||||
doc::grid::create_ascii_debug_string, doc::grid::create_ascii_doc_string,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn ascii_docs_debug() {
|
||||
let data = create_menu(vec![
|
||||
let data = create_menu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_submenu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item(),
|
||||
]),
|
||||
create_submenu(vec![create_item(), create_item(), create_item()]),
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item()
|
||||
]);
|
||||
|
||||
let grid = DocGrid::from(&data);
|
||||
|
||||
|
||||
println!("{}", grid.to_debug_string());
|
||||
|
||||
let s = create_ascii_debug_string(&grid);
|
||||
|
||||
println!("{}", s);
|
||||
|
||||
assert_eq!(s, r#"+-+---+
|
||||
assert_eq!(
|
||||
s,
|
||||
r#"+-+---+
|
||||
|S|I i|
|
||||
| +---+
|
||||
|E|I i|
|
||||
@@ -705,51 +777,58 @@ mod tests {
|
||||
| +---+
|
||||
|E|I i|
|
||||
+-+---+
|
||||
"#);
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii_docs_real() {
|
||||
let data = create_menu(vec![
|
||||
let data = create_menu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_submenu(vec![
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item(),
|
||||
]),
|
||||
create_submenu(vec![create_item(), create_item(), create_item()]),
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item(),
|
||||
create_item()
|
||||
]);
|
||||
|
||||
let grid = DocGrid::from(&data);
|
||||
|
||||
|
||||
println!("{}", grid.to_debug_string());
|
||||
|
||||
let s = create_ascii_doc_string(&grid);
|
||||
|
||||
println!("{}", s);
|
||||
|
||||
assert_eq!(s, r#"+----------+----------------+
|
||||
| TestMenu | Test |
|
||||
| +----------------+
|
||||
| | Test |
|
||||
| +---------+------+
|
||||
| | SubMenu | Test |
|
||||
| | +------+
|
||||
| | | Test |
|
||||
| | +------+
|
||||
| | | Test |
|
||||
| +---------+------+
|
||||
| | Test |
|
||||
| +----------------+
|
||||
| | Test |
|
||||
| +----------------+
|
||||
| | Test |
|
||||
+----------+----------------+
|
||||
"#);
|
||||
assert_eq!(
|
||||
s,
|
||||
r#"+----------+---------------------+
|
||||
| TestMenu | Test |
|
||||
| | [Yes/No] |
|
||||
| +---------------------+
|
||||
| | Test |
|
||||
| | [Yes/No] |
|
||||
| +----------+----------+
|
||||
| | SubMenu | Test |
|
||||
| | | [Yes/No] |
|
||||
| | +----------+
|
||||
| | | Test |
|
||||
| | | [Yes/No] |
|
||||
| | +----------+
|
||||
| | | Test |
|
||||
| | | [Yes/No] |
|
||||
| +----------+----------+
|
||||
| | Test |
|
||||
| | [Yes/No] |
|
||||
| +---------------------+
|
||||
| | Test |
|
||||
| | [Yes/No] |
|
||||
| +---------------------+
|
||||
| | Test |
|
||||
| | [Yes/No] |
|
||||
+----------+---------------------+
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,137 +1,288 @@
|
||||
use std::{cell::RefCell, rc::{Weak, Rc}};
|
||||
use graphviz_rust::{
|
||||
dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, NodeId, Stmt, Vertex},
|
||||
exec,
|
||||
printer::PrinterContext,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FSM {
|
||||
states: Vec<Rc<RefCell<FSMState>>>
|
||||
name: String,
|
||||
states: Vec<Rc<RefCell<FSMState>>>,
|
||||
}
|
||||
|
||||
impl FSM {
|
||||
pub fn new(state: Vec<Rc<RefCell<FSMState>>>) -> Self {
|
||||
FSM {states: state}
|
||||
pub fn new(state: Vec<Rc<RefCell<FSMState>>>, name: &str) -> Self {
|
||||
FSM {
|
||||
states: state,
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_stmts(&self) -> Vec<Stmt> {
|
||||
let mut stmts: Vec<Stmt> = vec![Stmt::Attribute(Attribute(
|
||||
Id::Plain("rankdir".to_string()),
|
||||
Id::Plain("LR".to_string()),
|
||||
))];
|
||||
for s in &self.states {
|
||||
let s_borrowed = s.borrow();
|
||||
|
||||
stmts.push(Stmt::Node(graphviz_rust::dot_structures::Node {
|
||||
id: NodeId(Id::Plain(s_borrowed.to_graph_label()), None),
|
||||
attributes: {
|
||||
let mut res = vec![Attribute(
|
||||
Id::Plain("label".to_string()),
|
||||
Id::Plain(s_borrowed.to_graph_label()),
|
||||
)];
|
||||
match s_borrowed.action {
|
||||
FSMStateAction::NONE => {
|
||||
res.push(Attribute(
|
||||
Id::Plain("shape".to_string()),
|
||||
Id::Plain("circle".to_string()),
|
||||
));
|
||||
}
|
||||
FSMStateAction::TERMINAL => {
|
||||
res.push(Attribute(
|
||||
Id::Plain("shape".to_string()),
|
||||
Id::Plain("doublecircle".to_string()),
|
||||
));
|
||||
}
|
||||
}
|
||||
res
|
||||
},
|
||||
}));
|
||||
|
||||
match &s_borrowed.transition {
|
||||
None => {}
|
||||
Some(t) => {
|
||||
for (t, range) in t.to_targets_vec() {
|
||||
match t.upgrade() {
|
||||
None => {}
|
||||
Some(target) => {
|
||||
stmts.push(Stmt::Edge(Edge {
|
||||
ty: EdgeTy::Pair(
|
||||
Vertex::N(NodeId(
|
||||
Id::Plain(s_borrowed.to_graph_label()),
|
||||
None,
|
||||
)),
|
||||
Vertex::N(NodeId(
|
||||
Id::Plain(target.borrow().to_graph_label()),
|
||||
None,
|
||||
)),
|
||||
),
|
||||
attributes: vec![Attribute(
|
||||
Id::Plain("label".to_string()),
|
||||
Id::Plain(format!("\"{}\"", range.to_string())),
|
||||
)],
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stmts
|
||||
}
|
||||
|
||||
fn render_graph(&self, format: graphviz_rust::cmd::Format) -> Result<Vec<u8>, std::io::Error> {
|
||||
let g = Graph::DiGraph {
|
||||
id: Id::Plain("root".to_string()),
|
||||
strict: false,
|
||||
stmts: self.render_stmts(),
|
||||
};
|
||||
|
||||
let mut ctx: graphviz_rust::printer::PrinterContext = PrinterContext::default();
|
||||
|
||||
println!("{}", graphviz_rust::print(g.clone(), &mut ctx));
|
||||
|
||||
let data = exec(g, &mut ctx, vec![format.into()])?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum FSMStateAction {
|
||||
NONE, TERMINAL
|
||||
NONE,
|
||||
TERMINAL,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FSMState {
|
||||
name: String,
|
||||
action: FSMStateAction,
|
||||
transition: Option<FSMTranistion>
|
||||
transition: Option<tranisitions::FSMTranistion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FSMTranistion {
|
||||
array: [Weak<RefCell<FSMState>>; 128],
|
||||
}
|
||||
|
||||
|
||||
impl FSMState {
|
||||
pub fn new(name: String, action: FSMStateAction) -> Self {
|
||||
FSMState { name: name, action: action, transition: None }
|
||||
FSMState {
|
||||
name: name,
|
||||
action: action,
|
||||
transition: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_transitions(&mut self, t: FSMTranistion) {
|
||||
pub fn set_transitions(&mut self, t: tranisitions::FSMTranistion) {
|
||||
self.transition = Some(t);
|
||||
}
|
||||
}
|
||||
|
||||
impl FSMTranistion {
|
||||
|
||||
pub fn new_from_mask(mask: TransitionMask, pos: Weak<RefCell<FSMState>>, default: Weak<RefCell<FSMState>>) -> Self {
|
||||
let mut array: [Weak<RefCell<FSMState>>; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
||||
for i in 0..128 {
|
||||
if mask.get_bit(i) {
|
||||
array[i] = Weak::clone(&pos );
|
||||
}
|
||||
}
|
||||
|
||||
FSMTranistion { array }
|
||||
pub fn to_graph_label(&self) -> String {
|
||||
return format!("\"{}\"", self.name);
|
||||
}
|
||||
|
||||
pub fn new(mask: TransitionMask, default: Weak<RefCell<FSMState>>) -> Self {
|
||||
let mut array: [Weak<RefCell<FSMState>>; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
||||
FSMTranistion { array }
|
||||
}
|
||||
|
||||
pub fn apply(&mut self, mask: TransitionMask, state: Weak<RefCell<FSMState>>) {
|
||||
|
||||
for i in 0..128 {
|
||||
if mask.get_bit(i) {
|
||||
self.array[i] = Weak::clone(&state );
|
||||
}
|
||||
}
|
||||
pub fn to_graph_hash(&self) -> String {
|
||||
self.to_graph_label()
|
||||
}
|
||||
}
|
||||
|
||||
use transition_masking::{TransitionMask};
|
||||
mod tranisitions {
|
||||
use super::transition_masking::TransitionMask;
|
||||
use super::FSMState;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, rc::Weak};
|
||||
|
||||
type StateRef = Weak<RefCell<FSMState>>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FSMTranistion {
|
||||
array: [StateRef; 128],
|
||||
}
|
||||
|
||||
impl FSMTranistion {
|
||||
pub fn new_from_mask(mask: TransitionMask, pos: StateRef, default: StateRef) -> Self {
|
||||
let mut array: [StateRef; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
||||
for i in 0..128 {
|
||||
if mask.get_bit(i) {
|
||||
array[i] = Weak::clone(&pos);
|
||||
}
|
||||
}
|
||||
|
||||
FSMTranistion { array }
|
||||
}
|
||||
|
||||
pub fn new(default: StateRef) -> Self {
|
||||
let array: [StateRef; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
||||
|
||||
FSMTranistion { array }
|
||||
}
|
||||
|
||||
pub fn apply(&mut self, mask: TransitionMask, state: StateRef) {
|
||||
for i in 0..128 {
|
||||
if mask.get_bit(i) {
|
||||
self.array[i] = Weak::clone(&state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_targets_vec(&self) -> Vec<(StateRef, TransitionMask)> {
|
||||
let mut target_maps: HashMap<String, (StateRef, TransitionMask)> = HashMap::new();
|
||||
|
||||
self.array.iter().enumerate().for_each(|(index, target)| {
|
||||
match target.upgrade() {
|
||||
None => {}
|
||||
Some(t) => {
|
||||
let s_borrowd = t.borrow();
|
||||
let key = s_borrowd.to_graph_label();
|
||||
match target_maps.get_mut(&key) {
|
||||
Some((_, mask)) => {
|
||||
mask.set_bit(index);
|
||||
}
|
||||
None => {
|
||||
let mut mask = TransitionMask::new();
|
||||
mask.set_bit(index);
|
||||
target_maps.insert(key, (Rc::downgrade(&t), mask));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let mut res: Vec<(StateRef, TransitionMask)> = Vec::new();
|
||||
|
||||
target_maps
|
||||
.iter()
|
||||
.for_each(|(_, (target, mask))| match target.upgrade() {
|
||||
Some(t) => {
|
||||
res.push((Rc::downgrade(&t), mask.clone()));
|
||||
}
|
||||
None => {}
|
||||
});
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod transition_masking {
|
||||
use std::ops::RangeInclusive;
|
||||
use itertools::Itertools;
|
||||
use std::{ops::RangeInclusive, usize};
|
||||
|
||||
pub enum MaskPresets {
|
||||
Char(char),
|
||||
AnyCase, LowerCase, UpperCase,
|
||||
AnyCase,
|
||||
LowerCase,
|
||||
UpperCase,
|
||||
AllHumanReadable,
|
||||
Numbers, SpecialChars,
|
||||
WhiteSpace, LineTermination,
|
||||
NoChar
|
||||
Numbers,
|
||||
SpecialChars,
|
||||
WhiteSpace,
|
||||
LineTermination,
|
||||
NoChar,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TransitionMask {
|
||||
mask: u128
|
||||
mask: u128,
|
||||
}
|
||||
|
||||
impl From<MaskPresets> for TransitionMask {
|
||||
fn from(value: MaskPresets) -> Self {
|
||||
let mut n: u128 = 0;
|
||||
|
||||
|
||||
let mut set_range = |r: RangeInclusive<i32>| {
|
||||
r.for_each(|i| { n |= 1 << i });
|
||||
r.for_each(|i| n |= 1 << i);
|
||||
};
|
||||
|
||||
match value {
|
||||
MaskPresets::NoChar => {}
|
||||
MaskPresets::AnyCase => {
|
||||
set_range(0x41..=0x5a); // UpperCase
|
||||
set_range(0x61..=0x7a); // LowerCase
|
||||
},
|
||||
set_range(0x41..=0x5a); // UpperCase
|
||||
set_range(0x61..=0x7a); // LowerCase
|
||||
}
|
||||
MaskPresets::LowerCase => {
|
||||
set_range(0x61..=0x7a); // LowerCase
|
||||
},
|
||||
set_range(0x61..=0x7a); // LowerCase
|
||||
}
|
||||
MaskPresets::UpperCase => {
|
||||
set_range(0x41..=0x5a); // UpperCase
|
||||
},
|
||||
set_range(0x41..=0x5a); // UpperCase
|
||||
}
|
||||
MaskPresets::AllHumanReadable => {
|
||||
set_range(0x20..=0x7e); // All Ascii Chars + Space + Tab
|
||||
n |= 1 << 0x9; // Tab
|
||||
},
|
||||
set_range(0x20..=0x7e); // All Ascii Chars + Space + Tab
|
||||
n |= 1 << 0x9; // Tab
|
||||
}
|
||||
MaskPresets::Numbers => {
|
||||
set_range(0x30..=0x39); // Numbers
|
||||
},
|
||||
set_range(0x30..=0x39); // Numbers
|
||||
}
|
||||
MaskPresets::SpecialChars => {
|
||||
set_range(0x21..=0x2F); // ! to /
|
||||
set_range(0x3A..=0x40); // : to @
|
||||
set_range(0x5B..=0x0D); // [ to `
|
||||
set_range(0x7B..=0x7E); // { to ~
|
||||
},
|
||||
set_range(0x21..=0x2F); // ! to /
|
||||
set_range(0x3A..=0x40); // : to @
|
||||
set_range(0x5B..=0x0D); // [ to `
|
||||
set_range(0x7B..=0x7E); // { to ~
|
||||
}
|
||||
MaskPresets::WhiteSpace => {
|
||||
n |= 1 << 0x9; // Tab
|
||||
n |= 1 << 0x20; // Spaces
|
||||
|
||||
},
|
||||
n |= 1 << 0x9; // Tab
|
||||
n |= 1 << 0x20; // Spaces
|
||||
}
|
||||
MaskPresets::LineTermination => {
|
||||
n |= 1 << 0x0a; // Line Feed
|
||||
n |= 1 << 0x0d; // Carriage Return
|
||||
}
|
||||
n |= 1 << 0x0a; // Line Feed
|
||||
n |= 1 << 0x0d; // Carriage Return
|
||||
}
|
||||
MaskPresets::Char(c) => {
|
||||
if c.is_ascii() {
|
||||
n |= 1 << (c as u32)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
TransitionMask { mask: n }
|
||||
@@ -144,11 +295,15 @@ mod transition_masking {
|
||||
}
|
||||
|
||||
pub fn or(&self, other: Self) -> TransitionMask {
|
||||
TransitionMask { mask: other.mask | self.mask }
|
||||
TransitionMask {
|
||||
mask: other.mask | self.mask,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn and(&self, other: Self) -> TransitionMask {
|
||||
TransitionMask { mask: other.mask & self.mask }
|
||||
TransitionMask {
|
||||
mask: other.mask & self.mask,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inv(&self) -> TransitionMask {
|
||||
@@ -158,42 +313,190 @@ mod transition_masking {
|
||||
pub fn get_bit(&self, i: usize) -> bool {
|
||||
(self.mask & (1 << i)) > 0
|
||||
}
|
||||
|
||||
pub fn set_bit(&mut self, i: usize) {
|
||||
self.mask |= 1 << i;
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
let mut res: Vec<String> = Vec::new();
|
||||
let mut strike: Option<usize> = None;
|
||||
|
||||
fn num_to_str(i: usize) -> String {
|
||||
if (0x21..=0x7E).contains(&i) {
|
||||
format!("'{}'", ((i as u8) as char).to_string())
|
||||
} else {
|
||||
match i {
|
||||
0x20 => "SP".to_string(),
|
||||
0x0D => "LF".to_string(),
|
||||
0x0A => "CR".to_string(),
|
||||
0x09 => "TAB".to_string(),
|
||||
_ => format!("{:02x}", i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (p, n) in (0..128).tuple_windows() {
|
||||
match (strike, self.get_bit(p), self.get_bit(n)) {
|
||||
(None, true, true) => {
|
||||
// First two
|
||||
strike = Some(p);
|
||||
}
|
||||
(None, true, false) => {
|
||||
// First, only one
|
||||
res.push(num_to_str(p));
|
||||
}
|
||||
(None, false, true) => {
|
||||
// Strik Start
|
||||
strike = Some(n)
|
||||
}
|
||||
(None, false, false) => {
|
||||
// No Strike
|
||||
}
|
||||
(Some(_), true, true) => {
|
||||
// Stike ongoing
|
||||
}
|
||||
(Some(s), true, false) => {
|
||||
// Strik end
|
||||
if s == p {
|
||||
// Single Number
|
||||
res.push(num_to_str(p));
|
||||
} else {
|
||||
// Range
|
||||
res.push(format!("{}-{}", num_to_str(s), num_to_str(p)));
|
||||
}
|
||||
|
||||
strike = None;
|
||||
}
|
||||
(Some(_), false, true) => {
|
||||
// This should no happend
|
||||
strike = Some(n);
|
||||
}
|
||||
(Some(_), false, false) => {
|
||||
// Should also no happend
|
||||
strike = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match strike {
|
||||
None => (),
|
||||
Some(s) => {
|
||||
if s == 127 {
|
||||
res.push(num_to_str(s));
|
||||
} else {
|
||||
res.push(format!("{}-{}", num_to_str(s), num_to_str(127)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res.join(",")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{transition_masking::*, *};
|
||||
use graphviz_rust::cmd::Format;
|
||||
use std::io::Write;
|
||||
|
||||
use super::{tranisitions::*, transition_masking::*, *};
|
||||
|
||||
#[test]
|
||||
fn simple_fsm_building() {
|
||||
let z0 = Rc::new(RefCell::new(FSMState::new("z0".to_string(), FSMStateAction::NONE)));
|
||||
let z1 = Rc::new(RefCell::new(FSMState::new("z1".to_string(), FSMStateAction::NONE)));
|
||||
let z2 = Rc::new(RefCell::new(FSMState::new("z2".to_string(), FSMStateAction::TERMINAL)));
|
||||
let z3 = Rc::new(RefCell::new(FSMState::new("z3".to_string(), FSMStateAction::NONE)));
|
||||
fn transition_mask_to_string() {
|
||||
let mut mask1 = TransitionMask::new();
|
||||
|
||||
mask1.set_bit(0);
|
||||
|
||||
(10..=16).for_each(|i| {
|
||||
mask1.set_bit(i);
|
||||
});
|
||||
|
||||
(50..=51).for_each(|i| {
|
||||
mask1.set_bit(i);
|
||||
});
|
||||
|
||||
mask1.set_bit(60);
|
||||
mask1.set_bit(62);
|
||||
mask1.set_bit(64);
|
||||
|
||||
(70..=73).for_each(|i| {
|
||||
mask1.set_bit(i);
|
||||
});
|
||||
|
||||
(126..=127).for_each(|i| {
|
||||
mask1.set_bit(i);
|
||||
});
|
||||
|
||||
println!("{}", mask1.to_string());
|
||||
|
||||
assert_eq!(
|
||||
mask1.to_string(),
|
||||
"00,CR-10,'2'-'3','<','>','@','F'-'I','~'-7f"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_fsm_render() {
|
||||
let z0 = Rc::new(RefCell::new(FSMState::new(
|
||||
"z0".to_string(),
|
||||
FSMStateAction::NONE,
|
||||
)));
|
||||
let z1 = Rc::new(RefCell::new(FSMState::new(
|
||||
"z1".to_string(),
|
||||
FSMStateAction::NONE,
|
||||
)));
|
||||
let z2 = Rc::new(RefCell::new(FSMState::new(
|
||||
"z2".to_string(),
|
||||
FSMStateAction::TERMINAL,
|
||||
)));
|
||||
let z3 = Rc::new(RefCell::new(FSMState::new(
|
||||
"z3".to_string(),
|
||||
FSMStateAction::NONE,
|
||||
)));
|
||||
|
||||
let mut z0_transisions = FSMTranistion::new_from_mask(
|
||||
TransitionMask::from(MaskPresets::Char('a')), Rc::downgrade(&z1), Rc::downgrade(&z3));
|
||||
z0_transisions.apply(TransitionMask::from(MaskPresets::Char('b')), Rc::downgrade(&z2));
|
||||
TransitionMask::from(MaskPresets::Char('a')),
|
||||
Rc::downgrade(&z1),
|
||||
Rc::downgrade(&z3),
|
||||
);
|
||||
z0_transisions.apply(
|
||||
TransitionMask::from(MaskPresets::Char('b')),
|
||||
Rc::downgrade(&z2),
|
||||
);
|
||||
|
||||
let mut z1_transisions = FSMTranistion::new_from_mask(
|
||||
TransitionMask::from(MaskPresets::Char('a')), Rc::downgrade(&z1), Rc::downgrade(&z3));
|
||||
z1_transisions.apply(TransitionMask::from(MaskPresets::Char('b')), Rc::downgrade(&z2));
|
||||
TransitionMask::from(MaskPresets::Char('a')),
|
||||
Rc::downgrade(&z1),
|
||||
Rc::downgrade(&z3),
|
||||
);
|
||||
z1_transisions.apply(
|
||||
TransitionMask::from(MaskPresets::Char('b')),
|
||||
Rc::downgrade(&z2),
|
||||
);
|
||||
|
||||
let z2_transisions = FSMTranistion::new_from_mask(
|
||||
TransitionMask::from(MaskPresets::Char('a')), Rc::downgrade(&z2), Rc::downgrade(&z3));
|
||||
TransitionMask::from(MaskPresets::Char('a')),
|
||||
Rc::downgrade(&z2),
|
||||
Rc::downgrade(&z3),
|
||||
);
|
||||
|
||||
let z3_transisions = FSMTranistion::new(Rc::downgrade(&z3));
|
||||
|
||||
let z3_transisions = FSMTranistion::new(
|
||||
TransitionMask::from(MaskPresets::Char('a')), Rc::downgrade(&z3));
|
||||
|
||||
z0.borrow_mut().set_transitions(z0_transisions);
|
||||
z1.borrow_mut().set_transitions(z1_transisions);
|
||||
z2.borrow_mut().set_transitions(z2_transisions);
|
||||
z3.borrow_mut().set_transitions(z3_transisions);
|
||||
|
||||
let fsm = FSM::new(vec![z0, z1, z2, z3], "Test FSM #1");
|
||||
|
||||
let fsm = FSM::new(vec![z0, z1, z2, z3]);
|
||||
let data = fsm.render_graph(Format::Svg).unwrap();
|
||||
|
||||
let mut output_file = std::fs::File::create("/tmp/debug-simle_fsm_building.svg").unwrap();
|
||||
output_file.write_all(&data).unwrap();
|
||||
|
||||
|
||||
let svg_content = std::fs::read_to_string("/tmp/debug-simle_fsm_building.svg").unwrap();
|
||||
assert_eq!(svg_content, String::from_utf8(data).unwrap());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pub mod conf;
|
||||
pub mod doc;
|
||||
pub mod fsm;
|
||||
pub mod fsm;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#[allow(dead_code)]
|
||||
mod configotron;
|
||||
|
||||
use configotron::conf::{read_and_parse_yaml_file};
|
||||
use configotron::doc::grid::{DocGrid, create_ascii_doc_string};
|
||||
use configotron::conf::read_and_parse_yaml_file;
|
||||
use configotron::doc::grid::{create_ascii_doc_string, DocGrid};
|
||||
|
||||
fn main() {
|
||||
|
||||
let data = match read_and_parse_yaml_file("example.yml") {
|
||||
Ok(d) => d,
|
||||
Err(error) => {
|
||||
|
||||
Reference in New Issue
Block a user