diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c5c2eaf --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'configotron'", + "cargo": { + "args": [ + "build", + "--bin=configotron", + "--package=configotron" + ], + "filter": { + "name": "configotron", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'configotron'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=configotron", + "--package=configotron" + ], + "filter": { + "name": "configotron", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fb962c8..0c4491e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -16,6 +16,23 @@ "showReuseMessage": true, "clear": true } + }, + { + "type": "cargo", + "command": "test", + "problemMatcher": [ + "$rustc" + ], + "group": "test", + "label": "rust: cargo test", + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "showReuseMessage": true, + "clear": true + } } ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1a72a7e..1ae84e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,11 +8,18 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "configotron" version = "0.1.0" dependencies = [ "pad", + "rand", "serde", "serde_yml", "thiserror", @@ -24,6 +31,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.15.3" @@ -46,6 +64,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + [[package]] name = "libyml" version = "0.0.5" @@ -71,6 +95,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -89,6 +122,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "ryu" version = "1.0.20" @@ -178,3 +241,29 @@ name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 25caafb..90f11f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ pad = "0.1.6" serde = { version = "1.0", features = ["derive"] } serde_yml = "0.0.12" thiserror = "1" +rand = "0.8" diff --git a/src/configotron/conf.rs b/src/configotron/conf.rs new file mode 100644 index 0000000..f101b5d --- /dev/null +++ b/src/configotron/conf.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; + +use std::{fs}; + + +#[derive(Deserialize, Serialize, Debug)] +pub enum UserConfigItem { + String { + name: String, + max_length: u64 + }, + IPv4 { + name: String, + }, + Boolean { + name: String + }, + Number { + name: String, + max: Option, + min: Option + }, + Submenu(UserConfigData) +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct UserConfigData { + pub name: String, + pub config_items: Vec +} + + +#[derive(thiserror::Error, Debug)] +pub enum ConfigotronError { + #[error("IO Operation failed: {}", ._0.to_string())] + IoError(#[from] std::io::Error), + + #[error("Failed to parse YAML File: {}", ._0.to_string())] + YamlParsingError(#[from] serde_yml::Error), + + #[error("Missing field {} (line {} col {}) {}", .missing_filed, .line, .col, .context)] + MissingFieldError{ + col: i32, + line: i32, + missing_filed: Box, + context: Box + } +} + +pub fn read_and_parse_yaml_file(input_file: &str) -> Result { + let file_content = fs::read_to_string(&input_file)?; + let config: UserConfigData = serde_yml::from_str(&file_content)?; + return Ok(config); +} \ No newline at end of file diff --git a/src/configotron/doc/grid.rs b/src/configotron/doc/grid.rs new file mode 100644 index 0000000..26737f0 --- /dev/null +++ b/src/configotron/doc/grid.rs @@ -0,0 +1,575 @@ +use std::{fmt::format, usize}; +use crate::configotron::{conf::{UserConfigData, UserConfigItem}, doc::grid}; + +#[derive(Clone, PartialEq, Debug)] +pub enum DocGridCellType{ + Boarder, + Empty, + SubMenu { name: String }, + Item { name: String }, + ItemJoin +} + +impl DocGridCellType { + fn to_debug_string(&self) -> String { + match self { + DocGridCellType::Empty => "E".to_string(), + DocGridCellType::Item { .. } => "I".to_string(), + DocGridCellType::ItemJoin => "i".to_string(), + DocGridCellType::SubMenu { .. } => "S".to_string(), + DocGridCellType::Boarder => "B".to_string() + } + } +} + +pub enum ResultingGridCell { + Grid(DocGrid), + Cell(DocGridCellType) +} + +fn user_config_item_to_grid_cells(item: &UserConfigItem) -> ResultingGridCell { + match item { + UserConfigItem::String { name, max_length } => ResultingGridCell::Cell(DocGridCellType::Item { name: name.clone() }), + UserConfigItem::IPv4 { name } => ResultingGridCell::Cell(DocGridCellType::Item { name: name.clone() }), + UserConfigItem::Boolean { name } => ResultingGridCell::Cell(DocGridCellType::Item { name: name.clone() }), + UserConfigItem::Number { name, max, min } => ResultingGridCell::Cell(DocGridCellType::Item { name: name.clone() }), + UserConfigItem::Submenu(user_config_data) => ResultingGridCell::Grid(DocGrid::from(user_config_data)), + } +} + +#[derive(Clone)] +pub struct DocGrid { + rows: usize, + cols: usize, + array_cells: Vec +} + +impl DocGrid { + pub fn new() -> Self { + Self { rows: 0, cols: 2, array_cells: Vec::new() } + } + + pub fn len(&self) -> usize { + self.cols * self.rows + } + + pub fn get_row(&self) -> usize { + self.rows + } + + pub fn set_title(&mut self, cell: DocGridCellType) { + 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.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); + + + let row_offset = self.rows; + let old_cols = self.cols; + self.cols = new_col_size; + self.rows = new_row_size; + + for row_index in (0..new_row_size).rev() { + for col_index in (0..new_col_size).rev() { + let new_index = row_index * new_col_size + col_index; + if col_index < old_cols && row_index < self.rows { + // Reshuffel Stuff Old Stuff + let old_index = row_index * old_cols + col_index; + self.array_cells[new_index] = self.array_cells[old_index].clone(); + } else { + // Make Cells left to inset empty + self.array_cells[new_index] = DocGridCellType::Empty; + } + } + } + + // 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_index = target_col_index + target_row_index * self.cols; + let src_index = row_index * grid.cols + col_index; + + self.array_cells[target_index] = grid.array_cells[src_index].clone(); + } + } + } + }; + } + + pub fn to_debug_string(&self) -> String { + let mut s = String::new(); + + 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("|\n") + } + + s + } +} + +impl From<&UserConfigData> for DocGrid { + fn from(value: &UserConfigData) -> Self { + let mut res = DocGrid::new(); + res.set_title(DocGridCellType::SubMenu { name: value.name.clone() }); + value.config_items.iter().for_each(|i| { + res.append_item(user_config_item_to_grid_cells(i)); + }); + + // Add ItemJoins + 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] { + 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 +} + +#[derive(Clone)] +pub enum DocGridLineCrossType { + NONE, FULL, VERTICAL, HORIZONTAL +} + +pub struct DocGridLayout { + grid: DocGrid, + lines_hort: Vec, + lines_vert: Vec, + lines_cross: Vec, +} + +impl DocGridLayout { + + 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 { + &self.lines_vert[r as usize * (self.grid.cols + 1) + c as usize] + } + } + + fn set_vert_line(&mut self, r: usize, c: usize, v: DocGridLineType) { + self.lines_vert[r as usize * (self.grid.cols + 1) + c as usize] = v; + } + + 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 { + &self.lines_hort[(r as usize) * self.grid.cols + (c as usize)] + } + } + + fn set_hort_line(&mut self, r: usize, c: usize, v: DocGridLineType) { + self.lines_hort[r as usize * (self.grid.cols) + c as usize] = v; + } + + + 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 { + &self.lines_cross[(r as usize) * (self.grid.cols + 1) + (c as usize)] + } + } + + fn set_cross (&mut self, r: usize, c: usize, v: DocGridLineCrossType) { + self.lines_cross[r * (self.grid.cols + 1) + c] = v; + } + + pub fn new(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 + { + 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); + + let mut res: DocGridLayout = DocGridLayout { + grid: grid.clone(), + lines_cross: crosses, + lines_hort: lines_hort, + lines_vert: lines_vert + }; + + let get_cell = |r: i32, c: i32| -> &DocGridCellType { + if r < 0 || (r as usize) >= grid.rows || c < 0 || (c as usize) >= grid.cols { + &DocGridCellType::Boarder + } else { + &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); + 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); + 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); + let south = res.get_vert_line(row as i32, col as i32); + + let east = res.get_hort_line(row as i32, col as i32); + let west = res.get_hort_line(row as i32, (col as i32) - 1); + + res.set_cross(row, col, cross_mapper(north, east, south, west)); + } + } + + res + } + + pub fn to_debug_string(&self) -> String { + let mut s = String::new(); + + fn map_line_to_char(l: &DocGridLineType) -> String { + match l { + DocGridLineType::HORIZONTAL => "-".to_string(), + DocGridLineType::NONE => " ".to_string(), + DocGridLineType::VERTICAL => "|".to_string() + } + } + + fn map_cross_to_chr(c: &DocGridLineCrossType) -> String { + match c { + DocGridLineCrossType::NONE => " ".to_string(), + DocGridLineCrossType::FULL => "+".to_string(), + DocGridLineCrossType::VERTICAL => "|".to_string(), + DocGridLineCrossType::HORIZONTAL => "-".to_string(), + } + } + + for r in 0..self.grid.rows { + for c in 0..self.grid.cols { + s.push_str(&format!("{}{}", + &map_cross_to_chr(self.get_cross(r as i32, c as i32)), + &map_line_to_char(self.get_hort_line(r as i32, c as i32)) + )); + } + s.push_str(&format!("{}\n", &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!("{}{}", + &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", + &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!("{}{}", + &map_cross_to_chr(self.get_cross(self.grid.rows as i32, c as i32)), + &map_line_to_char(self.get_hort_line(self.grid.rows as i32, c as i32)) + )); + } + + s.push_str(&format!("{}\n", &map_cross_to_chr(self.get_cross(self.grid.rows as i32, self.grid.cols as i32)))); + + s + } +} + +mod DocGridLayoutMappers { + use super::{DocGridLineType, DocGridLineCrossType, DocGridCellType}; + + pub fn vert_line_ascii(left: &DocGridCellType, right: &DocGridCellType) -> DocGridLineType { + use DocGridCellType::{ItemJoin, Item}; + match (left, right) { + (ItemJoin, Item { .. }) => DocGridLineType::NONE, + (Item { .. }, ItemJoin) => DocGridLineType::NONE, + + (_, _) => DocGridLineType::VERTICAL + } + } + + pub fn hort_line_ascii(top: &DocGridCellType, bottom: &DocGridCellType) -> DocGridLineType { + use DocGridCellType::{Boarder, Empty, ItemJoin, Item, SubMenu}; + match (top, bottom) { + (Empty, Empty) => DocGridLineType::NONE, + + // Sub Menu "Joins" + (Empty, SubMenu { .. }) => DocGridLineType::HORIZONTAL, + (SubMenu { .. }, Empty) => DocGridLineType::NONE, + (SubMenu { .. }, SubMenu { .. }) => DocGridLineType::HORIZONTAL, + + // Horizontal Rules + + (Boarder, _) => DocGridLineType::HORIZONTAL, + (_, Boarder) => DocGridLineType::HORIZONTAL, + + (Item { .. }, _) => DocGridLineType::HORIZONTAL, + (_, Item { .. }) => DocGridLineType::HORIZONTAL, + + (ItemJoin, _) => DocGridLineType::HORIZONTAL, + (_, ItemJoin) => DocGridLineType::HORIZONTAL, + } + } + + pub fn cross_ascii(n: &DocGridLineType, e: &DocGridLineType, s: &DocGridLineType, w: &DocGridLineType) -> DocGridLineCrossType{ + use DocGridLineType::{NONE, HORIZONTAL, VERTICAL}; + match (n, e, s, w) { + (VERTICAL, NONE, VERTICAL, NONE) => DocGridLineCrossType::VERTICAL, + (NONE, HORIZONTAL, NONE, HORIZONTAL) => DocGridLineCrossType::HORIZONTAL, + (_, _, _, _) => DocGridLineCrossType::FULL, + } + } +} + +pub fn create_ascii_doc_string(grid: &DocGrid) -> String { + let layout = DocGridLayout::new(grid, + DocGridLayoutMappers::vert_line_ascii, + DocGridLayoutMappers::hort_line_ascii, + DocGridLayoutMappers::cross_ascii + ); + + return layout.to_debug_string(); +} + +#[cfg(test)] +mod tests { + use crate::configotron::{conf::{UserConfigData, UserConfigItem}}; + use super::{DocGrid, DocGridCellType, ResultingGridCell}; + + // Helper functino for creating elements for testing + fn create_item() -> UserConfigItem { + UserConfigItem::Boolean { name: "Test".to_string() } + } + + fn create_submenu(v: Vec) -> UserConfigItem { + UserConfigItem::Submenu(UserConfigData { name: "SubMenu".to_string(), config_items: v }) + } + + fn create_menu(v: Vec) -> UserConfigData { + UserConfigData { name: "TestMenu".to_string(), config_items: v } + } + + // Actuall Test + #[test] + fn test_grid_extend() { + let mut grid = DocGrid::new(); + + grid.set_title(DocGridCellType::SubMenu { name: "name".to_string() }); + grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item { name: "name".to_string() })); + grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin)); + grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item { name: "name".to_string() })); + grid.append_item(ResultingGridCell::Cell(DocGridCellType::Item { name: "name".to_string() })); + grid.append_item(ResultingGridCell::Cell(DocGridCellType::ItemJoin)); + grid.append_item(ResultingGridCell::Cell(DocGridCellType::SubMenu { name: "name".to_string() })); + + assert_eq!(grid.to_debug_string(), r#"| S I | +| E i | +| E I | +| E I | +| E i | +| E S | +"#); + + + } + + #[test] + fn test_grid_assembly_from_data() { + let data = create_menu(vec![ + create_item(), + create_item(), + create_submenu(vec![ + 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 | +| E I i | +| E S I | +| E E I | +| E E I | +| E I i | +| E I i | +| E I i | +"#); + } + + #[test] + fn test_grid_deep_assembly_from_data() { + 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 | +"#); + } + + #[test] + fn test_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_item(), + ]); + + let grid = DocGrid::from(&data); + + println!("{}", grid.to_debug_string()); + + 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 | +| E E S I i | +| E E E I i | +| E E E S I | +| E E E E I | +| E E E I i | +| E E I i i | +| E E S I i | +| E E E I i | +| E E I i i | +| 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_doc_string}; + use super::{DocGrid, create_item, create_menu, create_submenu}; + + #[test] + fn ascii_docs_simple() { + let data = create_menu(vec![ + create_item(), + create_item(), + create_submenu(vec![ + 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#"+-+---+ +|S|I i| +| +---+ +|E|I i| +| +-+-+ +|E|S|I| +| | +-+ +|E|E|I| +| | +-+ +|E|E|I| +| +-+-+ +|E|I i| +| +---+ +|E|I i| +| +---+ +|E|I i| ++-+---+ +"#); + } + + } +} \ No newline at end of file diff --git a/src/configotron/doc/mod.rs b/src/configotron/doc/mod.rs new file mode 100644 index 0000000..f7a1b46 --- /dev/null +++ b/src/configotron/doc/mod.rs @@ -0,0 +1 @@ +pub mod grid; diff --git a/src/configotron/mod.rs b/src/configotron/mod.rs new file mode 100644 index 0000000..43ce38d --- /dev/null +++ b/src/configotron/mod.rs @@ -0,0 +1,2 @@ +pub mod conf; +pub mod doc; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index aa74dda..a4a3606 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,111 +1,7 @@ -use serde::{Deserialize, Serialize}; -use serde_yml; +mod configotron; -use std::fs; -use pad::PadStr; - -#[derive(Deserialize, Serialize, Debug)] -enum UserConfigItem { - String { - name: String, - max_length: u64 - }, - IPv4 { - name: String, - }, - Boolean { - name: String - }, - Number { - name: String, - max: Option, - min: Option - }, - Submenu(UserConfigData) -} - -#[derive(Deserialize, Serialize, Debug)] -struct UserConfigData { - name: String, - config_items: Vec -} - - -#[derive(thiserror::Error, Debug)] -pub enum ConfigotronError { - #[error("IO Operation failed: {}", ._0.to_string())] - IoError(#[from] std::io::Error), - - #[error("Failed to parse YAML File: {}", ._0.to_string())] - YamlParsingError(#[from] serde_yml::Error), - - #[error("Missing field {} (line {} col {}) {}", .missing_filed, .line, .col, .context)] - MissingFieldError{ - col: i32, - line: i32, - missing_filed: Box, - context: Box - } -} - -fn read_and_parse_yaml_file(input_file: &str) -> Result { - let file_content = fs::read_to_string(&input_file)?; - let config: UserConfigData = serde_yml::from_str(&file_content)?; - return Ok(config); -} - - -fn generate_text_report_from_item(item: &UserConfigItem) -> Vec { - return match item { - UserConfigItem::Boolean {name} => vec![String::from(name), String::from("[y/n]")] , - UserConfigItem::IPv4 { name } => vec![String::from(name), format!("[ipv4]")], - UserConfigItem::String { name, max_length } => vec![String::from(name), format!("[text, max length {}]", max_length)], - UserConfigItem::Number { name, min, max } => match (min, max) { - (None, None) => vec![String::from(name), String::from("[number]")], - (None, Some(maximum)) => vec![String::from(name), format!("[number, ..{}]", maximum)], - (Some(minmum), None) => vec![String::from(name), format!("[number, {}..]", minmum)], - (Some(minmum), Some(maximum)) => vec![String::from(name), format!("[number, {}..{}]", minmum, maximum)], - }, - UserConfigItem::Submenu(c) => generate_text_report(c) - } -} - -fn generate_text_report(config: &UserConfigData) -> Vec { - let lines: Vec> = config.config_items - .iter() - .map(|i| generate_text_report_from_item(i)) - .collect(); - - let block_width = lines - .iter() - .map(|l: &Vec| - l - .iter() - .map(|s: &String| s.chars().count()) - .max().unwrap_or(0) - ) - .max().unwrap_or(0); - - let line_seperator = format!("+{}+", "-".repeat(block_width + 2)); - - let config_name_width = config.name.chars().count() + 2; - let mut lines_flattend: Vec = lines - .iter() - .map(|i| i - .iter() - .map(|s| format!("| {} |", s.pad_to_width(block_width))) - .collect::>() - ) - .flat_map(|seg| [seg, vec![String::from(&line_seperator)]]) - .flatten() - .into_iter() - .map(|e| format!("{}{}", " ".repeat(config_name_width), e)) - .collect(); - - lines_flattend.insert(0, format!("{}{}", config.name.pad_to_width(config_name_width), line_seperator)); - - return lines_flattend; -} +use configotron::conf::{read_and_parse_yaml_file}; +use configotron::doc::grid::{DocGrid}; fn main() { @@ -116,8 +12,9 @@ fn main() { return; } }; - let res = generate_text_report(&data).join("\n"); - println!("{}", res); - println!("Hello, world!"); + let grid = DocGrid::from(&data); + println!("{}", grid.to_debug_string()); + + println!("Done!"); } diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..536618b --- /dev/null +++ b/test.txt @@ -0,0 +1,12 @@ + ++-------------+----------------------+-----------------+------+ +| ssbc_remote | (1) network settings | (1) ip | IPv4 | +| | +-----------------+------+ +| | | (2) subnet_mask | IPv4 | +| | +-----------------+------+ +| | | (3) gateway | IPv4 | +| +----------------------+-----------------+------+ +| | (2) wifi_ssid | text (64) | +| +----------------------+------------------------+ +| | (3) wifi_password | text (64) | ++-------------+----------------------+------------------------+