From 8a595d78f3bbf8bd8e18da64c79b3bda938ab63c Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Thu, 18 Apr 2024 21:51:18 -0300 Subject: [PATCH] refactor!: frontmatter command and manipulation --- src/frontmatter.rs | 196 +++++++++++++++++++++++++++++---------------- src/links.rs | 7 +- src/main.rs | 129 +++++++++++++++++------------ src/utils.rs | 17 +++- 4 files changed, 223 insertions(+), 126 deletions(-) diff --git a/src/frontmatter.rs b/src/frontmatter.rs index 52ec6c7..f09a6ac 100644 --- a/src/frontmatter.rs +++ b/src/frontmatter.rs @@ -1,82 +1,142 @@ +use std::borrow::{Borrow, BorrowMut}; +use std::cell::RefCell; + use serde_yaml as yaml; use std::collections::HashMap; +use comrak::arena_tree::Node; +use comrak::nodes::{Ast, NodeValue}; + +use crate::utils; + #[derive(Debug)] -pub struct Frontmatter { - map: HashMap, +pub struct Frontmatter { + map: HashMap, } -impl Frontmatter -where - for<'a> T: serde::de::Deserialize<'a> + serde::Serialize, -{ - pub fn new(f: &mut String) -> Result, yaml::Error> { - let f = f.split("---").collect::>()[1]; - let m = match yaml::from_str::>(&f) { - Ok(m) => m, - Err(e) => return Err(e), - }; - Ok(Frontmatter { map: m }) +impl<'a> Frontmatter { + pub fn new() -> Frontmatter { + Frontmatter { + map: HashMap::new(), + } } - pub fn get(&self, key: &str) -> Option<&T> { + pub fn get(&self, key: &str) -> Option<&yaml::Value> { self.map.get(key) } - pub fn set(&mut self, key: &str, value: T) { + pub fn set(&mut self, key: &str, value: yaml::Value) { self.map.insert(String::from(key), value); } - pub fn to_string(&self) -> Result { - Ok(format!("---\n{}---\n\n", yaml::to_string(&self.map)?)) - } - pub fn to_map(&self) -> &HashMap { - &self.map - } -} - -pub fn to_yaml_value(value: Vec, json_to_string: bool) -> yaml::Value -where - T: ToString + std::fmt::Display, -{ - if value.len() >= 2 { - yaml::Value::Sequence( - value - .iter() - // This causes a recursion limit, which I'm not caring on fixing - // for now knowing the scope of this project as a hole. - // .map(|v| to_yaml_value(vec![v; 1], json_to_string)) - .map(|v| yaml::Value::String(v.to_string())) - .collect::>(), - ); - } - - let value = &value[0].to_string(); - - match value.to_lowercase().as_str() { - "null" | "~" => return yaml::Value::Null, - "true" | "yes" => return yaml::Value::Bool(true), - "false" | "no" => return yaml::Value::Bool(false), - _ => (), - } - - if let Ok(v) = value.parse::() { - return yaml::Value::Number(v.into()); - } - if let Ok(v) = value.parse::() { - return yaml::Value::Number(v.into()); - } - if let Ok(v) = value.parse::() { - return yaml::Value::Number(v.into()); - } - - match yaml::from_str::(value) { - Ok(v) => { - if json_to_string { - return yaml::Value::String(String::from(value)); - } else { - return v; - } + pub fn rename_prop(&mut self, from: &str, to: &str) { + if let Some(value) = self.map.get(from) { + self.map.insert(String::from(to), value.clone()); + self.map.remove(from); } - Err(_) => (), } - - yaml::Value::String(String::from(value)) + pub fn place_on_ast( + self, + ast: &'a Node<'a, RefCell>, + ) -> RefCell, yaml::Error>> { + let result = RefCell::new(Ok(None)); + utils::iter_nodes(ast, &|node| { + if let NodeValue::FrontMatter(ref mut f) = &mut node.data.borrow_mut().value { + match String::try_from(&self) { + Ok(s) => { + // I'm sure there's a way to not use clone here, something in the lines + // of passing the pointer of f to the result, and then changing the pointer + // of f to be equals to s. But premature optimisation is the root of all + // evil, and this is a not a "serious" project. When I better learn Rust, + // I hopefully learn how to do this. + // + // - Gustavo "Guz" L. de Mello (2024-04-18) + *result.borrow_mut() = Ok(Some(f.clone())); + f.replace_range(.., &s); + } + Err(e) => { + *result.borrow_mut() = Err(e); + } + }; + } + }); + result + } +} + +impl<'a> TryFrom<&'a Node<'a, RefCell>> for Frontmatter { + type Error = yaml::Error; + fn try_from(value: &'a Node<'a, RefCell>) -> Result { + let str = RefCell::new(String::new()); + utils::iter_nodes(value, &|node| { + if let NodeValue::FrontMatter(f) = &node.data.borrow_mut().value { + *str.borrow_mut() = f.to_string(); + }; + }); + let s = str.borrow().clone(); + Frontmatter::try_from(s) + } +} + +impl TryFrom<&str> for Frontmatter { + type Error = yaml::Error; + fn try_from(value: &str) -> Result { + let value = if value.trim().starts_with("---") { + value.split("---").collect::>()[1] + } else { + value + }; + Ok(Frontmatter { + map: yaml::from_str::>(&value)?, + }) + } +} + +impl TryFrom for Frontmatter { + type Error = yaml::Error; + fn try_from(value: String) -> Result { + Frontmatter::try_from(value.as_str()) + } +} + +impl TryFrom<&String> for Frontmatter { + type Error = yaml::Error; + fn try_from(value: &String) -> Result { + Frontmatter::try_from(value.as_str()) + } +} + +impl AsRef for Frontmatter { + fn as_ref(&self) -> &Frontmatter { + self + } +} + +impl AsMut for Frontmatter { + fn as_mut(&mut self) -> &mut Frontmatter { + self + } +} + +impl TryFrom for String { + type Error = yaml::Error; + fn try_from(value: Frontmatter) -> Result { + String::try_from(&value) + } +} + +impl TryFrom<&Frontmatter> for String { + type Error = yaml::Error; + fn try_from(value: &Frontmatter) -> Result { + Ok(format!("---\n{}---\n\n", yaml::to_string(&value.map)?)) + } +} + +impl From for HashMap { + fn from(value: Frontmatter) -> Self { + value.map + } +} + +impl From<&Frontmatter> for HashMap { + fn from(value: &Frontmatter) -> Self { + value.map.clone() + } } diff --git a/src/links.rs b/src/links.rs index c234566..5eacabd 100644 --- a/src/links.rs +++ b/src/links.rs @@ -9,18 +9,17 @@ pub fn iterate_links<'a, F>(ast: &'a Node<'a, RefCell>, iterator: F) where F: Fn(&mut NodeLink), { - let _ = utils::iter_nodes(ast, &|node| { + utils::iter_nodes(ast, &|node| { if let NodeValue::Link(ref mut l) = &mut node.data.borrow_mut().value { iterator(l); }; - Ok::<(), ()>(()) }); } -pub fn replace_links<'a>(ast: &'a Node<'a, RefCell>, from: &'a str, to: &'a str) { +pub fn replace_links<'a>(ast: &'a Node<'a, RefCell>, from: String, to: String) { iterate_links(ast, |l| { if l.url == from { - l.url = String::from(to) + l.url = String::from(&to) } }); } diff --git a/src/main.rs b/src/main.rs index 4131471..d7601c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,10 @@ -use std::borrow::Borrow; -use std::fs::File; use std::io::Write; use std::path::PathBuf; use clap::{ArgAction, Parser, Subcommand}; -use clio::{Input, Output}; +use clio::Input; -use mdparser::convert; +use mdparser::frontmatter::Frontmatter; use mdparser::links; use mdparser::utils; @@ -39,36 +37,14 @@ enum Commands { replace_url: Vec, }, Frontmatter { - #[command(subcommand)] - command: FrontmatterCommands, - }, - Convert { - #[arg(short, long)] - format: convert::Formats, - }, -} - -#[derive(Debug, Subcommand)] -enum FrontmatterCommands { - Set { - #[clap()] - property: String, - - #[clap(num_args(1..))] - value: Vec, - #[arg(short, long, action = ArgAction::SetTrue)] - json: bool, - }, - Get { - #[clap()] - property: String, + list: bool, - #[arg(short, long, action = ArgAction::SetTrue)] - error_on_unfound: bool, + #[arg(short, long, num_args = 2, value_names = ["PROPERTY", "VALUE"], allow_negative_numbers = true)] + set_value: Vec, - #[arg(short, long, action = ArgAction::SetTrue)] - stderr_on_unfound: bool, + #[arg(short, long, num_args = 2, value_names = ["FROM", "TO"])] + rename_prop: Vec, }, } @@ -104,23 +80,54 @@ fn main() { list }; + // TODO: Remove clone replace_url .chunks(2) - .for_each(|p| links::replace_links(ast, &p[0], &p[1])); + .for_each(|p| links::replace_links(ast, p[0].clone(), p[1].clone())); if list { cli::ResultType::List(links::get_links(ast)) } else { - let mut str = vec![]; - match comrak::format_commonmark(ast, &utils::default_options(), &mut str) { - Ok(_) => cli::ResultType::String(String::from_utf8(str).unwrap()), - Err(e) => cli::ResultType::Err(cli::Error { - code: cli::ErrorCode::EPRSG, - description: format!("Error formatting ast back to markdown\n{e:#?}"), - fix: None, - url: None, - }), - } + cli::ResultType::Markdown(ast) + } + } + Commands::Frontmatter { + list, + set_value, + rename_prop, + } => { + let list = if set_value.len() + rename_prop.len() == 0 && !list { + true + } else { + list + }; + + let mut frontmatter = Frontmatter::try_from(ast).unwrap(); + + // I don't care anymore + set_value + .chunks(2) + .map(|c| { + if let Ok(j) = serde_json::from_str::(&c[1]) { + (c[0].clone(), serde_yaml::to_value(j).unwrap()) + } else if let Ok(i) = String::from(&c[1]).parse::() { + (c[0].clone(), serde_yaml::to_value(i).unwrap()) + } else { + (c[0].clone(), serde_yaml::to_value(c[1].clone()).unwrap()) + } + }) + .for_each(|p| frontmatter.set(&p.0, p.1)); + + rename_prop + .chunks(2) + .for_each(|p| frontmatter.rename_prop(&p[0], &p[1])); + + frontmatter.place_on_ast(ast); + + if list { + todo!() + } else { + cli::ResultType::Markdown(ast) } } _ => cli::ResultType::Err(cli::Error { @@ -208,7 +215,10 @@ fn main() { mod cli { use core::panic; - use std::fmt; + use std::{cell::RefCell, fmt}; + + use comrak::{arena_tree::Node, nodes::Ast}; + use mdparser::utils; #[derive(Clone, Debug, clap::ValueEnum)] pub enum ListFormat { @@ -270,20 +280,16 @@ mod cli { } #[derive(Debug)] - pub enum ResultType + pub enum ResultType<'a, T> where T: fmt::Display + fmt::Debug + serde::Serialize, { List(Vec), String(String), + Markdown(&'a Node<'a, RefCell>), Err(Error), } - #[derive(serde::Serialize)] - struct YAMLList { - list: Vec, - } - pub fn result_to_str( result: ResultType, list_format: &ListFormat, @@ -330,7 +336,30 @@ mod cli { }), }, ResultType::String(s) => Ok(s), - _ => Ok(String::new()), + ResultType::Markdown(ast) => { + let mut str = vec![]; + if let Err(e) = comrak::format_commonmark(ast, &utils::default_options(), &mut str) + { + return Err(Error { + code: ErrorCode::EPRSG, + description: format!("Error formatting ast back to markdown\n{e:#?}"), + fix: None, + url: None, + }); + } + match String::from_utf8(str) { + Ok(s) => Ok(s), + Err(e) => Err(Error { + code: ErrorCode::EPRSG, + description: format!( + "Error making string from utf8, after markdown formatting\n{e:#?}", + ), + fix: None, + url: None, + }), + } + } + ResultType::Err(e) => Err(e), } } diff --git a/src/utils.rs b/src/utils.rs index ffecf07..7971057 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,13 +14,23 @@ pub fn default_options() -> comrak::Options { opts } -pub fn iter_nodes<'a, F, E>(node: &'a comrak::nodes::AstNode<'a>, f: &F) -> Result<(), E> +pub fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) +where + F: Fn(&'a comrak::nodes::AstNode<'a>), +{ + f(node); + for c in node.children() { + iter_nodes(c, f) + } +} + +pub fn iter_nodes_err<'a, F, E>(node: &'a comrak::nodes::AstNode<'a>, f: &F) -> Result<(), E> where F: Fn(&'a comrak::nodes::AstNode<'a>) -> Result<(), E>, { f(node)?; for c in node.children() { - iter_nodes(c, f)? + iter_nodes_err(c, f)? } Ok(()) } @@ -54,11 +64,10 @@ where pub fn extract_text<'a>(node: &'a comrak::nodes::AstNode<'a>) -> String { let text = RefCell::new(String::new()); - let _ = iter_nodes(node, &|node| { + iter_nodes(node, &|node| { if let NodeValue::Text(t) = &node.data.borrow().value { text.borrow_mut().push_str(&t); } - Ok::<(), std::fmt::Error>(()) }); let r = text.borrow().to_string(); r