refactor!: frontmatter command and manipulation
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
129
src/main.rs
129
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<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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
src/utils.rs
17
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
|
||||
|
||||
Reference in New Issue
Block a user