refactor!: frontmatter command and manipulation

This commit is contained in:
Gustavo "Guz" L. de Mello
2024-04-18 21:51:18 -03:00
parent c26c9cf888
commit 8a595d78f3
4 changed files with 223 additions and 126 deletions

View File

@@ -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<T> {
map: HashMap<String, T>,
pub struct Frontmatter {
map: HashMap<String, yaml::Value>,
}
impl<T> Frontmatter<T>
where
for<'a> T: serde::de::Deserialize<'a> + serde::Serialize,
{
pub fn new(f: &mut String) -> Result<Frontmatter<T>, yaml::Error> {
let f = f.split("---").collect::<Vec<&str>>()[1];
let m = match yaml::from_str::<HashMap<String, T>>(&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<String, yaml::Error> {
Ok(format!("---\n{}---\n\n", yaml::to_string(&self.map)?))
}
pub fn to_map(&self) -> &HashMap<String, T> {
&self.map
}
}
pub fn to_yaml_value<T>(value: Vec<T>, 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::<Vec<yaml::Value>>(),
);
}
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::<u64>() {
return yaml::Value::Number(v.into());
}
if let Ok(v) = value.parse::<i64>() {
return yaml::Value::Number(v.into());
}
if let Ok(v) = value.parse::<f64>() {
return yaml::Value::Number(v.into());
}
match yaml::from_str::<serde_yaml::Value>(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<Ast>>,
) -> RefCell<Result<Option<String>, 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<Ast>>> for Frontmatter {
type Error = yaml::Error;
fn try_from(value: &'a Node<'a, RefCell<Ast>>) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
let value = if value.trim().starts_with("---") {
value.split("---").collect::<Vec<&str>>()[1]
} else {
value
};
Ok(Frontmatter {
map: yaml::from_str::<HashMap<String, yaml::Value>>(&value)?,
})
}
}
impl TryFrom<String> for Frontmatter {
type Error = yaml::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Frontmatter::try_from(value.as_str())
}
}
impl TryFrom<&String> for Frontmatter {
type Error = yaml::Error;
fn try_from(value: &String) -> Result<Self, Self::Error> {
Frontmatter::try_from(value.as_str())
}
}
impl AsRef<Frontmatter> for Frontmatter {
fn as_ref(&self) -> &Frontmatter {
self
}
}
impl AsMut<Frontmatter> for Frontmatter {
fn as_mut(&mut self) -> &mut Frontmatter {
self
}
}
impl TryFrom<Frontmatter> for String {
type Error = yaml::Error;
fn try_from(value: Frontmatter) -> Result<Self, Self::Error> {
String::try_from(&value)
}
}
impl TryFrom<&Frontmatter> for String {
type Error = yaml::Error;
fn try_from(value: &Frontmatter) -> Result<Self, Self::Error> {
Ok(format!("---\n{}---\n\n", yaml::to_string(&value.map)?))
}
}
impl From<Frontmatter> for HashMap<String, yaml::Value> {
fn from(value: Frontmatter) -> Self {
value.map
}
}
impl From<&Frontmatter> for HashMap<String, yaml::Value> {
fn from(value: &Frontmatter) -> Self {
value.map.clone()
}
}

View File

@@ -9,18 +9,17 @@ pub fn iterate_links<'a, F>(ast: &'a Node<'a, RefCell<Ast>>, 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<Ast>>, from: &'a str, to: &'a str) {
pub fn replace_links<'a>(ast: &'a Node<'a, RefCell<Ast>>, from: String, to: String) {
iterate_links(ast, |l| {
if l.url == from {
l.url = String::from(to)
l.url = String::from(&to)
}
});
}

View File

@@ -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<String>,
},
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<String>,
#[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<String>,
#[arg(short, long, action = ArgAction::SetTrue)]
stderr_on_unfound: bool,
#[arg(short, long, num_args = 2, value_names = ["FROM", "TO"])]
rename_prop: Vec<String>,
},
}
@@ -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::<serde_json::Value>(&c[1]) {
(c[0].clone(), serde_yaml::to_value(j).unwrap())
} else if let Ok(i) = String::from(&c[1]).parse::<f64>() {
(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<T>
pub enum ResultType<'a, T>
where
T: fmt::Display + fmt::Debug + serde::Serialize,
{
List(Vec<T>),
String(String),
Markdown(&'a Node<'a, RefCell<Ast>>),
Err(Error),
}
#[derive(serde::Serialize)]
struct YAMLList<T: fmt::Display + fmt::Debug + serde::Serialize> {
list: Vec<T>,
}
pub fn result_to_str<T: fmt::Display + fmt::Debug + serde::Serialize>(
result: ResultType<T>,
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),
}
}

View File

@@ -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