diff --git a/cli/src/compiler.rs b/cli/src/compiler.rs index f16481ad..2746cfb0 100644 --- a/cli/src/compiler.rs +++ b/cli/src/compiler.rs @@ -1,6 +1,7 @@ //! Entry module for unimarkup-rs. use std::{ + ffi::OsStr, fs, path::{Path, PathBuf}, }; @@ -23,13 +24,16 @@ use crate::log_id::{GeneralError, GeneralInfo}; /// /// Returns a [`GeneralError`] if error occurs during compilation. pub fn compile(config: Config) -> Result<(), GeneralError> { - let source = fs::read_to_string(&config.input).map_err(|error| { - pipe!( - GeneralError::FileRead, - format!("Could not read file: '{:?}'", &config.input), - add: AddonKind::Info(format!("Cause: {}", error)) - ) - })?; + let source: String = match config.input.extension().and_then(OsStr::to_str) { + Some("umi") => unsafe { String::from_utf8_unchecked(fs::read(&config.input).unwrap()) }, + _ => fs::read_to_string(&config.input).map_err(|error| { + pipe!( + GeneralError::FileRead, + format!("Could not read file: '{:?}'", &config.input), + add: AddonKind::Info(format!("Cause: {}", error)) + ) + })?, + }; let out_path = { if let Some(ref out_file) = config.output.file { diff --git a/cli/tests/umi.rs b/cli/tests/umi.rs index 14d04cda..9e25908a 100644 --- a/cli/tests/umi.rs +++ b/cli/tests/umi.rs @@ -1,6 +1,13 @@ +use std::iter::zip; use std::path::PathBuf; -use unimarkup_core::{commons::config::Config, Unimarkup}; +use unimarkup_core::{ + commons::config::Config, + inline::element::{Inline, InlineElement}, + parser::{document::Document, elements::blocks::Block}, + render::umi::Umi, + Unimarkup, +}; fn compile_um(config: Config) -> Option { let source = std::fs::read_to_string(&config.input).ok()?; @@ -8,8 +15,111 @@ fn compile_um(config: Config) -> Option { Some(Unimarkup::parse(&source, config)) } +fn equals_inlines_output(input: &Vec, output: &Vec) -> bool { + assert_eq!( + input.len(), + output.len(), + "Parsed Inlines does not have the same number of elements" + ); + + for (in_elem, out_elem) in zip(input.iter(), output.iter()) { + assert_eq!( + in_elem.as_unimarkup(), + out_elem.as_unimarkup(), + "Inline contains wrong content" + ) + } + true +} + +fn equals_blocks_output(input: &Vec, output: &Vec) -> bool { + assert_eq!( + input.len(), + output.len(), + "Parsed Blocks does not have the same length as the input" + ); + + for (in_elem, out_elem) in zip(input.iter(), output.iter()) { + assert_eq!( + in_elem.variant_str(), + out_elem.variant_str(), + "Blocks did not match up at Index" + ); + let block_in = in_elem.clone(); + let block_out = out_elem.clone(); + match (block_in, block_out) { + (Block::Heading(block_in), Block::Heading(block_out)) => { + assert_eq!(block_in.id, block_out.id, "Heading ids do not match!"); + assert_eq!( + block_in.level, block_out.level, + "Heading Levels do not match!" + ); + assert!(equals_inlines_output(&block_in.content, &block_out.content)); + assert_eq!( + block_in.attributes, block_out.attributes, + "Heading Attributes do not match!" + ); + } + (Block::Paragraph(block_in), Block::Paragraph(block_out)) => { + assert!(equals_inlines_output(&block_in.content, &block_out.content)); + } + (Block::VerbatimBlock(block_in), Block::VerbatimBlock(block_out)) => { + assert_eq!( + block_in.content, block_out.content, + "Verbatim Content does not match" + ); + assert_eq!( + block_in.data_lang, block_out.data_lang, + "Verbatim Data_Lang does not match" + ); + assert_eq!( + block_in.attributes, block_out.attributes, + "Verbatim Attributes do not match" + ); + assert_eq!( + block_in.implicit_closed, block_out.implicit_closed, + "Verbatim Implicit_Closed does not match" + ); + assert_eq!( + block_in.tick_len, block_out.tick_len, + "Verbatim Tick-Len does not match" + ); + } + (Block::BulletList(block_in), Block::BulletList(block_out)) => { + assert_eq!( + block_in.entries.len(), + block_out.entries.len(), + "Bullet List entry count does not match" + ); + + for (in_entry, out_entry) in zip(block_in.entries.iter(), block_out.entries.iter()) + { + assert_eq!( + in_entry.keyword, out_entry.keyword, + "Bullet List Entry Keyword does not match" + ); + assert!(equals_inlines_output(&in_entry.heading, &out_entry.heading)); + assert!(equals_blocks_output(&in_entry.body, &out_entry.body)); + } + } + _ => return false, + } + } + + true +} + +fn equals_umi_output(input: &Document, output: &Document) -> bool { + assert_eq!( + input.config, output.config, + "Parsed UMI Config differs from original Config" + ); + + equals_blocks_output(&input.blocks, &output.blocks) +} + #[test] -fn umi_loop() { +fn umi_supported() { let mut config = Config::default(); let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .canonicalize() @@ -21,11 +131,11 @@ fn umi_loop() { let mut umi = um.render_umi().unwrap(); let workbook = umi.create_workbook(); - let looped_doc = workbook.create_um(); + let looped_doc = &Umi::create_um(workbook.to_string().as_str(), &mut workbook.config).unwrap(); + let input = um.get_document(); - assert_eq!( - looped_doc.blocks.len(), - um.get_document().blocks.len(), - "Parsed UMI file differs from original UM." + assert!( + equals_umi_output(input, looped_doc), + "Output does not equal the Input" ); } diff --git a/commons/src/config/preamble.rs b/commons/src/config/preamble.rs index 779024b2..a3374db4 100644 --- a/commons/src/config/preamble.rs +++ b/commons/src/config/preamble.rs @@ -54,6 +54,7 @@ pub struct I18n { #[arg(long, value_parser = parse_to_hashset::, required = false, default_value = "")] #[serde(with = "locale::serde::multiple", default)] + #[serde(skip_serializing_if = "HashSet::is_empty")] pub output_langs: HashSet, } @@ -72,9 +73,11 @@ impl ConfigFns for I18n { #[derive(Args, Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RenderConfig { #[arg(long = "ignore-file", value_parser = parse_ignore_file, required = false, default_value = "")] + #[serde(skip_serializing_if = "HashSet::is_empty")] #[serde(default)] pub ignore: HashSet, #[arg(long, value_parser = parse_parameter, required = false, default_value = "")] + #[serde(skip_serializing_if = "HashMap::is_empty")] #[serde(default)] pub parameter: HashMap, #[arg(long)] @@ -103,8 +106,11 @@ impl ConfigFns for RenderConfig { #[derive(Args, Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Citedata { #[arg(long = "citation-style")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] pub style: Option, #[arg(long, value_parser = parse_to_hashset::, required = false, default_value = "")] + #[serde(skip_serializing_if = "HashSet::is_empty")] #[serde(default)] pub references: HashSet, } @@ -141,15 +147,23 @@ impl ConfigFns for Citedata { #[derive(Args, Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Metadata { #[arg(long)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] pub title: Option, #[arg(long, value_parser = parse_to_hashset::, required = false, default_value = "")] + #[serde(skip_serializing_if = "HashSet::is_empty")] #[serde(default)] pub authors: HashSet, #[arg(long)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] pub description: Option, #[arg(long)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] pub base: Option, #[arg(long, value_parser = parse_to_hashset::, required = false, default_value = "")] + #[serde(skip_serializing_if = "HashSet::is_empty")] #[serde(default)] pub fonts: HashSet, } diff --git a/core/src/lib.rs b/core/src/lib.rs index a680059e..cde2eeb2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,3 +1,5 @@ +use std::ffi::OsStr; + pub use unimarkup_commons as commons; pub use unimarkup_inline as inline; pub use unimarkup_parser as parser; @@ -25,8 +27,13 @@ impl Unimarkup { /// * `um_content` - String containing Unimarkup elements. /// * `config` - Unimarkup configuration to be used on top of preambles. pub fn parse(um_content: &str, mut config: Config) -> Self { - Unimarkup { - doc: parser::parse_unimarkup(um_content, &mut config), + match config.input.extension().and_then(OsStr::to_str) { + Some("umi") => Unimarkup { + doc: Umi::create_um(um_content, &mut config).unwrap(), + }, + _ => Unimarkup { + doc: parser::parse_unimarkup(um_content, &mut config), + }, } } diff --git a/render/src/log_id.rs b/render/src/log_id.rs index 61228ee6..c048764c 100644 --- a/render/src/log_id.rs +++ b/render/src/log_id.rs @@ -9,3 +9,21 @@ pub enum RenderError { #[error("Output format `append()` failed. See log: '{}: {}'", .0.event_id, .0.entry_id)] BadAppend(FinalizedEvent), } + +#[derive(Debug, Clone, ErrLogId, Error, PartialEq, Eq)] +pub enum UmiParserError { + #[error("The UMI Parser failed to parse the Element Kind at Position {}.", .0)] + UnknownKind(u8), + + #[error("The UMI Parser failed to parse Property {} from Element at Position {}.", (.0).0, (.0).1)] + MissingProperty((String, u8)), + + #[error("The UMI Parser failed to parse Property Value {} from Element at Position {}.", (.0).0, (.0).1)] + InvalidPropertyValue((String, u8)), + + #[error("The UMI Parser failed to parse the corresponding Document.")] + NoUnimarkupDetected, + + #[error("The UMI Parser failed to parse the Element at Position {}, because it has not Parent Element wwith Depth 0.", .0)] + MissingParentElement(u8), +} diff --git a/render/src/umi/mod.rs b/render/src/umi/mod.rs index 1742aeed..0b09beb1 100644 --- a/render/src/umi/mod.rs +++ b/render/src/umi/mod.rs @@ -1,10 +1,15 @@ use std::collections::HashMap; +use std::path::PathBuf; use crate::render::OutputFormat; -use spreadsheet_ods::{read_ods_buf, write_ods_buf_uncompressed, Sheet, WorkBook}; +use crate::log_id::UmiParserError; +use spreadsheet_ods::{ + read_ods_buf, write_ods_buf_uncompressed, Sheet, Value, ValueType, WorkBook, +}; use unimarkup_commons::config::Config; use unimarkup_commons::lexer::{ + position::Position, symbol::SymbolKind, token::{iterator::TokenIterator, lex_str, TokenKind}, }; @@ -24,11 +29,59 @@ use unimarkup_parser::{ pub mod render; +fn unpack_content_safe(value: Value) -> String { + if value.value_type() == ValueType::Text { + value.as_str_or("").into() + } else { + value.as_cow_str_or("").into() + } +} + +const DEFAULT_CONTENT_COLUMN: u32 = 5; +const DEFAULT_ATTRIBUTES_COLUMN: u32 = 6; + +fn retrieve_localised_content(sheet: &Sheet, row_index: u32, col_index: u32) -> String { + let content_localised = + unpack_content_safe(sheet.cell(row_index, col_index).unwrap_or_default().value); + if content_localised.is_empty() { + unpack_content_safe( + sheet + .cell(row_index, DEFAULT_CONTENT_COLUMN) + .unwrap_or_default() + .value, + ) + } else { + content_localised + } +} + +fn retrieve_localised_attributes(sheet: &Sheet, row_index: u32, col_index: u32) -> String { + let attributes_localised = sheet + .cell(row_index, col_index) + .unwrap_or_default() + .value + .as_str_opt() + .unwrap_or_default() + .to_string(); + if attributes_localised.is_empty() { + sheet + .cell(row_index, DEFAULT_ATTRIBUTES_COLUMN) + .unwrap_or_default() + .value + .as_str_opt() + .unwrap_or_default() + .to_string() + } else { + attributes_localised + } +} + #[derive(Debug, Default, Clone)] pub struct UmiRow { position: u8, id: String, kind: String, + properties: String, depth: u8, content: String, attributes: String, @@ -39,6 +92,7 @@ impl UmiRow { position: u8, id: String, kind: String, + properties: String, depth: u8, content: String, attributes: String, @@ -47,6 +101,7 @@ impl UmiRow { position, id, kind, + properties, depth, content, attributes, @@ -80,31 +135,45 @@ impl Umi { sheet.set_value(0, 0, "position"); sheet.set_value(0, 1, "id"); sheet.set_value(0, 2, "kind"); - sheet.set_value(0, 3, "depth"); + sheet.set_value(0, 3, "properties"); + sheet.set_value(0, 4, "depth"); sheet.set_value( 0, - 4, + 5, String::from("content-") + self.lang.to_string().as_str(), ); sheet.set_value( 0, - 5, + 6, String::from("attributes-") + self.lang.to_string().as_str(), ); - // Row 1: Config + let mut hashmap: HashMap = HashMap::new(); + hashmap.insert( + String::from("input_path"), + self.config.input.to_str().unwrap_or_default().to_string(), + ); + let properties = serde_json::to_string(&hashmap).unwrap_or(String::from("{}")); + + // Row 1: Preamble + Input File sheet.set_value(1, 0, 0); - sheet.set_value(1, 2, "config"); - sheet.set_value(1, 4, serde_yaml::to_string(&self.config).unwrap()); + sheet.set_value(1, 2, "Preamble"); + sheet.set_value(1, 3, properties); + sheet.set_value( + 1, + 5, + serde_yaml::to_string(&self.config.preamble).unwrap_or_default(), + ); for element in &self.elements { let row = element.position + 2; sheet.set_value(row.into(), 0, element.position + 1); sheet.set_value(row.into(), 1, element.id.clone()); sheet.set_value(row.into(), 2, element.kind.clone()); - sheet.set_value(row.into(), 3, element.depth); - sheet.set_value(row.into(), 4, element.content.clone()); - sheet.set_value(row.into(), 5, element.attributes.clone()); + sheet.set_value(row.into(), 3, element.properties.clone()); + sheet.set_value(row.into(), 4, element.depth); + sheet.set_value(row.into(), 5, element.content.clone()); + sheet.set_value(row.into(), 6, element.attributes.clone()); } wb.push_sheet(sheet); @@ -131,101 +200,130 @@ impl Umi { } } - fn read_row(&mut self, line: usize) -> Block { + fn read_row(&mut self, line: usize) -> Result { let mut current_line = self.elements[line].clone(); - let attributes: HashMap = - serde_json::from_str(¤t_line.attributes).unwrap(); + let properties: HashMap = + serde_json::from_str(¤t_line.properties).unwrap_or_default(); match current_line.kind.as_str() { "Heading" => { let heading = Heading { id: current_line.id.clone(), level: unimarkup_parser::elements::atomic::HeadingLevel::try_from( - attributes.get("level").unwrap().as_str(), + properties + .get("level") + .ok_or(UmiParserError::MissingProperty(( + "level".into(), + current_line.position, + )))? + .as_str(), ) - .unwrap(), + .ok() + .ok_or(UmiParserError::InvalidPropertyValue(( + "level".into(), + current_line.position, + )))?, content: self.read_inlines(current_line.content.clone()), - attributes: (attributes.get("attributes").cloned()).filter(|s| !s.is_empty()), - start: serde_json::from_str(attributes.get("start").unwrap()).unwrap(), - end: serde_json::from_str(attributes.get("end").unwrap()).unwrap(), + attributes: Some(current_line.attributes).filter(|s| !s.is_empty()), + start: Position::new(1, 1), // Fallback in case content has been changed manually in .umi + end: Position::new(1, 1), // Fallback in case content has been changed manually in .umi }; - Block::Heading(heading) + Ok(Block::Heading(heading)) } "Paragraph" => { let paragraph = Paragraph { content: self.read_inlines(current_line.content.clone()), }; - Block::Paragraph(paragraph) + Ok(Block::Paragraph(paragraph)) } "VerbatimBlock" => { let verbatim = VerbatimBlock { - content: current_line.content.clone(), - data_lang: attributes.get("data_lang").cloned(), - attributes: attributes.get("attributes").cloned(), - implicit_closed: attributes.get("implicit_closed").unwrap().parse().unwrap(), - tick_len: attributes.get("tick_len").unwrap().parse().unwrap(), - start: serde_json::from_str(attributes.get("start").unwrap()).unwrap(), - end: serde_json::from_str(attributes.get("end").unwrap()).unwrap(), + content: current_line.content.clone(), // TODO: use inline parser, but only allow 'logic' and plain text + data_lang: properties.get("data_lang").cloned(), + attributes: Some(current_line.attributes).filter(|s| !s.is_empty()), + implicit_closed: properties + .get("implicit_closed") + .ok_or(UmiParserError::MissingProperty(( + "implicit_closed".into(), + current_line.position, + )))? + .parse() + .unwrap_or_default(), + tick_len: properties + .get("tick_len") + .ok_or(UmiParserError::MissingProperty(( + "tick_len".into(), + current_line.position, + )))? + .parse() + .unwrap_or_default(), + start: Position::new(1, 1), // Fallback in case content has been changed manually in .umi + end: Position::new(1, 1), // Fallback in case content has been changed manually in .umi }; - Block::VerbatimBlock(verbatim) + Ok(Block::VerbatimBlock(verbatim)) } "BulletList" => { let mut bullet_list = BulletList { entries: vec![], - start: serde_json::from_str(attributes.get("start").unwrap()).unwrap(), - end: serde_json::from_str(attributes.get("end").unwrap()).unwrap(), + start: Position::new(1, 1), // Fallback in case content has been changed manually in .umi + end: Position::new(1, 1), // Fallback in case content has been changed manually in .umi }; let bullet_list_depth = current_line.depth; let mut current_line_index = line + 1; - current_line = self.fetch_next_line(current_line_index).unwrap(); + current_line = self.fetch_next_line(current_line_index).unwrap_or_default(); while current_line.depth > bullet_list_depth { if current_line.depth == bullet_list_depth + 1 { // Append Element to Bullet List let block = self.read_row(current_line_index); let bullet_list_entry = match block { - Block::BulletListEntry(block) => block, - _ => panic!(), + Ok(Block::BulletListEntry(block)) => block, + _ => break, }; bullet_list.entries.append(&mut vec![bullet_list_entry]); - } else { - break; } current_line_index += 1; - let fetched = self.fetch_next_line(current_line_index); - if fetched.is_none() { + let Some(fetched) = self.fetch_next_line(current_line_index) else { break; - } - current_line = fetched.unwrap(); + }; + current_line = fetched; } - Block::BulletList(bullet_list) + Ok(Block::BulletList(bullet_list)) } "BulletListEntry" => { let mut bullet_list_entry = BulletListEntry { keyword: TokenKind::from(SymbolKind::from( - attributes.get("keyword").unwrap().as_str(), + properties + .get("keyword") + .ok_or(UmiParserError::MissingProperty(( + "keyword".into(), + current_line.position, + )))? + .as_str(), )) .try_into() - .unwrap(), - heading: self.read_inlines(attributes.get("heading").unwrap().to_string()), + .ok() + .ok_or(UmiParserError::InvalidPropertyValue(( + "keyword".into(), + current_line.position, + )))?, + heading: self.read_inlines(current_line.content.clone()), body: vec![], - start: serde_json::from_str(attributes.get("start").unwrap()).unwrap(), - end: serde_json::from_str(attributes.get("end").unwrap()).unwrap(), + start: Position::new(1, 1), // Fallback in case content has been changed manually in .umi + end: Position::new(1, 1), // Fallback in case content has been changed manually in .umi }; let bullet_list_entry_depth = current_line.depth; let mut current_line_index = line + 1; - current_line = self.fetch_next_line(current_line_index).unwrap(); + current_line = self.fetch_next_line(current_line_index).unwrap_or_default(); while current_line.depth > bullet_list_entry_depth { if current_line.depth == bullet_list_entry_depth + 1 { // Append Element to Bullet List Entry Body - let block = self.read_row(current_line_index); + let block = self.read_row(current_line_index)?; bullet_list_entry.body.append(&mut vec![block]); - } else { - break; } current_line_index += 1; @@ -234,87 +332,144 @@ impl Umi { if fetched.is_none() { break; } - current_line = fetched.unwrap(); + current_line = fetched.unwrap_or_default(); } - Block::BulletListEntry(bullet_list_entry) + Ok(Block::BulletListEntry(bullet_list_entry)) } - &_ => panic!(), + &_ => Err(UmiParserError::UnknownKind(current_line.position)), } } - pub fn create_um(&mut self) -> Document { - self.elements.clear(); - debug_assert!(!self.ods.is_empty()); + pub fn create_um(um_content: &str, config: &mut Config) -> Result { + let mut umi = Umi::with_um( + vec![], + config.clone(), + config.preamble.i18n.lang.to_string().to_owned(), + ); + umi.ods = um_content.into(); - let wb: WorkBook = read_ods_buf(&self.ods).unwrap(); + let wb: WorkBook = read_ods_buf(&umi.ods).unwrap_or_default(); let sheet = wb.sheet(0); let rows = sheet.used_grid_size().0; + // Load Stored Config Values from Sheet + let hash_map_input: HashMap = serde_json::from_str( + sheet + .cell(1, 3) + .unwrap_or_default() + .value + .as_str_opt() + .unwrap_or_default(), + ) + .unwrap_or_default(); + let input_path: PathBuf = + PathBuf::from(hash_map_input.get("input_path").unwrap_or(&String::new())); + + umi.config.preamble = serde_yaml::from_str( + sheet + .cell(1, 4) + .unwrap_or_default() + .value + .as_cow_str_or("") + .to_string() + .as_str(), + ) + .unwrap_or_default(); + umi.config.input = input_path; + + // Determine the correct column for parsing Locale Content and Attributes + let mut index = DEFAULT_CONTENT_COLUMN; + let mut localised_content_index = 0; + let mut localised_attributes_index = 0; + loop { + let next = sheet + .cell(0, index) + .unwrap_or_default() + .value + .as_str_opt() + .unwrap_or_default() + .to_string(); + if (localised_content_index != 0 && localised_attributes_index != 0) || next.is_empty() + { + break; + } + if next == (String::from("content-") + umi.lang.as_str()) { + localised_content_index = index; + } + if next == (String::from("attributes-") + umi.lang.as_str()) { + localised_attributes_index = index; + } + index += 1; + } + for row_index in 2..rows { - self.elements.push(UmiRow::new( - sheet.cell(row_index, 0).unwrap().value.as_u8_opt().unwrap(), + umi.elements.push(UmiRow::new( + // position + sheet + .cell(row_index, 0) + .unwrap_or_default() + .value + .as_u8_opt() + .unwrap_or(0), + // id sheet .cell(row_index, 1) - .unwrap() + .unwrap_or_default() .value .as_str_opt() .unwrap_or_default() .to_string(), + // kind sheet .cell(row_index, 2) - .unwrap() + .unwrap_or_default() .value .as_str_opt() .unwrap_or_default() .to_string(), - sheet.cell(row_index, 3).unwrap().value.as_u8_opt().unwrap(), + // properties sheet - .cell(row_index, 4) - .unwrap() + .cell(row_index, 3) + .unwrap_or_default() .value .as_str_opt() .unwrap_or_default() .to_string(), + // depth sheet - .cell(row_index, 5) - .unwrap() - .value - .as_str_opt() + .cell(row_index, 4) .unwrap_or_default() - .to_string(), + .value + .as_u8_opt() + .unwrap_or(0), + // content + retrieve_localised_content(sheet, row_index, localised_content_index), + // attributes + retrieve_localised_attributes(sheet, row_index, localised_attributes_index), )) } let mut um: Vec = vec![]; let mut index = 0; - while index < self.elements.len() { - if self.elements[index].depth == 0 { - um.push(self.read_row(index)); + while index < umi.elements.len() { + if umi.elements[index].depth == 0 { + um.push(umi.read_row(index)?); + } else { + // TODO Warn if a proper parent element is missing } index += 1; } - let mut config = self.config.clone(); - config.preamble = serde_yaml::from_str( - sheet - .cell(1, 4) - .unwrap() - .value - .as_cow_str_or("") - .to_string() - .as_str(), - ) - .unwrap(); - Document { + Ok(Document { blocks: um, - config, + config: umi.config.clone(), macros: vec![], variables: vec![], metadata: vec![], resources: vec![], - } + }) } } diff --git a/render/src/umi/render.rs b/render/src/umi/render.rs index 4c2f31b1..1ec258ae 100644 --- a/render/src/umi/render.rs +++ b/render/src/umi/render.rs @@ -44,15 +44,16 @@ impl Renderer for UmiRenderer { let content = self.render_inlines(¶graph.content, context)?; let hashmap: HashMap = HashMap::new(); - let attributes = serde_json::to_string(&hashmap).unwrap(); + let properties = serde_json::to_string(&hashmap).unwrap_or(String::from("{}")); let paragraph = UmiRow::new( self.pos, String::new(), String::from(Block::Paragraph(paragraph.to_owned()).variant_str()), + properties, self.depth, content.elements[0].content.clone(), - attributes, + String::new(), ); self.pos += 1; @@ -73,32 +74,21 @@ impl Renderer for UmiRenderer { String::from("data_lang"), verbatim.data_lang.clone().unwrap_or_default(), ); - hashmap.insert( - String::from("attributes"), - verbatim.attributes.clone().unwrap_or_default(), - ); hashmap.insert(String::from("tick_len"), verbatim.tick_len.to_string()); hashmap.insert( String::from("implicit_closed"), verbatim.implicit_closed.to_string(), ); - hashmap.insert( - String::from("start"), - serde_json::to_string(&verbatim.start).unwrap(), - ); - hashmap.insert( - String::from("end"), - serde_json::to_string(&verbatim.end).unwrap(), - ); - let attributes = serde_json::to_string(&hashmap).unwrap(); + let properties = serde_json::to_string(&hashmap).unwrap_or(String::from("{}")); let verbatim = UmiRow::new( self.pos, String::new(), String::from("VerbatimBlock"), + properties, self.depth, verbatim.content.clone(), - attributes, + verbatim.attributes.clone().unwrap_or_default(), ); self.pos += 1; @@ -115,20 +105,8 @@ impl Renderer for UmiRenderer { context: &Context, ) -> Result { let mut hashmap: HashMap = HashMap::new(); - hashmap.insert( - String::from("attributes"), - heading.attributes.clone().unwrap_or_default(), - ); hashmap.insert(String::from("level"), heading.level.as_str().to_string()); - hashmap.insert( - String::from("start"), - serde_json::to_string(&heading.start).unwrap(), - ); - hashmap.insert( - String::from("end"), - serde_json::to_string(&heading.end).unwrap(), - ); - let attributes = serde_json::to_string(&hashmap).unwrap(); + let properties = serde_json::to_string(&hashmap).unwrap_or(String::from("{}")); let content = self.render_inlines(&heading.content, context)?; @@ -136,9 +114,10 @@ impl Renderer for UmiRenderer { self.pos, heading.id.clone(), String::from("Heading"), + properties, self.depth, content.elements[0].content.clone(), - attributes, + heading.attributes.clone().unwrap_or_default(), ); self.pos += 1; @@ -154,24 +133,17 @@ impl Renderer for UmiRenderer { bullet_list: &unimarkup_parser::elements::indents::BulletList, context: &Context, ) -> Result { - let mut hashmap: HashMap = HashMap::new(); - hashmap.insert( - String::from("start"), - serde_json::to_string(&bullet_list.start).unwrap(), - ); - hashmap.insert( - String::from("end"), - serde_json::to_string(&bullet_list.end).unwrap(), - ); - let attributes = serde_json::to_string(&hashmap).unwrap(); + let hashmap: HashMap = HashMap::new(); + let properties = serde_json::to_string(&hashmap).unwrap_or(String::from("{}")); let bullet_list_heading = UmiRow::new( self.pos, String::new(), String::from("BulletList"), + properties, self.depth, String::new(), - attributes, + String::new(), ); self.pos += 1; @@ -200,22 +172,7 @@ impl Renderer for UmiRenderer { String::from("keyword"), bullet_list_entry.keyword.as_str().to_string(), ); - hashmap.insert( - String::from("heading"), - self.render_inlines(&bullet_list_entry.heading, context) - .unwrap() - .elements[0] - .content - .clone(), - ); - hashmap.insert( - String::from("start"), - serde_json::to_string(&bullet_list_entry.start).unwrap(), - ); - hashmap.insert( - String::from("end"), - serde_json::to_string(&bullet_list_entry.end).unwrap(), - ); + let properties = serde_json::to_string(&hashmap).unwrap_or(String::from("{}")); let mut entry = Umi::with_um( vec![UmiRow::new( self.pos, @@ -223,13 +180,13 @@ impl Renderer for UmiRenderer { Block::BulletListEntry(bullet_list_entry.to_owned()) .variant_str() .to_string(), + properties, self.depth, - self.render_inlines(&bullet_list_entry.heading, context) - .unwrap() + self.render_inlines(&bullet_list_entry.heading, context)? .elements[0] .content .clone(), - serde_json::to_string(&hashmap).unwrap(), + String::new(), )], context.get_config().clone(), context.get_lang().to_string(), @@ -262,6 +219,7 @@ impl Renderer for UmiRenderer { self.pos, String::new(), String::from("Inline"), + String::new(), self.depth, res, String::new(),