diff --git a/cli/src/error.rs b/cli/src/error.rs index 84a0d3f3..3d5eaeb6 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -29,6 +29,7 @@ impl From for CliError { CoreError::Backend(log_id) => CliError::Core(log_id), CoreError::Element(log_id) => CliError::Core(log_id), CoreError::Config(log_id) => CliError::Core(log_id), + CoreError::Security(log_id) => CliError::Core(log_id), } } } diff --git a/core/Cargo.toml b/core/Cargo.toml index 65f39ddd..e670f0e3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -28,3 +28,5 @@ clap = { version = "^3.1.0", features = ["derive", "cargo", "env"] } sha3 = "0.10" hex = "0.4" unimarkup_inline = { path = "../inline/"} +syntect = "4.6" +lazy_static = "1.4.0" diff --git a/core/src/backend/inline_formatting.rs b/core/src/backend/inline_formatting.rs deleted file mode 100644 index 9aca0439..00000000 --- a/core/src/backend/inline_formatting.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::{collections::VecDeque, fmt::Debug}; - -use pest::iterators::Pair; -use pest::Parser; - -use crate::backend::BackendError; -use crate::frontend::parser::{Rule, UnimarkupParser}; -use crate::log_id::{LogId, SetLog}; - -use super::{log_id::InlineErrLogId, Render}; -/// [`Plain`] is one of the inline formatting types, which contains the raw text as String. -#[derive(Debug)] -pub struct Plain { - content: String, -} -/// [`Bold`] is one of the inline formatting types -#[derive(Debug)] -pub struct Bold { - content: VecDeque, -} -/// [`Italic`] is one of the inline formatting types. -#[derive(Debug)] -pub struct Italic { - content: VecDeque, -} -/// [`Subscript`] is one of the inline formatting types. -#[derive(Debug)] -pub struct Subscript { - content: VecDeque, -} -/// [`Superscript`] is one of the inline formatting types. -#[derive(Debug)] -pub struct Superscript { - content: VecDeque, -} -/// [`Verbatim`] is one of the inline formatting types. -#[derive(Debug)] -pub struct Verbatim { - content: String, -} -/// [`FormatTypes`] is an enum of all the inline formatting types. -#[derive(Debug)] -pub enum FormatTypes { - /// Represents the [`Bold`] FormatType - Bold(Bold), - /// Represents the [`Italic`] FormatType - Italic(Italic), - /// Represents the [`Subscript`] FormatType - Subscript(Subscript), - /// Represents the [`Superscript`] FormatType - Superscript(Superscript), - /// Represents the [`Verbatim`] FormatType - Verbatim(Verbatim), - /// Represents the [`Plain`] FormatType - Plain(Plain), -} - -/// [`parse_inline`] parses through the content of a [`UnimarkupBlock`] and returns a VecDeque of Formattypes -pub fn parse_inline(source: &str) -> Result, BackendError> { - let mut rule_pairs = UnimarkupParser::parse(Rule::inline_format, source).map_err(|err| { - BackendError::General( - (InlineErrLogId::NoInlineDetected as LogId) - .set_log("No inline format detected!", file!(), line!()) - .add_info(&format!("Given: {}", source)) - .add_info(&format!("Cause: {}", err)), - ) - })?; - - let mut inline_format = VecDeque::::new(); - - if let Some(inline) = rule_pairs.next() { - inline_format.append(&mut pair_into_format_types(inline)); - } - - Ok(inline_format) -} - -fn create_format_types(pair: Pair) -> VecDeque { - let mut content: VecDeque = VecDeque::::new(); - - match pair.as_rule() { - Rule::plain => { - let plain = Plain { - content: pair.as_str().to_string(), - }; - content.push_back(FormatTypes::Plain(plain)); - } - Rule::italic_inline => { - let italic = Italic { - content: pair_into_format_types(pair), - }; - content.push_back(FormatTypes::Italic(italic)); - } - Rule::subscript_inline => { - let subscript = Subscript { - content: pair_into_format_types(pair), - }; - content.push_back(FormatTypes::Subscript(subscript)); - } - Rule::superscript_inline => { - let superscript = Superscript { - content: pair_into_format_types(pair), - }; - content.push_back(FormatTypes::Superscript(superscript)); - } - Rule::bold_inline => { - let bold = Bold { - content: pair_into_format_types(pair), - }; - content.push_back(FormatTypes::Bold(bold)); - } - Rule::verbatim_inline => { - let verbatim = Verbatim { - content: pair.into_inner().as_str().to_string(), - }; - content.push_back(FormatTypes::Verbatim(verbatim)); - } - _ => unreachable!("No other inline types allowed."), - } - - content -} - -fn pair_into_format_types(pair: Pair) -> VecDeque { - pair.into_inner().flat_map(create_format_types).collect() -} - -impl Render for Bold { - fn render_html(&self) -> Result { - let mut html = String::default(); - html.push_str(""); - for element in &self.content { - html.push_str( - &element - .render_html() - .expect("At least one or more formatting types expected"), - ); - } - html.push_str(""); - Ok(html) - } -} - -impl Render for Italic { - fn render_html(&self) -> Result { - let mut html = String::default(); - html.push_str(""); - for element in &self.content { - html.push_str( - &element - .render_html() - .expect("At least one or more formatting types expected"), - ); - } - html.push_str(""); - Ok(html) - } -} - -impl Render for Subscript { - fn render_html(&self) -> Result { - let mut html = String::default(); - html.push_str(""); - for element in &self.content { - html.push_str( - &element - .render_html() - .expect("At least one or more formatting types expected"), - ); - } - html.push_str(""); - Ok(html) - } -} - -impl Render for Superscript { - fn render_html(&self) -> Result { - let mut html = String::default(); - html.push_str(""); - for element in &self.content { - html.push_str( - &element - .render_html() - .expect("At least one or more formatting types expected"), - ); - } - html.push_str(""); - Ok(html) - } -} - -impl Render for Verbatim { - fn render_html(&self) -> Result { - let mut html = String::default(); - html.push_str("
");
-        html.push_str(&self.content);
-        html.push_str("
"); - Ok(html) - } -} - -impl Render for Plain { - fn render_html(&self) -> Result { - Ok(self.content.clone()) - } -} - -impl Render for FormatTypes { - fn render_html(&self) -> Result { - match self { - FormatTypes::Bold(content) => content.render_html(), - FormatTypes::Italic(content) => content.render_html(), - FormatTypes::Subscript(content) => content.render_html(), - FormatTypes::Superscript(content) => content.render_html(), - FormatTypes::Verbatim(content) => content.render_html(), - FormatTypes::Plain(content) => content.render_html(), - } - } -} - -impl Render for VecDeque { - fn render_html(&self) -> Result { - let mut html = String::default(); - - for element in self { - html.push_str( - &element - .render_html() - .expect("Rendered format types expected"), - ); - } - Ok(html) - } -} diff --git a/core/src/backend/loader.rs b/core/src/backend/loader.rs index c254f52a..68642267 100644 --- a/core/src/backend/loader.rs +++ b/core/src/backend/loader.rs @@ -3,13 +3,14 @@ use std::{collections::VecDeque, str::FromStr}; use rusqlite::Connection; use crate::{ - backend::{BackendError, Render}, + backend::BackendError, elements::{types, types::UnimarkupType, HeadingBlock, ParagraphBlock, VerbatimBlock}, log_id::{LogId, SetLog}, middleend::{self, ContentIrLine}, + unimarkup_block::UnimarkupBlockKind, }; -use super::{log_id::LoaderErrLogId, RenderBlock}; +use super::log_id::LoaderErrLogId; /// Trait that must be implemented for a [`UnimarkupType`] to be stored in IR pub trait ParseFromIr { @@ -33,27 +34,31 @@ pub trait ParseFromIr { /// # Arguments /// /// * `connection` - [`rusqlite::Connection`] used for interaction with IR -pub fn get_blocks_from_ir(connection: &mut Connection) -> Result, BackendError> { - let mut blocks: Vec> = vec![]; +pub fn get_blocks_from_ir( + connection: &mut Connection, +) -> Result, BackendError> { + let mut blocks: Vec = vec![]; let mut content_lines: VecDeque = middleend::get_content_lines(connection)?.into(); while let Some(line) = content_lines.get(0) { let um_type = parse_um_type(&line.um_type)?; - let block: Box = match um_type { + let block = match um_type { // UnimarkupType::List => todo!(), - UnimarkupType::Heading => Box::new(HeadingBlock::parse_from_ir(&mut content_lines)?), + UnimarkupType::Heading => { + UnimarkupBlockKind::Heading(HeadingBlock::parse_from_ir(&mut content_lines)?) + } UnimarkupType::Paragraph => { - Box::new(ParagraphBlock::parse_from_ir(&mut content_lines)?) + UnimarkupBlockKind::Paragraph(ParagraphBlock::parse_from_ir(&mut content_lines)?) } UnimarkupType::VerbatimBlock => { - Box::new(VerbatimBlock::parse_from_ir(&mut content_lines)?) + UnimarkupBlockKind::Verbatim(VerbatimBlock::parse_from_ir(&mut content_lines)?) } _ => { let _ = content_lines.pop_front(); - Box::new(HeadingBlock::default()) + UnimarkupBlockKind::Paragraph(ParagraphBlock::default()) } }; diff --git a/core/src/backend/mod.rs b/core/src/backend/mod.rs index 373522e1..72fb0227 100644 --- a/core/src/backend/mod.rs +++ b/core/src/backend/mod.rs @@ -4,14 +4,12 @@ //! //! [`UnimarkupBlocks`]: crate::frontend::UnimarkupBlocks -use crate::{config::Config, unimarkup::UnimarkupDocument}; +use crate::{config::Config, unimarkup::UnimarkupDocument, unimarkup_block::UnimarkupBlockKind}; use rusqlite::Connection; -mod inline_formatting; mod loader; mod renderer; -pub use inline_formatting::*; pub use loader::ParseFromIr; pub use renderer::*; @@ -20,9 +18,6 @@ use self::error::BackendError; pub mod error; pub mod log_id; -/// Abstract type for elements that implement the [`Render`] trait -pub type RenderBlock = Box; - /// This is the entry function for the [`backend`](crate::backend) module. It fetches /// [`UnimarkupBlocks`] from IR, renders them to the wanted output format and writes the resulting output. /// @@ -34,7 +29,7 @@ pub type RenderBlock = Box; /// /// [`UnimarkupBlocks`]: crate::frontend::UnimarkupBlocks pub fn run(connection: &mut Connection, config: Config) -> Result { - let blocks: Vec = loader::get_blocks_from_ir(connection)?; + let blocks: Vec = loader::get_blocks_from_ir(connection)?; Ok(UnimarkupDocument { elements: blocks, diff --git a/core/src/backend/renderer.rs b/core/src/backend/renderer.rs index e6f89a27..2ac666cf 100644 --- a/core/src/backend/renderer.rs +++ b/core/src/backend/renderer.rs @@ -1,6 +1,7 @@ use crate::backend::error::BackendError; -use super::RenderBlock; +/// Abstract type for elements that implement the [`Render`] trait +pub type RenderBlock = Box; /// Used to provide render implementation for supported output formats pub trait Render { diff --git a/core/src/elements/heading_block.rs b/core/src/elements/heading_block.rs index 9e5fa6ed..79580653 100644 --- a/core/src/elements/heading_block.rs +++ b/core/src/elements/heading_block.rs @@ -56,7 +56,7 @@ impl Default for HeadingLevel { } } -impl From for usize { +impl From for u8 { fn from(level: HeadingLevel) -> Self { match level { HeadingLevel::Level1 => 1, @@ -152,20 +152,22 @@ impl HeadingBlock { None => None, }; - let level = heading_start.as_str().trim().into(); + let level = heading_start.as_str().trim(); let (line_nr, _) = heading_start.as_span().start_pos().line_col(); - // unwrap() is ok becuase heading grammar guarantees that heading has non-empty content + let generated_id = match parser::generate_id(heading_content.as_str()) { + Some(id) => id.to_lowercase(), + None => format!("heading-{}-line-{}", level, line_nr), + }; + let id = match attributes { Some(ref attrs) if attrs.get("id").is_some() => attrs.get("id").unwrap().to_string(), - _ => parser::generate_id(heading_content.as_str()) - .unwrap() - .to_lowercase(), + _ => generated_id, }; Ok(HeadingBlock { id, - level, + level: level.into(), content: heading_content.as_str().parse_unimarkup_inlines().collect(), attributes: serde_json::to_string(&attributes.unwrap_or_default()).unwrap(), line_nr, @@ -302,7 +304,7 @@ impl Render for HeadingBlock { fn render_html(&self) -> Result { let mut html = String::default(); - let tag_level = usize::from(self.level).to_string(); + let tag_level = u8::from(self.level).to_string(); html.push_str(", /// Preamble of the Unimarkup file pub preamble: String, /// Kind of the Unimarkup file @@ -44,12 +39,11 @@ impl Default for MetadataKind { impl AsIrLines for Metadata { fn as_ir_lines(&self) -> Vec { let filepath = self.file.to_string_lossy().into_owned(); - let err_filehash_calc = format!("Could not calculate hash for file `{}`!", &filepath); let err_filename_conversion = format!("Given file `{}` is not a valid metadata file!", &filepath); let metadata = MetadataIrLine { - filehash: get_filehash(&self.file).expect(&err_filehash_calc), + filehash: self.contenthash.clone(), filename: self .file .file_name() @@ -78,24 +72,3 @@ impl WriteToIr for Metadata { ir_metadata.write_to_ir(ir_transaction) } } - -/// Calculates the sha3-256 hash of a given file -fn get_filehash(file: &Path) -> Result, MetaDataError> { - let mut hasher = Sha3_256::new(); - let source = fs::read_to_string(file).map_err(|err| { - MetaDataError::General( - (MetaDataErrLogId::FailedReadingFile as LogId) - .set_log( - &format!("Could not read file: '{:?}'", file), - file!(), - line!(), - ) - .add_info(&format!("Cause: {}", err)), - ) - })?; - - hasher.update(source); - - let hash = hasher.finalize(); - Ok(hash.to_vec()) -} diff --git a/core/src/elements/paragraph_block.rs b/core/src/elements/paragraph_block.rs index 8894acd6..be203a16 100644 --- a/core/src/elements/paragraph_block.rs +++ b/core/src/elements/paragraph_block.rs @@ -274,9 +274,9 @@ mod tests { }; let expected_html = format!( - "

This is

the
content of the paragraph

", - id - ); + "

This is

the
content of the paragraph

", + id + ); assert_eq!(expected_html, block.render_html().unwrap()); } diff --git a/core/src/elements/verbatim_block.rs b/core/src/elements/verbatim_block.rs index 24e01876..85a97501 100644 --- a/core/src/elements/verbatim_block.rs +++ b/core/src/elements/verbatim_block.rs @@ -8,6 +8,7 @@ use crate::elements::log_id::EnclosedErrLogId; use crate::elements::types::{UnimarkupBlocks, UnimarkupType}; use crate::frontend::error::{custom_pest_error, FrontendError}; use crate::frontend::parser::{Rule, UmParse}; +use crate::highlight::{self, DEFAULT_THEME, PLAIN_SYNTAX}; use crate::log_id::{LogId, SetLog}; use crate::middleend::{AsIrLines, ContentIrLine}; @@ -190,18 +191,21 @@ impl Render for VerbatimBlock { let attributes = serde_json::from_str::(&self.attributes).unwrap_or_default(); - res.push_str("
");
-        res.push_str(&self.content);
-        res.push_str("
"); + res.push_str(&format!( + "
", + &self.id, &language + )); + res.push_str(&highlight::highlight_html_lines( + &self.content, + &language, + DEFAULT_THEME, + )); + res.push_str("
"); Ok(res) } @@ -240,8 +244,10 @@ mod tests { }; let expected_html = format!( - "
{}
", - id, lang, content + "
{}
", + id, + lang, + &highlight::highlight_html_lines(&content, lang, DEFAULT_THEME) ); assert_eq!(expected_html, block.render_html().unwrap()); @@ -264,7 +270,11 @@ mod tests { line_nr: 0, }; - let expected_html = format!("
{}
", id, content); + let expected_html = format!( + "
{}
", + id, + &highlight::highlight_html_lines(&content, PLAIN_SYNTAX, DEFAULT_THEME) + ); assert_eq!(expected_html, block.render_html().unwrap()); } diff --git a/core/src/error.rs b/core/src/error.rs index a07558fa..cc329a85 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -17,6 +17,8 @@ pub enum CoreError { Element(LogId), /// Config error of the core crate Config(LogId), + /// Security errir of the core crate + Security(LogId), } impl From for LogId { @@ -28,6 +30,7 @@ impl From for LogId { CoreError::Backend(log_id) => log_id, CoreError::Element(log_id) => log_id, CoreError::Config(log_id) => log_id, + CoreError::Security(log_id) => log_id, } } } diff --git a/core/src/frontend/parser.rs b/core/src/frontend/parser.rs index 04a98681..9586ed86 100644 --- a/core/src/frontend/parser.rs +++ b/core/src/frontend/parser.rs @@ -10,6 +10,7 @@ use crate::{ HeadingBlock, Metadata, MetadataKind, ParagraphBlock, VerbatimBlock, }, log_id::{LogId, SetLog}, + security, }; use super::{ @@ -101,6 +102,7 @@ pub fn parse_unimarkup( let metadata = Metadata { file: config.um_file.clone(), + contenthash: security::get_contenthash(um_content), preamble: String::new(), kind: MetadataKind::Root, namespace: ".".to_string(), diff --git a/core/src/highlight.rs b/core/src/highlight.rs new file mode 100644 index 00000000..4cb4fcd0 --- /dev/null +++ b/core/src/highlight.rs @@ -0,0 +1,87 @@ +//! Provides access to syntax and theme sets and syntax highlighting in general + +use lazy_static::lazy_static; +use syntect::easy::HighlightLines; +use syntect::highlighting::{Theme, ThemeSet}; +use syntect::parsing::{SyntaxReference, SyntaxSet}; + +/// The default theme that is used for highlighting +pub const DEFAULT_THEME: &str = "Solarized (dark)"; + +/// Constant to get syntax highlighting for a plain text +pub const PLAIN_SYNTAX: &str = "plain"; + +lazy_static! { + /// Static reference to the syntax set containing all supported syntaxes + pub static ref SYNTAX_SET: SyntaxSet = SyntaxSet::load_defaults_newlines(); + /// Static reference to the theme set containing all supported themes + pub static ref THEME_SET: ThemeSet = ThemeSet::load_defaults(); +} + +/// This function highlights given lines according to set language and theme, +/// returning a standalone HTML string with surrounding `pre` tags. +/// +/// If the language is not found in the available syntax set, the first line is analysed. +/// If this also leads to no match, the content is highlighted as plain text. +/// +/// If the theme is not supported, a fallback theme is used. +/// +/// # Arguments +/// +/// * `content` - Content that is being highlighted +/// * `language` - The language to use for highlighting +/// * `theme` - The theme to use for highlighting +/// +/// Returns HTML with the highlighted content. +pub fn highlight_html_lines(content: &str, language: &str, theme: &str) -> String { + let syntax = get_syntax(content.lines().next().unwrap(), language); + syntect::html::highlighted_html_for_string(content, &SYNTAX_SET, syntax, get_theme(theme)) +} + +/// This function highlights a single line according to set language and theme to HTML. +/// +/// If the language is not found in the available syntax set, the line is analysed. +/// If this also leads to no match, the content is highlighted as plain text. +/// +/// If the theme is not supported, a fallback theme is used. +/// +/// # Arguments +/// +/// * `content` - Content that is being highlighted. Must NOT contain a newline! +/// * `language` - The language to use for highlighting +/// * `theme` - The theme to use for highlighting +/// +/// Returns HTML with the highlighted content. +pub fn highlight_single_html_line(one_line: &str, language: &str, theme: &str) -> String { + let syntax = get_syntax(one_line, language); + let mut h = HighlightLines::new(syntax, get_theme(theme)); + let regions = h.highlight(one_line, &SYNTAX_SET); + syntect::html::styled_line_to_highlighted_html( + ®ions[..], + syntect::html::IncludeBackground::No, + ) +} + +/// Get syntax for given language or try to identify syntax by given line. +/// Falls back to plain text if neither matches a syntax. +fn get_syntax(first_line: &str, language: &str) -> &'static SyntaxReference { + if language.to_lowercase() == PLAIN_SYNTAX { + return SYNTAX_SET.find_syntax_plain_text(); + } + + match SYNTAX_SET.find_syntax_by_name(language) { + Some(syntax) => syntax, + None => match SYNTAX_SET.find_syntax_by_first_line(first_line) { + Some(syntax) => syntax, + None => SYNTAX_SET.find_syntax_plain_text(), + }, + } +} + +/// Get theme or fallback, if theme is not found +fn get_theme(theme: &str) -> &'static Theme { + match THEME_SET.themes.get(theme) { + Some(theme) => theme, + None => &THEME_SET.themes[DEFAULT_THEME], + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 6d812b42..b3ae7f6b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -6,6 +6,9 @@ pub mod config; pub mod elements; pub mod error; pub mod frontend; +pub mod highlight; pub mod log_id; pub mod middleend; +pub mod security; pub mod unimarkup; +pub mod unimarkup_block; diff --git a/core/src/log_id.rs b/core/src/log_id.rs index 27367ce5..97d13db9 100644 --- a/core/src/log_id.rs +++ b/core/src/log_id.rs @@ -19,6 +19,8 @@ pub enum LogSubGrp { Element = 4, /// Config log-id sub group Config = 5, + /// Security log-id sub group + Security = 6, } enum SubSubGrp { diff --git a/core/src/security/error.rs b/core/src/security/error.rs new file mode 100644 index 00000000..fa84f0f1 --- /dev/null +++ b/core/src/security/error.rs @@ -0,0 +1,32 @@ +//! Defines errors for the security section + +use crate::{error::CoreError, log_id::LogId}; + +/// Error enum for the security section +#[derive(Debug)] +pub enum SecurityError { + /// Hashing error of the security section + Hashing(LogId), +} + +impl From for LogId { + fn from(err: SecurityError) -> Self { + match err { + SecurityError::Hashing(log_id) => log_id, + } + } +} + +impl From for SecurityError { + fn from(log_id: LogId) -> Self { + SecurityError::Hashing(log_id) + } +} + +impl From for CoreError { + fn from(err: SecurityError) -> Self { + match err { + SecurityError::Hashing(log_id) => CoreError::Security(log_id), + } + } +} diff --git a/core/src/security/hashing.rs b/core/src/security/hashing.rs new file mode 100644 index 00000000..f872d467 --- /dev/null +++ b/core/src/security/hashing.rs @@ -0,0 +1,39 @@ +//! Provides hashing functionality + +use std::{fs, path::Path}; + +use sha3::{Digest, Sha3_256}; + +use crate::log_id::{LogId, SetLog}; + +use super::{error::SecurityError, log_id::HashingErrLogId}; + +/// Calculates the sha3-256 hash of a given file +pub fn get_filehash(file: &Path) -> Result, SecurityError> { + let mut hasher = Sha3_256::new(); + let source = fs::read_to_string(file).map_err(|err| { + SecurityError::Hashing( + (HashingErrLogId::FailedReadingFile as LogId) + .set_log( + &format!("Could not read file: '{:?}'", file), + file!(), + line!(), + ) + .add_info(&format!("Cause: {}", err)), + ) + })?; + + hasher.update(source); + + let hash = hasher.finalize(); + Ok(hash.to_vec()) +} + +/// Calculates the sha3-256 hash of the given content +pub fn get_contenthash(content: &str) -> Vec { + let mut hasher = Sha3_256::new(); + hasher.update(content); + + let hash = hasher.finalize(); + hash.to_vec() +} diff --git a/core/src/security/log_id.rs b/core/src/security/log_id.rs new file mode 100644 index 00000000..afde861b --- /dev/null +++ b/core/src/security/log_id.rs @@ -0,0 +1,20 @@ +//! Defined log-ids for the security section + +use crate::log_id::{get_log_id, LogKind, LogSubGrp, CORE_GRP}; + +enum LogSubSubGrp { + Hashing = 1, +} + +/// Hashing error log-ids for the security section +#[derive(Debug)] +pub enum HashingErrLogId { + /// Log-id denoting that a file could not be read for hashing + FailedReadingFile = get_log_id( + CORE_GRP, + LogSubGrp::Security as u8, + LogSubSubGrp::Hashing as u8, + LogKind::Error, + 0, + ), +} diff --git a/core/src/security/mod.rs b/core/src/security/mod.rs new file mode 100644 index 00000000..838878f6 --- /dev/null +++ b/core/src/security/mod.rs @@ -0,0 +1,9 @@ +//! Security functionality of [`unimarkup-rs`](crate). +//! +//! i.e. hashing + +pub mod error; +pub mod log_id; + +mod hashing; +pub use hashing::*; diff --git a/core/src/unimarkup.rs b/core/src/unimarkup.rs index b6c57965..60ed1069 100644 --- a/core/src/unimarkup.rs +++ b/core/src/unimarkup.rs @@ -1,7 +1,7 @@ //! Entry module for unimarkup-rs. use crate::backend; -use crate::backend::RenderBlock; +use crate::backend::Render; use crate::config::Config; use crate::config::OutputFormat; use crate::error::CoreError; @@ -9,11 +9,15 @@ use crate::frontend; use crate::log_id::LogId; use crate::log_id::SetLog; use crate::middleend; +use crate::unimarkup_block::UnimarkupBlockKind; /// Struct representing a Unimarkup document that can be rendered to supported output formats. +#[derive(Debug, Clone)] pub struct UnimarkupDocument { - pub(crate) elements: Vec, - pub(crate) config: Config, + /// Elements of a Unimarkup document + pub elements: Vec, + /// Configuration used to create this Unimarkup document + pub config: Config, } impl UnimarkupDocument { @@ -33,7 +37,7 @@ impl UnimarkupDocument { /// HTML representation of the Unimarkup document pub struct Html<'a> { - elements: &'a Vec, + elements: &'a Vec, _metadata: String, } @@ -76,6 +80,13 @@ impl Html<'_> { /// /// Returns a [`CoreError`], if error occurs during compilation. pub fn compile(um_content: &str, mut config: Config) -> Result { + if um_content.is_empty() { + return Ok(UnimarkupDocument { + elements: vec![], + config, + }); + } + let mut connection = middleend::setup_ir_connection()?; middleend::setup_ir(&connection)?; diff --git a/core/src/unimarkup_block.rs b/core/src/unimarkup_block.rs new file mode 100644 index 00000000..657e5f27 --- /dev/null +++ b/core/src/unimarkup_block.rs @@ -0,0 +1,27 @@ +//! Defines the UnimarkupBlockKind a Unimarkup document consists of + +use crate::backend::Render; +use crate::elements::HeadingBlock; +use crate::elements::ParagraphBlock; +use crate::elements::VerbatimBlock; + +/// Enum of supported Unimarkup block elements +#[derive(Debug, Clone)] +pub enum UnimarkupBlockKind { + /// Represents the heading block + Heading(HeadingBlock), + /// Represents the paragraph block + Paragraph(ParagraphBlock), + /// Represents the verbatim block + Verbatim(VerbatimBlock), +} + +impl Render for UnimarkupBlockKind { + fn render_html(&self) -> Result { + match self { + UnimarkupBlockKind::Heading(heading) => heading.render_html(), + UnimarkupBlockKind::Paragraph(paragraph) => paragraph.render_html(), + UnimarkupBlockKind::Verbatim(verbatim) => verbatim.render_html(), + } + } +} diff --git a/core/tests/elements/metadata.rs b/core/tests/elements/metadata.rs index 974e0994..de1fe388 100644 --- a/core/tests/elements/metadata.rs +++ b/core/tests/elements/metadata.rs @@ -7,6 +7,7 @@ use unimarkup_core::{ elements::{Metadata, MetadataKind}, frontend, middleend, middleend::MetadataIrLine, + security, }; #[test] @@ -24,6 +25,7 @@ fn test__ir_root__metadata_in_ir() { let expected_metadata = Metadata { file: Path::new(testfile).to_path_buf(), + contenthash: security::get_filehash(Path::new(testfile)).unwrap(), preamble: String::new(), kind: MetadataKind::Root, namespace: ".".to_string(), @@ -40,3 +42,23 @@ fn test__ir_root__metadata_in_ir() { Err(_) => panic!("Failed creating database connection"), }; } + +#[test] +fn test__metadata__create_from_memory() { + let testfile = "from_memory"; + let content = "some **unimarkup content**"; + + let metadata = Metadata { + file: Path::new(testfile).to_path_buf(), + contenthash: security::get_contenthash(content), + preamble: String::new(), + kind: MetadataKind::Root, + namespace: ".".to_string(), + }; + + assert_eq!( + metadata.file.to_str().unwrap(), + testfile, + "Creating metadata from memory content failed" + ); +} diff --git a/core/tests/unimarkup.rs b/core/tests/unimarkup.rs new file mode 100644 index 00000000..e6ab3f95 --- /dev/null +++ b/core/tests/unimarkup.rs @@ -0,0 +1,17 @@ +#![allow(non_snake_case)] + +use clap::Parser; +use unimarkup_core::config::Config; + +#[test] +fn test__compile__empty_content() { + let cfg: Config = Config::parse_from(vec![ + "unimarkup", + "--output-formats=html", + "tests/test_files/all_syntax.um", + ]); + + let rendered_result = unimarkup_core::unimarkup::compile("", cfg); + + assert!(rendered_result.unwrap().elements.is_empty()); +} diff --git a/inline/src/error.rs b/inline/src/error.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/inline/src/log_id.rs b/inline/src/log_id.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/system_tests/tests/cli.rs b/system_tests/tests/cli.rs index 69006b02..18800de4 100644 --- a/system_tests/tests/cli.rs +++ b/system_tests/tests/cli.rs @@ -183,11 +183,7 @@ fn test__config_parse__enable_elements_option_set() { let um_filename = "file.um"; let elements = vec![UnimarkupType::VerbatimBlock, UnimarkupType::DefinitionList]; - let options = format!( - "--enable-elements={},{}", - elements[0].to_string(), - elements[1].to_string() - ); + let options = format!("--enable-elements={},{}", elements[0], elements[1]); let args = get_args(&options, um_filename); let cfg: Config = Config::parse_from(args); @@ -208,11 +204,7 @@ fn test__config_parse__disable_elements_option_set() { let um_filename = "file.um"; let elements = vec![UnimarkupType::VerbatimBlock, UnimarkupType::DefinitionList]; - let options = format!( - "--disable-elements={},{}", - elements[0].to_string(), - elements[1].to_string() - ); + let options = format!("--disable-elements={},{}", elements[0], elements[1]); let args = get_args(&options, um_filename); let cfg: Config = Config::parse_from(args); diff --git a/system_tests/tests/logging/cli_logs.rs b/system_tests/tests/logging/cli_logs.rs index cea06cb1..9359c4fb 100644 --- a/system_tests/tests/logging/cli_logs.rs +++ b/system_tests/tests/logging/cli_logs.rs @@ -3,32 +3,56 @@ use std::{ process::{Command, Stdio}, }; +const TEST_FILE: &str = "attrs.um"; + #[test] fn test__main_log_trace__attributes_file() { - let path = PathBuf::from("tests/test_files/attrs.um"); - let path = path.canonicalize().unwrap(); + let path = get_path(); let cli_proc = Command::new("cargo") - .current_dir("./..") + .current_dir(get_proc_path()) .stderr(Stdio::piped()) .args(["run", "--", "--formats=html", &path.to_string_lossy()]) .spawn() - .expect("Failed to spawn cargo run") - .wait_with_output() - .expect("Failed to execute cargo run"); - - let logs = String::from_utf8_lossy(&cli_proc.stderr); + .expect("Failed to spawn 'cargo run'"); - let out_path = path.with_extension("html"); + let output = cli_proc + .wait_with_output() + .expect("Failed to execute 'cargo run'"); + let logs = String::from_utf8_lossy(&output.stderr); - assert!(logs.contains(&format!( - "INFO : 536936448: Writing to file: \"{}\"", - out_path.to_string_lossy() - ))); + assert!(logs.contains("INFO : 536936448: Writing to file: \"")); + assert!(logs.contains(&format!("{}\"", TEST_FILE.replace(".um", ".html")))); assert!(logs.contains("TRACE: 536936448: Occured in file")); - assert!(logs.contains(&format!( - "INFO : 536936449: Finished compiling: \"{}\"", - path.to_string_lossy() - ))); + assert!(logs.contains("INFO : 536936449: Finished compiling: \"")); + assert!(logs.contains(&format!("{}\"", TEST_FILE))); assert!(logs.contains("TRACE: 536936449: Occured in file")); } + +// Note: Functions below needed to get the test running in 'run' and 'debug' mode + +fn get_path() -> PathBuf { + let filePath = PathBuf::from(file!()); + let fileRoot = filePath.parent().unwrap(); + let path = fileRoot.join("../test_files/".to_owned() + TEST_FILE); + match path.canonicalize() { + Ok(path) => path, + Err(_) => { + let path = PathBuf::from("tests/test_files/".to_owned() + TEST_FILE); + path.canonicalize().unwrap() + } + } +} + +fn get_proc_path() -> PathBuf { + let filePath = PathBuf::from(file!()); + let fileRoot = filePath.parent().unwrap(); + let repoPath = fileRoot.join("../../../."); + if fileRoot.canonicalize().is_ok() { + if let Ok(path) = repoPath.canonicalize() { + return path; + } + } + + PathBuf::from("./..") +}