diff --git a/src/convert.rs b/src/convert.rs index 4d4fa6b..fe39804 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,3 +1 @@ pub mod npf; - -fn test() {} diff --git a/src/convert/npf.rs b/src/convert/npf.rs index 34a1f51..b95b83e 100644 --- a/src/convert/npf.rs +++ b/src/convert/npf.rs @@ -1,5 +1,143 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + cell::RefCell, +}; + +use comrak::{ + arena_tree::Node, + nodes::{Ast, AstNode, NodeValue}, +}; + pub mod attributions; pub mod content_blocks; pub mod layout_blocks; pub mod objects; pub mod text_formatting; + +use content_blocks::{BlockText, BlockValue}; +use text_formatting::{FormatTypeBold, FormatTypeItalic, FormatValue}; + +#[derive(Debug)] +pub enum NPFConvertError { + TODO, +} + +fn extract_text(contents: &Vec) -> String { + contents + .iter() + .fold(String::new(), |mut a, c| { + if let BlockValue::Text(block) = c { + a.push_str(&format!(" {}", block.text)); + } + a + }) + .trim() + .to_string() +} + +impl<'a> TryFrom<&'a Node<'a, RefCell>> for objects::Post { + type Error = NPFConvertError; + fn try_from(value: &'a Node<'a, RefCell>) -> Result { + let mut post = Self::new(0); + + let nodes = value.children().into_iter(); + let r: Result, NPFConvertError> = nodes + .map(|n| match &n.data.borrow().value { + NodeValue::Paragraph => { + let mut paragraph_contents = Self::try_from(n)?.content; + post.content.append(&mut paragraph_contents); + Ok(()) + } + NodeValue::Text(t) => { + let block_text = BlockText::from(String::from(t.clone())); + post.content.push(BlockValue::Text(block_text)); + Ok(()) + } + NodeValue::Strong => { + let mut content = Self::try_from(n)?.content; + let mut res = content.iter_mut().fold( + BlockText::new(&String::new()), + |mut acc, c| match c { + BlockValue::Text(t) => { + let text = &t.text.trim(); + if let Some(ref mut f) = &mut t.formatting { + let offset = acc.text.chars().count() as u64; + f.iter_mut().for_each(|f| { + f.offset(offset); + }); + if let Some(ref mut af) = acc.formatting { + af.append(f); + } else { + acc.formatting = Some(f.to_vec()); + } + } + acc.text.push_str(&format!("{} ", text)); + acc + } + _ => acc, + }, + ); + res.text = res.text.trim().to_string(); + let format = FormatValue::Bold(FormatTypeBold::from(&res.text)); + if let Some(ref mut f) = res.formatting { + f.push(format); + } else { + res.formatting = Some(vec![format]); + } + post.content.push(BlockValue::Text(res)); + Ok(()) + } + NodeValue::Emph => { + let mut content = Self::try_from(n)?.content; + let mut res = content.iter_mut().fold( + BlockText::new(&String::new()), + |mut acc, c| match c { + BlockValue::Text(t) => { + let text = &t.text.trim(); + if let Some(ref mut f) = &mut t.formatting { + let offset = acc.text.chars().count() as u64; + f.iter_mut().for_each(|f| { + f.offset(offset); + }); + if let Some(ref mut af) = acc.formatting { + af.append(f); + } else { + acc.formatting = Some(f.to_vec()); + } + } + acc.text.push_str(&format!("{} ", text)); + acc + } + _ => acc, + }, + ); + res.text = res.text.trim().to_string(); + let format = FormatValue::Italic(FormatTypeItalic::from(&res.text)); + if let Some(ref mut f) = res.formatting { + f.push(format); + } else { + res.formatting = Some(vec![format]); + } + // println!("italic {:#?}", res); + post.content.push(BlockValue::Text(res)); + Ok(()) + } + _ => Ok(()), + }) + .collect(); + if let Err(e) = r { + Err(e) + } else { + // println!("{:#?}", post); + Ok(post) + } + } +} + +pub fn from<'a>(node: &'a Node<'a, RefCell>) -> Result { + objects::Post::try_from(node) +} + +/* +Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis +*/ diff --git a/src/convert/npf/attributions.rs b/src/convert/npf/attributions.rs index 71aeea9..70d1ac7 100644 --- a/src/convert/npf/attributions.rs +++ b/src/convert/npf/attributions.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use super::objects; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] #[serde(untagged)] pub enum AttributionValue { Post(AttributionPost), @@ -12,7 +12,7 @@ pub enum AttributionValue { Blog(AttributionBlog), } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct AttributionPost { r#type: String, pub url: url::Url, @@ -41,7 +41,7 @@ impl AttributionPost { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct AttributionLink { r#type: String, pub url: url::Url, @@ -66,7 +66,7 @@ impl From for AttributionLink { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct AttributionBlog { r#type: String, pub url: Option, @@ -103,7 +103,7 @@ impl TryFrom for AttributionBlog { } */ -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct AttributionApp { r#type: String, pub url: url::Url, diff --git a/src/convert/npf/content_blocks.rs b/src/convert/npf/content_blocks.rs index 5289c37..46e2a37 100644 --- a/src/convert/npf/content_blocks.rs +++ b/src/convert/npf/content_blocks.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use super::{attributions, objects}; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] #[serde(untagged)] pub enum BlockValue { Text(BlockText), @@ -14,7 +14,7 @@ pub enum BlockValue { Video(BlockVideo), } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] #[serde(rename_all = "snake_case")] pub enum BlockTextSubtype { Heading1, @@ -28,7 +28,7 @@ pub enum BlockTextSubtype { } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct BlockText { r#type: String, pub subtype: Option, @@ -68,7 +68,7 @@ impl From<&str> for BlockText { } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct BlockImage { r#type: String, pub media: Vec, @@ -111,7 +111,7 @@ impl From for BlockImage { } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct BlockLink { r#type: String, pub url: url::Url, @@ -149,7 +149,7 @@ impl From for BlockLink { } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct BlockAudio { r#type: String, pub media: Option, @@ -210,7 +210,7 @@ impl From for BlockAudio { } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct BlockVideo { r#type: String, pub url: Option, diff --git a/src/convert/npf/layout_blocks.rs b/src/convert/npf/layout_blocks.rs index fdad1fc..92b6b9a 100644 --- a/src/convert/npf/layout_blocks.rs +++ b/src/convert/npf/layout_blocks.rs @@ -1,13 +1,13 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] #[serde(untagged)] pub enum BlockValue { Rows(BlockRows), Ask(BlockAsk), } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct BlockRows { r#type: String, pub display: Vec, @@ -42,7 +42,7 @@ impl From for BlockRows { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct DisplayBlocks { pub blocks: Vec, pub mode: Option, @@ -75,7 +75,7 @@ impl From for DisplayBlocks { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct BlockAsk { r#type: String, blocks: Vec, diff --git a/src/convert/npf/objects.rs b/src/convert/npf/objects.rs index c2c16e4..bb59bce 100644 --- a/src/convert/npf/objects.rs +++ b/src/convert/npf/objects.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct BlogInfo { pub uuid: String, pub name: Option, @@ -48,14 +48,14 @@ impl From for BlogInfo { } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct ReblogTrailPost { pub id: String, pub timestamp: Option, pub is_commercial: Option, } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct ReblogTrail { pub post: Option, pub blog: Option, @@ -65,7 +65,7 @@ pub struct ReblogTrail { } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct Avatar { pub width: u64, pub height: u64, @@ -74,7 +74,7 @@ pub struct Avatar { } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct Post { object_type: String, pub id: u64, @@ -188,7 +188,7 @@ impl From for Post { } #[serde_with::skip_serializing_none] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct Media { pub r#type: Option, pub url: url::Url, @@ -223,7 +223,7 @@ impl From for Media { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct EmbedIframe { pub url: url::Url, pub width: u64, diff --git a/src/convert/npf/text_formatting.rs b/src/convert/npf/text_formatting.rs index fba9d33..6e95212 100644 --- a/src/convert/npf/text_formatting.rs +++ b/src/convert/npf/text_formatting.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use super::objects; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] #[serde(untagged)] pub enum FormatValue { Bold(FormatTypeBold), @@ -15,12 +15,25 @@ pub enum FormatValue { Mention(FormatTypeMention), Color(FormatTypeColor), } +impl FormatValue { + pub fn offset(&mut self, offset: u64) { + match self { + FormatValue::Bold(ref mut f) => f.offset(offset), + FormatValue::Italic(ref mut f) => f.offset(offset), + FormatValue::Link(ref mut f) => f.offset(offset), + FormatValue::Small(ref mut f) => f.offset(offset), + FormatValue::Color(ref mut f) => f.offset(offset), + FormatValue::Mention(ref mut f) => f.offset(offset), + FormatValue::StrikeThrough(ref mut f) => f.offset(offset), + } + } +} trait FormatType: From> + From { fn default() -> Self; } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct FormatTypeLink { r#type: String, pub start: u64, @@ -46,7 +59,7 @@ impl FormatType for FormatTypeLink { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct FormatTypeMention { r#type: String, pub start: u64, @@ -72,7 +85,7 @@ impl FormatType for FormatTypeMention { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct FormatTypeColor { r#type: String, pub start: u64, @@ -120,6 +133,15 @@ macro_rules! ImplInlines { } } })* + $(impl From<&String> for $t { + fn from(value: &String) -> Self { + Self { + start: 0, + end: value.chars().count() as u64, + ..Self::default() + } + } + })* $(impl From<&str> for $t { fn from(value: &str) -> Self { Self { @@ -129,11 +151,17 @@ macro_rules! ImplInlines { } } })* + $(impl $t { + pub fn offset(&mut self, offset: u64) { + self.start += offset; + self.end += offset; + } + })* }; // Defines the struct and implements Default trait if the token is an // identifier and a literal (for $($t:ident $s:literal),+) => { - $(#[derive(Debug, Deserialize, Serialize)] + $(#[derive(Debug, Deserialize, Serialize, Clone)] pub struct $t { r#type: String, pub start: u64, diff --git a/src/main.rs b/src/main.rs index d7601c9..feb61a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use clap::{ArgAction, Parser, Subcommand}; use clio::Input; +use mdparser::convert; use mdparser::frontmatter::Frontmatter; use mdparser::links; use mdparser::utils; @@ -72,6 +73,9 @@ fn main() { let arena = comrak::Arena::new(); let ast = comrak::parse_document(&arena, &file, &mdparser::utils::default_options()); + let _ = convert::npf::from(ast); + // println!("{:#?}", ast); + let result = match cli.command { Commands::Links { list, replace_url } => { let list = if replace_url.len() == 0 && !list {