From c4f493966a063562b61814f2cf53f1757eee65dc Mon Sep 17 00:00:00 2001 From: JMARyA Date: Sat, 4 Nov 2023 08:22:18 +0100 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 482 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 19 +++ src/args.rs | 27 +++ src/main.rs | 364 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 893 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/args.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4909d5a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,482 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "clap" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "json-patch" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "treediff", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "mdpatch" +version = "0.1.0" +dependencies = [ + "clap", + "console", + "json-patch", + "regex", + "serde", + "serde_json", + "serde_yaml", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "treediff" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" +dependencies = [ + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..189505b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mdpatch" +version = "0.1.0" +edition = "2021" + +[profile.release] +strip = true +lto = true +codegen-units = 1 +panic = "abort" + +[dependencies] +clap = { version = "4.4.7", features = ["cargo"] } +console = "0.15.7" +json-patch = "1.2.0" +regex = "1.10.2" +serde = "1.0.190" +serde_json = "1.0.108" +serde_yaml = "0.9.27" diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..292e60f --- /dev/null +++ b/src/args.rs @@ -0,0 +1,27 @@ +use clap::{arg, command, ArgMatches}; + +pub fn get_args() -> ArgMatches { + command!() + .about("Patch the frontmatter of markdown files") + .arg(arg!([markdown] "Markdown File").required(true)) + .arg(arg!(-p --patch "Apply JSON Patch file. If set to '-' read from stdin").required(false).conflicts_with("merge")) + .arg(arg!(--merge "Merge Markdown frontmatter. If set to '-' read from stdin").required(false).conflicts_with("patch")) + .arg(arg!(-a --add "Only patch add operations").required(false).conflicts_with("merge")) + .arg(arg!(-m --modify "Only patch modify operations").required(false).conflicts_with("merge")) + .arg(arg!(-d --delete "Only patch delete operations").required(false).conflicts_with("merge")) + .arg(arg!(-v --verbose "Print out what changes will be made to the document").required(false)) + .arg(arg!(-n --dryrun "Dont modify the input file. Only print what would be done").required(false)) + .arg(arg!(-o --output "Write patched file to output path. Dont modify the input file directly. If set to '-' output to stdout")) + .arg(arg!(--notest "Ignore tests in JSON Patch files").conflicts_with("merge")) + .arg( + arg!(-x --exclude ... "Exclude json path from patch") + .required(false) + .conflicts_with("keys"), + ) + .arg( + arg!(-k --keys ... "Only include the specified json paths in patch") + .required(false) + .conflicts_with("exclude"), + ) + .get_matches() +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7ad4892 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,364 @@ +use std::io::Read; + +use json_patch::PatchOperation; +use serde_json::Value; + +mod args; + +trait ErrorExit { + type T; + + fn quit_on_error(self, msg: &str) -> Self::T; +} + +impl ErrorExit for Result { + 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, + ) + } +} + +/// seperate markdown string into frontmatter and markdown content +#[must_use] +pub fn markdown_with_frontmatter(markdown: &str) -> (Option, String) { + let frontmatter_regex = regex::Regex::new(r"(?s)^---\s*\n(.*?)\n---\s*\n(.*)$").unwrap(); + + frontmatter_regex.captures(markdown).map_or_else( + || (None, markdown.to_string()), + |captures| { + let frontmatter = captures.get(1).map(|m| m.as_str().to_string()); + let remaining_markdown = captures + .get(2) + .map(|m| m.as_str().to_string()) + .unwrap_or_default(); + + (frontmatter, remaining_markdown) + }, + ) +} + +fn assemble_markdown_doc(content: &str, frontmatter: serde_json::Value) -> String { + let frontmatter = serde_yaml::to_string(&serde_yaml::to_value(frontmatter).unwrap()).unwrap(); + format!("---\n{frontmatter}---\n\n{content}") +} + +fn remove_test_operations(patch: Vec) -> Vec { + patch + .into_iter() + .filter(|x| !matches!(x, json_patch::PatchOperation::Test(_))) + .collect() +} + +fn remove_add_operations(patch: Vec) -> Vec { + patch + .into_iter() + .filter(|x| !matches!(x, json_patch::PatchOperation::Add(_))) + .collect() +} + +fn remove_modify_operations(patch: Vec) -> Vec { + 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) -> Vec { + 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 = 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 = 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 only_include_json_pointers(paths: &[String], json_value: &Value) -> Value { + match json_value { + Value::Object(obj) => { + let mut filtered_obj = serde_json::Map::new(); + for path in paths { + if let Some(key) = path.strip_prefix('/') { + if let Some(value) = obj.get(key) { + filtered_obj + .insert(key.to_string(), only_include_json_pointers(paths, value)); + } + } + } + Value::Object(filtered_obj) + } + Value::Array(arr) => { + let filtered_arr: Vec = arr + .iter() + .map(|item| only_include_json_pointers(paths, item)) + .collect(); + Value::Array(filtered_arr) + } + _ => json_value.clone(), + } +} + +fn exclude_json_by_paths(paths: &[String], json_value: &Value) -> Value { + match json_value { + Value::Object(obj) => { + let mut filtered_obj = serde_json::Map::new(); + for (key, value) in obj { + if !paths.iter().any(|path| path == &("/".to_string() + key)) { + filtered_obj.insert(key.clone(), exclude_json_by_paths(paths, value)); + } + } + Value::Object(filtered_obj) + } + Value::Array(arr) => { + let filtered_arr: Vec = arr + .iter() + .map(|item| exclude_json_by_paths(paths, item)) + .collect(); + Value::Array(filtered_arr) + } + _ => json_value.clone(), + } +} + +fn main() { + let args = args::get_args(); + + let markdown_file = args.get_one::("markdown").unwrap(); + + let (frontmatter, markdown_content) = markdown_with_frontmatter( + &std::fs::read_to_string(markdown_file).quit_on_error("Could not read markdown file"), + ); + let frontmatter = frontmatter + .ok_or(0) + .quit_on_error("Could not parse frontmatter"); + let mut frontmatter = serde_json::to_value( + &serde_yaml::from_str::(&frontmatter) + .quit_on_error("Frontmatter is no valid yaml"), + ) + .unwrap(); + + if let Some(patch_file) = args.get_one::("patch") { + let mut patch: Vec = serde_json::from_str( + &(if patch_file == "-" { + let mut str = String::new(); + std::io::stdin() + .read_to_string(&mut str) + .quit_on_error("Coult not read stdin"); + str + } else { + std::fs::read_to_string(patch_file).quit_on_error("Could not read patch file") + }), + ) + .quit_on_error("Could not parse patch file"); + + if args.get_flag("add") | args.get_flag("modify") | args.get_flag("delete") { + if !args.get_flag("add") { + patch = remove_add_operations(patch); + } + if !args.get_flag("modify") { + patch = remove_modify_operations(patch); + } + if !args.get_flag("delete") { + patch = remove_delete_operations(patch); + } + } + + if args.get_flag("notest") { + patch = remove_test_operations(patch); + } + + let mut patch = serde_json::to_value(patch).unwrap(); + + if let Some(excludes) = args.get_many::("exclude") { + let excludes: Vec = excludes.cloned().collect(); + patch = exclude_json_paths(&patch, &excludes); + } + + if let Some(includes) = args.get_many::("keys") { + let includes: Vec = includes.cloned().collect(); + patch = include_only_json_paths(&patch, &includes); + } + + let patch: Vec = serde_json::from_value(patch).unwrap(); + + if args.get_flag("verbose") | args.get_flag("dryrun") { + for op in patch.clone() { + match op { + PatchOperation::Add(op) => { + eprintln!( + "Add {} at {}", + console::Style::new().green().apply_to(op.value), + console::Style::new().bright().red().apply_to(op.path) + ); + } + PatchOperation::Remove(op) => { + eprintln!( + "Remove value at {}", + console::Style::new().bright().red().apply_to(op.path) + ); + } + PatchOperation::Replace(op) => { + eprintln!( + "Replace value at {} with {}", + console::Style::new().bright().red().apply_to(op.path), + console::Style::new().green().apply_to(op.value) + ); + } + PatchOperation::Move(op) => { + eprintln!( + "Move value from {} to {}", + console::Style::new().bright().red().apply_to(op.from), + console::Style::new().green().apply_to(op.path) + ); + } + PatchOperation::Copy(op) => { + eprintln!( + "Copy value from {} to {}", + console::Style::new().bright().red().apply_to(op.from), + console::Style::new().green().apply_to(op.path) + ); + } + PatchOperation::Test(op) => { + eprintln!( + "Test value at {} for {}", + console::Style::new().bright().red().apply_to(op.path), + console::Style::new().green().apply_to(op.value) + ); + } + } + } + } + + if !args.get_flag("dryrun") { + if let Err(e) = json_patch::patch(&mut frontmatter, &patch) { + eprintln!( + "{} Patch failed", + console::Style::new().red().apply_to("Error:") + ); + eprintln!("{e}"); + std::process::exit(1); + } + + let markdown_doc = assemble_markdown_doc(&markdown_content, frontmatter); + if let Some(output_path) = args.get_one::("output") { + if output_path == "-" { + print!("{markdown_doc}"); + } else { + std::fs::write(output_path, markdown_doc) + .quit_on_error("Could not write output file"); + } + } else { + std::fs::write(markdown_file, markdown_doc) + .quit_on_error("Could not write patched file"); + } + } + } else if let Some(merge_file) = args.get_one::("merge") { + // TODO : Add support for merging frontmatter from markdown file + let mut merge_doc: serde_json::Value = serde_json::from_str( + &(if merge_file == "-" { + let mut str = String::new(); + std::io::stdin() + .read_to_string(&mut str) + .quit_on_error("Coult not read stdin"); + str + } else { + std::fs::read_to_string(merge_file).quit_on_error("Could not read merge file") + }), + ) + .quit_on_error("Could not parse merge file"); + + if let Some(excludes) = args.get_many::("exclude") { + let excludes: Vec = excludes.cloned().collect(); + merge_doc = exclude_json_by_paths(&excludes, &merge_doc); + } + + if let Some(includes) = args.get_many::("keys") { + let includes: Vec = includes.cloned().collect(); + merge_doc = only_include_json_pointers(&includes, &merge_doc); + } + + if args.get_flag("verbose") | args.get_flag("dryrun") { + eprintln!("Merging {}", serde_json::to_string(&merge_doc).unwrap()); + } + + if !args.get_flag("dryrun") { + json_patch::merge(&mut frontmatter, &merge_doc); + + let markdown_doc = assemble_markdown_doc(&markdown_content, frontmatter); + if let Some(output_path) = args.get_one::("output") { + if output_path == "-" { + print!("{markdown_doc}"); + } else { + std::fs::write(output_path, markdown_doc) + .quit_on_error("Could not write output file"); + } + } else { + std::fs::write(markdown_file, markdown_doc) + .quit_on_error("Could not write patched file"); + } + } + } +}