Packages
mdex
0.2.0
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.5
retired
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Fast and extensible Markdown for Elixir
Current section
Files
Jump to
Current section
Files
native/comrak_nif/src/decoder.rs
use crate::types::{
atoms::{
attr_key_not_string, empty, invalid_structure, missing_attr_field, missing_node_field,
node_name_not_string, unknown_attr_value, unknown_node_name,
},
nodes::{AttrValue, ExNode, NodeName},
};
use comrak::{
nodes::{
AstNode, ListDelimType, ListType, NodeCode, NodeCodeBlock, NodeDescriptionItem,
NodeFootnoteDefinition, NodeFootnoteReference, NodeHeading, NodeHtmlBlock, NodeLink,
NodeList, NodeMath, NodeMultilineBlockQuote, NodeShortCode, NodeTable, NodeValue,
NodeWikiLink, TableAlignment,
},
Arena,
};
use rustler::{Binary, Decoder, Encoder, Env, Error, NifResult, Term};
use std::str::FromStr;
#[derive(Debug)]
pub enum DecodeError {
InvalidStructure {
found: String,
},
Empty {
found: String,
},
MissingNodeField {
found: String,
},
MissingAttrField {
found: String,
node: String,
},
NodeNameNotString {
found: String,
node: String,
kind: String,
},
UnknownNodeName {
found: String,
node: String,
},
AttrKeyNotString {
found: String,
node: String,
attr: String,
kind: String,
},
UnknownAttrValue {
found: String,
node: String,
attr: String,
kind: String,
},
}
impl Encoder for DecodeError {
fn encode<'a>(&self, env: Env<'a>) -> Term<'a> {
match self {
DecodeError::InvalidStructure { found } => {
let reason = invalid_structure().encode(env);
(reason, found).encode(env)
}
DecodeError::Empty { found } => {
let reason = empty().encode(env);
(reason, found).encode(env)
}
DecodeError::MissingNodeField { found } => {
let reason = missing_node_field().encode(env);
(reason, found).encode(env)
}
DecodeError::MissingAttrField { found, node } => {
let reason = missing_attr_field().encode(env);
(reason, found, node).encode(env)
}
DecodeError::NodeNameNotString { found, node, kind } => {
let reason = node_name_not_string().encode(env);
(reason, found, node, kind).encode(env)
}
DecodeError::UnknownNodeName { found, node } => {
let reason = unknown_node_name().encode(env);
(reason, found, node).encode(env)
}
DecodeError::AttrKeyNotString {
found,
node,
attr,
kind,
} => {
let reason = attr_key_not_string().encode(env);
(reason, found, node, attr, kind).encode(env)
}
DecodeError::UnknownAttrValue {
found,
node,
attr,
kind,
} => {
let reason = unknown_attr_value().encode(env);
(reason, found, node, attr, kind).encode(env)
}
}
}
}
impl From<DecodeError> for Error {
fn from(err: DecodeError) -> Error {
Error::Term(Box::new(err))
}
}
impl<'a> Decoder<'a> for ExNode<'a> {
fn decode(term: Term<'a>) -> NifResult<Self> {
let list: Vec<Term> = term.decode().map_err(|_| DecodeError::InvalidStructure {
found: format!("{:?}", term),
})?;
if list.len() != 1 {
return Err(DecodeError::Empty {
found: format!("{:?}", term),
}
.into());
}
decode_node(&list[0])
}
}
fn decode_node<'a>(term: &Term<'a>) -> NifResult<ExNode<'a>> {
if let Ok(text) = term.decode::<String>() {
return Ok(ExNode::Text(text));
}
let node: (Term, Term, Term) = term.decode().map_err(|_| DecodeError::MissingNodeField {
found: format!("{:?}", term),
})?;
let name = node
.0
.decode::<Binary>()
.map_err(|_| DecodeError::NodeNameNotString {
found: format!("{:?}", node.0),
node: format!("{:?}", node),
kind: format!("{:?}", rustler::Term::get_type(node.0)),
})?;
let name =
std::str::from_utf8(name.as_slice()).map_err(|_| DecodeError::NodeNameNotString {
found: format!("{:?}", node.0),
node: format!("{:?}", node),
kind: format!("{:?}", rustler::Term::get_type(node.0)),
})?;
let name = NodeName::from_str(name).map_err(|_| DecodeError::UnknownNodeName {
found: name.to_string(),
node: format!("{:?}", node),
})?;
let attrs: Vec<(Term, Term)> = node.1.decode().map_err(|_| DecodeError::MissingAttrField {
found: format!("{:?}", node.1),
node: format!("{:?}", node),
})?;
let attrs = attrs
.into_iter()
.map(|(key, value)| {
let key_binary: Binary = key.decode().map_err(|_| DecodeError::AttrKeyNotString {
found: format!("{:?}", key),
node: format!("{:?}", node),
attr: format!("{:?}", (key, value)),
kind: format!("{:?}", rustler::Term::get_type(key)),
})?;
let key = std::str::from_utf8(key_binary.as_slice()).map_err(|_| {
DecodeError::AttrKeyNotString {
found: format!("{:?}", key),
node: format!("{:?}", node),
attr: format!("{:?}", (key, value)),
kind: format!("{:?}", rustler::Term::get_type(key)),
}
})?;
let attr_value: AttrValue =
value.decode().map_err(|_| DecodeError::UnknownAttrValue {
found: format!("{:?}", value),
node: format!("{:?}", node),
attr: format!("{:?}", (key, value)),
kind: format!("{:?}", rustler::Term::get_type(value)),
})?;
Ok((key, attr_value))
})
.collect::<Result<Vec<_>, Error>>()?;
let children: Vec<Term> = node.2.decode().map_err(|_| DecodeError::InvalidStructure {
found: format!("E7 {:?}", node.1),
})?;
let children = children
.into_iter()
.map(|child_term| decode_node(&child_term))
.collect::<Result<Vec<_>, Error>>()?;
Ok(ExNode::Element {
name,
attrs,
children,
})
}
pub fn ex_node_to_comrak_ast<'a>(
arena: &'a Arena<AstNode<'a>>,
ex_node: &ExNode,
) -> &'a AstNode<'a> {
match ex_node {
ExNode::Element {
name,
attrs,
children,
} => {
// println!("name: {:?}", name);
// println!("attrs: {:?}", attrs);
let node_value = match name {
NodeName::Document => AstNode::from(NodeValue::Document),
NodeName::FrontMatter => {
let mut front_matter = NodeValue::FrontMatter("".to_string());
for (key, value) in attrs {
match (key, value) {
(&"content", AttrValue::Text(ref value)) => {
front_matter = NodeValue::FrontMatter(value.clone())
}
(attr_name, attr_value) => {
unknown_attr("front_matter", attr_name, attr_value)
}
}
}
AstNode::from(front_matter)
}
NodeName::BlockQuote => AstNode::from(NodeValue::BlockQuote),
NodeName::List => {
AstNode::from(NodeValue::List(attrs_to_node_list("list", attrs.to_vec())))
}
NodeName::Item => {
AstNode::from(NodeValue::Item(attrs_to_node_list("item", attrs.to_vec())))
}
NodeName::DescriptionList => AstNode::from(NodeValue::DescriptionList),
NodeName::DescriptionItem => {
let mut node = NodeDescriptionItem::default();
for (key, value) in attrs {
match (key, value) {
(&"marker_offset", AttrValue::Usize(ref value)) => {
node.marker_offset = *value
}
(&"marker_offset", AttrValue::U8(ref value)) => {
node.marker_offset = *value as usize
}
(&"padding", AttrValue::Usize(ref value)) => node.padding = *value,
(&"padding", AttrValue::U8(ref value)) => {
node.padding = *value as usize
}
(attr_name, attr_value) => {
unknown_attr("description_item", attr_name, attr_value)
}
}
}
AstNode::from(NodeValue::DescriptionItem(node))
}
NodeName::DescriptionTerm => AstNode::from(NodeValue::DescriptionTerm),
NodeName::DescriptionDetails => AstNode::from(NodeValue::DescriptionDetails),
NodeName::CodeBlock => {
let mut node = NodeCodeBlock::default();
for (key, value) in attrs {
match (key, value) {
(&"fenced", AttrValue::Bool(ref value)) => node.fenced = *value,
(&"fence_char", AttrValue::Text(ref value)) => {
node.fence_char = string_to_char(value.clone())
}
(&"fence_length", AttrValue::Usize(ref value)) => {
node.fence_length = *value
}
(&"fence_length", AttrValue::U8(ref value)) => {
node.fence_length = *value as usize
}
(&"fence_offset", AttrValue::Usize(ref value)) => {
node.fence_offset = *value
}
(&"fence_offset", AttrValue::U8(ref value)) => {
node.fence_offset = *value as usize
}
(&"info", AttrValue::Text(ref value)) => node.info = value.clone(),
(&"literal", AttrValue::Text(ref value)) => {
node.literal = value.clone()
}
(attr_name, attr_value) => {
unknown_attr("code_block", attr_name, attr_value)
}
}
}
AstNode::from(NodeValue::CodeBlock(node))
}
NodeName::HtmlBlock => {
let mut node = NodeHtmlBlock::default();
for (key, value) in attrs {
match (key, value) {
(&"block_type", AttrValue::U8(ref value)) => node.block_type = *value,
(&"literal", AttrValue::Text(ref value)) => {
node.literal = value.clone()
}
(attr_name, att_value) => {
unknown_attr("html_block", attr_name, att_value)
}
}
}
AstNode::from(NodeValue::HtmlBlock(node))
}
NodeName::Paragraph => AstNode::from(NodeValue::Paragraph),
NodeName::Heading => {
let mut node = NodeHeading {
level: 1,
setext: false,
};
for (key, value) in attrs {
match (key, value) {
(&"level", AttrValue::U8(ref value)) => node.level = *value,
(&"setext", AttrValue::Bool(ref value)) => node.setext = *value,
(attr_name, attr_value) => {
unknown_attr("heading", attr_name, attr_value)
}
}
}
AstNode::from(NodeValue::Heading(node))
}
NodeName::ThematicBreak => AstNode::from(NodeValue::ThematicBreak),
NodeName::FootnoteDefinition => {
let mut node = NodeFootnoteDefinition::default();
for (key, value) in attrs {
match (key, value) {
(&"name", AttrValue::Text(ref value)) => node.name = value.clone(),
(&"total_references", AttrValue::U32(ref value)) => {
node.total_references = *value
}
(&"total_references", AttrValue::U8(ref value)) => {
node.total_references = *value as u32
}
(attr_name, attr_value) => {
unknown_attr("footnote_definition", attr_name, attr_value)
}
}
}
AstNode::from(NodeValue::FootnoteDefinition(node))
}
NodeName::FootnoteReference => {
let mut node = NodeFootnoteReference::default();
for (key, value) in attrs {
match (key, value) {
(&"name", AttrValue::Text(ref value)) => node.name = value.clone(),
(&"ref_num", AttrValue::U32(ref value)) => node.ref_num = *value,
(&"ref_num", AttrValue::U8(ref value)) => node.ref_num = *value as u32,
(&"ix", AttrValue::U32(ref value)) => node.ix = *value,
(&"ix", AttrValue::U8(ref value)) => node.ix = *value as u32,
(attr_name, attr_value) => {
unknown_attr("footnote_reference", attr_name, attr_value)
}
}
}
AstNode::from(NodeValue::FootnoteReference(node))
}
NodeName::Table => {
let mut node = NodeTable::default();
for (key, value) in attrs {
match (key, value) {
(&"alignments", AttrValue::List(ref value)) => {
let alignments = value
.iter()
.map(|ta| match ta.as_str() {
"none" => TableAlignment::None,
"left" => TableAlignment::Left,
"center" => TableAlignment::Center,
"right" => TableAlignment::Right,
_ => TableAlignment::None,
})
.collect::<Vec<TableAlignment>>();
node.alignments = alignments
}
(&"num_columns", AttrValue::Usize(ref value)) => {
node.num_columns = *value
}
(&"num_columns", AttrValue::U8(ref value)) => {
node.num_columns = *value as usize
}
(&"num_rows", AttrValue::Usize(ref value)) => node.num_rows = *value,
(&"num_rows", AttrValue::U8(ref value)) => {
node.num_rows = *value as usize
}
(&"num_nonempty_cells", AttrValue::Usize(ref value)) => {
node.num_nonempty_cells = *value
}
(&"num_nonempty_cells", AttrValue::U8(ref value)) => {
node.num_nonempty_cells = *value as usize
}
(attr_name, attr_value) => unknown_attr("table", attr_name, attr_value),
}
}
AstNode::from(NodeValue::Table(node))
}
NodeName::TableRow => {
let mut table_row = NodeValue::TableRow(false);
for (key, value) in attrs {
match (key, value) {
(&"header", AttrValue::Bool(ref value)) => {
table_row = NodeValue::TableRow(*value)
}
(attr_name, attr_value) => {
unknown_attr("table_row", attr_name, attr_value)
}
}
}
AstNode::from(table_row)
}
NodeName::TableCell => AstNode::from(NodeValue::TableCell),
NodeName::TaskItem => AstNode::from(NodeValue::TaskItem(attrs_to_task_item(
"task_item",
attrs.to_vec(),
))),
NodeName::SoftBreak => AstNode::from(NodeValue::SoftBreak),
NodeName::LineBreak => AstNode::from(NodeValue::LineBreak),
NodeName::Code => {
let mut node = NodeCode {
num_backticks: 0,
literal: "".to_string(),
};
for (key, value) in attrs {
match (key, value) {
(&"num_backticks", AttrValue::Usize(ref value)) => {
node.num_backticks = *value
}
(&"num_backticks", AttrValue::U8(ref value)) => {
node.num_backticks = *value as usize
}
(&"literal", AttrValue::Text(ref value)) => {
node.literal = value.clone()
}
(attr_name, attr_value) => unknown_attr("code", attr_name, attr_value),
}
}
AstNode::from(NodeValue::Code(node))
}
NodeName::HtmlInline => {
let mut html_inline = NodeValue::HtmlInline("".to_string());
for (key, value) in attrs {
match (key, value) {
(&"raw_html", AttrValue::Text(ref value)) => {
html_inline = NodeValue::HtmlInline(value.clone())
}
(attr_name, attr_value) => {
unknown_attr("html_inline", attr_name, attr_value)
}
}
}
AstNode::from(html_inline)
}
NodeName::Emph => AstNode::from(NodeValue::Emph),
NodeName::Strong => AstNode::from(NodeValue::Strong),
NodeName::Strikethrough => AstNode::from(NodeValue::Strikethrough),
NodeName::Superscript => AstNode::from(NodeValue::Superscript),
NodeName::Link => {
AstNode::from(NodeValue::Link(attrs_to_node_link("link", attrs.to_vec())))
}
NodeName::Image => AstNode::from(NodeValue::Image(attrs_to_node_link(
"image",
attrs.to_vec(),
))),
NodeName::ShortCode => {
let mut node = NodeShortCode {
code: "".to_string(),
emoji: "".to_string(),
};
for (key, value) in attrs {
match (key, value) {
(&"code", AttrValue::Text(ref value)) => node.code = value.clone(),
(&"emoji", AttrValue::Text(ref value)) => node.emoji = value.clone(),
(attr_name, attr_value) => {
unknown_attr("short_code", attr_name, attr_value)
}
}
}
AstNode::from(NodeValue::ShortCode(node))
}
NodeName::Math => {
let mut node = NodeMath {
dollar_math: false,
display_math: false,
literal: "".to_string(),
};
for (key, value) in attrs {
match (key, value) {
(&"dollar_math", AttrValue::Bool(ref value)) => {
node.dollar_math = *value
}
(&"display_math", AttrValue::Bool(ref value)) => {
node.display_math = *value
}
(&"literal", AttrValue::Text(ref value)) => {
node.literal = value.clone()
}
(attr_name, attr_value) => unknown_attr("math", attr_name, attr_value),
}
}
AstNode::from(NodeValue::Math(node))
}
NodeName::MultilineBlockQuote => {
let mut node = NodeMultilineBlockQuote {
fence_length: 0,
fence_offset: 0,
};
for (key, value) in attrs {
match (key, value) {
(&"fence_length", AttrValue::Usize(ref value)) => {
node.fence_length = *value
}
(&"fence_length", AttrValue::U8(ref value)) => {
node.fence_length = *value as usize
}
(&"fence_offset", AttrValue::Usize(ref value)) => {
node.fence_offset = *value
}
(&"fence_offset", AttrValue::U8(ref value)) => {
node.fence_offset = *value as usize
}
(attr_name, attr_value) => {
unknown_attr("multiline_block_quote", attr_name, attr_value)
}
}
}
AstNode::from(NodeValue::MultilineBlockQuote(node))
}
NodeName::Escaped => AstNode::from(NodeValue::Escaped),
NodeName::WikiLink => {
let mut node = NodeWikiLink {
url: "".to_string(),
};
for (key, value) in attrs {
match (key, value) {
(&"url", AttrValue::Text(ref value)) => node.url = value.clone(),
(attr_name, attr_value) => {
unknown_attr("wiki_link", attr_name, attr_value)
}
}
}
AstNode::from(NodeValue::WikiLink(node))
}
NodeName::Underline => AstNode::from(NodeValue::Underline),
NodeName::SpoileredText => AstNode::from(NodeValue::SpoileredText),
NodeName::EscapedTag => {
let mut escaped_tag = NodeValue::EscapedTag("".to_string());
for (key, value) in attrs {
match (key, value) {
(&"tag", AttrValue::Text(ref value)) => {
escaped_tag = NodeValue::EscapedTag(value.clone())
}
(attr_name, attr_value) => {
unknown_attr("escaped_tag", attr_name, attr_value)
}
}
}
AstNode::from(escaped_tag)
}
};
let node = arena.alloc(node_value);
for child in children {
let child_node = ex_node_to_comrak_ast(arena, child);
node.append(child_node);
}
node
}
ExNode::Text(content) => {
let text = AstNode::from(NodeValue::Text(content.clone()));
arena.alloc(text)
}
}
}
fn string_to_char(s: String) -> u8 {
if s.is_empty() {
return 0;
}
s.chars().next().unwrap_or(' ') as u8
}
fn unknown_attr(node_name: &str, attr_name: &str, attr_value: &AttrValue) {
log::warn!(
"unknown attribute {} on node {} with value {:?}",
attr_name,
node_name,
attr_value
);
}
fn attrs_to_node_list(node_name: &str, attrs: Vec<(&str, AttrValue)>) -> NodeList {
let mut list = NodeList::default();
let to_list_type = |value: &str| -> ListType {
match value {
"bullet" => ListType::Bullet,
"ordered" => ListType::Ordered,
_ => ListType::default(),
}
};
let to_delim_type = |value: &str| -> ListDelimType {
match value {
"period" => ListDelimType::Period,
"paren" => ListDelimType::Paren,
_ => ListDelimType::default(),
}
};
for (key, value) in attrs {
match (key, value) {
("list_type", AttrValue::Text(ref value)) => {
list.list_type = to_list_type(value.as_str())
}
("marker_offset", AttrValue::Usize(ref value)) => list.marker_offset = *value,
("marker_offset", AttrValue::U8(ref value)) => list.marker_offset = *value as usize,
("padding", AttrValue::Usize(ref value)) => list.padding = *value,
("padding", AttrValue::U8(ref value)) => list.padding = *value as usize,
("start", AttrValue::Usize(ref value)) => list.start = *value,
("start", AttrValue::U8(ref value)) => list.start = *value as usize,
("delimiter", AttrValue::Text(ref value)) => {
list.delimiter = to_delim_type(value.as_str())
}
("bullet_char", AttrValue::Text(ref value)) => {
list.bullet_char = string_to_char(value.clone())
}
("tight", AttrValue::Bool(ref value)) => list.tight = *value,
(attr_name, attr_value) => unknown_attr(node_name, attr_name, &attr_value),
}
}
list
}
fn attrs_to_task_item(node_name: &str, attrs: Vec<(&str, AttrValue)>) -> Option<char> {
let mut symbol = None;
for (key, value) in attrs {
match (key, value) {
("checked", AttrValue::Bool(false)) => return None,
("symbol", AttrValue::Text(ref value)) => symbol = value.chars().next(),
(attr_name, attr_value) => unknown_attr(node_name, attr_name, &attr_value),
}
}
symbol
}
fn attrs_to_node_link(node_name: &str, attrs: Vec<(&str, AttrValue)>) -> NodeLink {
let mut link = NodeLink {
url: "".to_string(),
title: "".to_string(),
};
for (key, value) in attrs {
match (key, value) {
("url", AttrValue::Text(ref value)) => link.url = value.clone(),
("title", AttrValue::Text(ref value)) => link.title = value.clone(),
(attr_name, attr_value) => unknown_attr(node_name, attr_name, &attr_value),
}
}
link
}