This commit is contained in:
JMARyA 2023-11-04 08:19:49 +01:00
commit e76f1e4419
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
5 changed files with 704 additions and 0 deletions

32
src/args.rs Normal file
View file

@ -0,0 +1,32 @@
use clap::{arg, command, Arg, ArgMatches};
pub fn get_args() -> ArgMatches {
command!()
.about("Generate json patch file for two markdown files with frontmatter")
.arg(arg!([file] "First File").required(true))
.arg(arg!([file2] "Second File").required(true))
.arg(arg!(-p --pretty "Output formatted json patch").required(false))
.arg(arg!(-a --add "Only output add operations").required(false))
.arg(arg!(-m --modify "Only output modify operations").required(false))
.arg(arg!(-d --delete "Only output delete operations").required(false))
.arg(
Arg::new("test")
.short('t')
.long("test")
.required(false)
.value_names(&["key", "val"])
.number_of_values(2)
.help("Add test case to json patch"),
)
.arg(
arg!(-x --exclude <JSONPATH>... "Exclude json path from diff")
.required(false)
.conflicts_with("keys"),
)
.arg(
arg!(-k --keys <JSONPATH>... "Only include the specified json paths in diff")
.required(false)
.conflicts_with("exclude"),
)
.get_matches()
}

170
src/main.rs Normal file
View file

@ -0,0 +1,170 @@
use json_patch::PatchOperation;
mod args;
trait ErrorExit {
type T;
fn quit_on_error(self, msg: &str) -> Self::T;
}
impl<T, E> ErrorExit for Result<T, E> {
type T = T;
fn quit_on_error(self, msg: &str) -> T {
self.map_or_else(
|_| {
eprintln!("{} {msg}", console::Style::new().red().apply_to("Error:"));
std::process::exit(1);
},
|res| res,
)
}
}
/// get frontmatter from markdown document
#[must_use]
pub fn get_frontmatter(markdown: &str) -> Option<String> {
let frontmatter_regex = regex::Regex::new(r"(?s)^---\s*\n(.*?)\n---").unwrap();
frontmatter_regex.captures(markdown).and_then(|captures| {
let frontmatter = captures.get(1).map(|m| m.as_str().to_string());
frontmatter
})
}
fn get_frontmatter_from_file(path: &str) -> serde_yaml::Value {
serde_yaml::from_str(
&get_frontmatter(&std::fs::read_to_string(path).quit_on_error("Could not read file"))
.ok_or(0)
.quit_on_error("Could not get frontmatter"),
)
.quit_on_error("Could not deserialize frontmatter")
}
fn remove_add_operations(patch: Vec<PatchOperation>) -> Vec<PatchOperation> {
patch
.into_iter()
.filter(|x| !matches!(x, json_patch::PatchOperation::Add(_)))
.collect()
}
fn remove_modify_operations(patch: Vec<PatchOperation>) -> Vec<PatchOperation> {
patch
.into_iter()
.filter(|x| {
!matches!(
x,
json_patch::PatchOperation::Replace(_)
| json_patch::PatchOperation::Move(_)
| json_patch::PatchOperation::Copy(_)
)
})
.collect()
}
fn remove_delete_operations(patch: Vec<PatchOperation>) -> Vec<PatchOperation> {
patch
.into_iter()
.filter(|x| !matches!(x, json_patch::PatchOperation::Remove(_)))
.collect()
}
fn exclude_json_paths(patch: &serde_json::Value, paths: &[String]) -> serde_json::Value {
let filtered: Vec<serde_json::Value> = patch
.as_array()
.unwrap()
.iter()
.filter(|x| {
let mut is_excluded = false;
for exclude in paths {
if x.as_object()
.unwrap()
.get("path")
.unwrap()
.as_str()
.unwrap()
.starts_with(exclude)
{
is_excluded = true;
}
}
!is_excluded
})
.cloned()
.collect();
serde_json::to_value(filtered).unwrap()
}
fn include_only_json_paths(patch: &serde_json::Value, paths: &[String]) -> serde_json::Value {
let filtered: Vec<serde_json::Value> = patch
.as_array()
.unwrap()
.iter()
.filter(|x| {
let mut is_included = false;
for include in paths {
if x.as_object()
.unwrap()
.get("path")
.unwrap()
.as_str()
.unwrap()
.starts_with(include)
{
is_included = true;
}
}
is_included
})
.cloned()
.collect();
serde_json::to_value(filtered).unwrap()
}
fn main() {
let args = args::get_args();
let f1 = get_frontmatter_from_file(args.get_one::<String>("file").unwrap());
let f2 = get_frontmatter_from_file(args.get_one::<String>("file2").unwrap());
let mut diff = json_patch::diff(
&serde_json::to_value(f1).unwrap(),
&serde_json::to_value(f2).unwrap(),
)
.to_vec();
if args.get_flag("add") | args.get_flag("modify") | args.get_flag("delete") {
if !args.get_flag("add") {
diff = remove_add_operations(diff);
}
if !args.get_flag("modify") {
diff = remove_modify_operations(diff);
}
if !args.get_flag("delete") {
diff = remove_delete_operations(diff);
}
}
let mut diff = serde_json::to_value(diff).unwrap();
if let Some(excludes) = args.get_many::<String>("exclude") {
let excludes: Vec<String> = excludes.cloned().collect();
diff = exclude_json_paths(&diff, &excludes);
}
if let Some(includes) = args.get_many::<String>("keys") {
let includes: Vec<String> = includes.cloned().collect();
diff = include_only_json_paths(&diff, &includes);
}
println!(
"{}",
if args.get_flag("pretty") {
serde_json::to_string_pretty(&diff).unwrap()
} else {
serde_json::to_string(&diff).unwrap()
}
);
}