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("./..")
+}