From 43bc232063560fa57bae1b3366462deab3d052e0 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Wed, 17 Apr 2024 17:31:09 -0300 Subject: [PATCH] refactor!: links command and cli logic --- src/links.rs | 27 +++++ src/main.rs | 307 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 228 insertions(+), 106 deletions(-) diff --git a/src/links.rs b/src/links.rs index 799b904..27c9e85 100644 --- a/src/links.rs +++ b/src/links.rs @@ -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, pub path_root: PathBuf, @@ -23,6 +26,30 @@ impl Default for ParseOptions { } } +pub fn iterate_links<'a, F>(ast: &'a Node<'a, RefCell>, 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>) -> Vec { + let links: RefCell> = 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 }, diff --git a/src/main.rs b/src/main.rs index 8445e33..626ec7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, - #[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 = 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, + pub url: Option, + } + 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 + where + T: fmt::Display + fmt::Debug + serde::Serialize, + { + List(Vec), + String(String), + Err(Error), + } + + #[derive(serde::Serialize)] + struct YAMLList { + list: Vec, + } + + pub fn result_to_str( + result: ResultType, + list_format: &ListFormat, + ) -> Result { + match result { + ResultType::List(list) => match list_format { + ListFormat::Lines => Ok(list + .iter() + .map(|i| i.to_string()) + .collect::>() + .join("\n") + .to_string()), + ListFormat::Comma => Ok(list + .iter() + .map(|i| i.to_string()) + .collect::>() + .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) + } }