refactor!: links command and cli logic

This commit is contained in:
Gustavo "Guz" L. de Mello
2024-04-17 17:31:09 -03:00
parent 81ed99b323
commit 43bc232063
2 changed files with 228 additions and 106 deletions

View File

@@ -1,9 +1,12 @@
use std::borrow::{Borrow, BorrowMut};
use std::{cell::RefCell, collections::HashMap};
use std::{fs, io, path::PathBuf};
use comrak::nodes::{Ast, NodeLink, NodeValue};
use comrak::{arena_tree::Node, Arena};
use crate::utils;
pub struct ParseOptions {
pub alias_prop: Option<String>,
pub path_root: PathBuf,
@@ -23,6 +26,30 @@ impl Default for ParseOptions {
}
}
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| {
if let NodeValue::Link(ref mut l) = &mut node.data.borrow_mut().value {
iterator(l);
};
Ok::<(), ()>(())
});
}
pub fn get_links<'a>(ast: &'a Node<'a, RefCell<Ast>>) -> Vec<String> {
let links: RefCell<Vec<String>> = RefCell::new(vec![]);
let _ = utils::iter_nodes(ast, &|node| {
if let NodeValue::Link(l) = &node.data.borrow().value {
links.borrow_mut().push(l.url.clone());
}
Ok::<(), ()>(())
});
let r = links.borrow().to_vec();
r
}
#[derive(Debug)]
pub enum ParsingError {
AliasNotFound { file: String },

View File

@@ -2,14 +2,11 @@ use core::panic;
use std::io::Write;
use clap::{ArgAction, Parser, Subcommand};
use clio::*;
use comrak::nodes::NodeValue;
use clio::{Input, Output};
use mdparser::{
convert,
frontmatter::{self, Frontmatter},
links, utils,
};
use mdparser::convert;
use mdparser::links;
use mdparser::utils;
#[derive(Parser, Debug)]
#[command(version = "0.1", about = "", long_about = None, propagate_version = true)]
@@ -17,10 +14,13 @@ struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long, default_value = "-")]
#[arg(long, global = true, default_value = "lines")]
list_format: cli::ListFormat,
#[arg(short, long, global = true, default_value = "-")]
input: Input,
#[arg(short, long, default_value = "-")]
#[arg(short, long, global = true, default_value = "-")]
output: Output,
#[arg(long)]
@@ -30,20 +30,14 @@ struct Cli {
#[derive(Debug, Subcommand)]
enum Commands {
Links {
#[arg(short, long)]
path_root: clio::ClioPath,
#[arg(short, long, action = ArgAction::SetTrue)]
list: bool,
#[arg(short, long, default_value = "x_alias_url")]
alias_prop: String,
#[arg(short, long, num_args = 2, value_names = ["FROM", "TO"])]
replace_url: Vec<String>,
#[arg(long)]
to_absolute_paths: bool,
#[arg(long)]
not_remove_unalised: bool,
#[arg(long)]
not_remove_unfound: bool,
#[arg(long, default_value = ".")]
root: clio::ClioPath,
},
Frontmatter {
#[command(subcommand)]
@@ -86,95 +80,196 @@ fn main() {
let arena = comrak::Arena::new();
let ast = comrak::parse_document(&arena, &file, &mdparser::utils::default_options());
// println!("{ast:#?}");
if let Commands::Convert { format } = &cli.command {
let r = match format {
convert::Formats::TumblrNPF => convert::to_tumblr_npf(&ast),
};
// println!("{}", serde_json::to_string_pretty(&r.unwrap()).unwrap());
let _ = &cli.output.write(
serde_json::to_string_pretty(&r.unwrap())
.unwrap()
.as_str()
.as_bytes(),
);
return;
}
let _: Result<()> = match &cli.command {
let result = match cli.command {
Commands::Links {
path_root,
alias_prop,
to_absolute_paths,
not_remove_unalised,
not_remove_unfound,
} => utils::iter_nodes(&ast, &|node| {
if let NodeValue::Link(ref mut link) = &mut node.data.borrow_mut().value {
match links::parse(
node,
link,
&links::ParseOptions {
path_root: path_root.to_path_buf(),
alias_prop: Some(String::from(alias_prop)),
to_complete_paths: *to_absolute_paths,
remove_unalised: !*not_remove_unalised,
remove_unfound: !*not_remove_unfound,
},
) {
Ok(_) => (),
Err(err) => {
if !&cli.surpress_errors {
panic!("{err:#?}\n");
} else {
eprint!("{err:#?}\n");
}
list,
root,
replace_url,
} => {
println!("{list:#?} {root:#?} {replace_url:#?}");
let list = if replace_url.len() == 0 && !list {
true
} else {
list
};
replace_url.chunks(2).for_each(|p| {
links::iterate_links(ast, |l| {
if l.url == p[0] {
l.url = (*p[1]).to_string()
}
};
})
});
if list {
let links = links::get_links(ast);
cli::ResultType::List(links)
} 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,
}),
}
}
Ok(())
}
_ => cli::ResultType::Err(cli::Error {
description: "".to_string(),
code: cli::ErrorCode::EPRSG,
url: None,
fix: None,
}),
Commands::Frontmatter { command } => utils::iter_nodes(&ast, &|node| {
if let NodeValue::FrontMatter(ref mut f) = &mut node.data.borrow_mut().value {
let mut frontmatter: Frontmatter<serde_yaml::Value> = match Frontmatter::new(f) {
Ok(f) => f,
Err(e) => panic!("{e:#?}"),
};
match command {
FrontmatterCommands::Set {
property,
value,
json,
} => {
frontmatter.set(
&property,
frontmatter::to_yaml_value(value.to_vec(), !*json),
);
}
FrontmatterCommands::Get {
property,
error_on_unfound,
stderr_on_unfound,
} => {
let v = frontmatter.get(property);
if let Some(v) = v {
print!("{v:#?}")
} else if *error_on_unfound {
panic!()
} else if *stderr_on_unfound {
eprint!("Not Found")
}
}
};
*f = match frontmatter.to_string() {
Ok(s) => s,
Err(e) => panic!("{e:#?}"),
};
}
Ok(())
}),
_ => Ok(()),
};
let _ = comrak::format_commonmark(&ast, &mdparser::utils::default_options(), &mut cli.output);
if let cli::ListFormat::JSON = &cli.list_format {
if cli.output.is_tty() {
cli.list_format = cli::ListFormat::PrettyJSON
}
}
let str = match cli::result_to_str(result, &cli.list_format) {
Ok(s) => s,
Err(e) => {
cli::print_error(e);
panic!();
}
};
match cli.output.write(str.as_bytes()) {
Ok(_) => (),
Err(e) => {
panic!("{e:#?}");
}
}
// println!("{ast:#?}");
}
mod cli {
use std::fmt;
#[derive(Clone, Debug, clap::ValueEnum)]
pub enum ListFormat {
Lines,
Comma,
JSON,
UglyJSON,
PrettyJSON,
}
#[derive(Debug)]
pub enum ErrorCode {
EPRSG,
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = format!("{:?}", self);
write!(f, "{}", s)
}
}
pub struct Error {
pub description: String,
pub code: ErrorCode,
pub fix: Option<String>,
pub url: Option<String>,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let title = match self.code {
ErrorCode::EPRSG => "Parsing error",
};
let fix = if let Some(fix) = &self.fix {
format!("\nFix: {}", fix)
} else {
String::new()
};
let url = if let Some(url) = &self.url {
format!("\nMore info: {}", url)
} else {
String::new()
};
write!(
f,
"Error {:?} - {:?} \n{}{}{}",
self.code, title, self.description, fix, url
)
}
}
pub enum ResultType<T>
where
T: fmt::Display + fmt::Debug + serde::Serialize,
{
List(Vec<T>),
String(String),
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,
) -> Result<String, Error> {
match result {
ResultType::List(list) => match list_format {
ListFormat::Lines => Ok(list
.iter()
.map(|i| i.to_string())
.collect::<Vec<String>>()
.join("\n")
.to_string()),
ListFormat::Comma => Ok(list
.iter()
.map(|i| i.to_string())
.collect::<Vec<String>>()
.join(",")
.to_string()),
ListFormat::UglyJSON | ListFormat::JSON => {
serde_json::to_string(&list).map_err(|e| Error {
description: format!(
"Failed to parse list vector into a JSON output \
on line {}, column {}. Used vector: \n{:#?}",
e.line(),
e.column(),
list
),
code: ErrorCode::EPRSG,
url: None,
fix: None,
})
}
ListFormat::PrettyJSON => serde_json::to_string_pretty(&list).map_err(|e| Error {
description: format!(
"Failed to parse list vector into a JSON output \
on line {}, column {}. Used vector: \n{:#?}",
e.line(),
e.column(),
list
),
code: ErrorCode::EPRSG,
url: None,
fix: None,
}),
},
ResultType::String(s) => Ok(s),
_ => Ok(String::new()),
}
}
pub fn print_error(err: Error) {
eprintln!("{}", err)
}
}