use radicle_surf::{self as surf}; use serde_json::{json, Value}; use radicle::cob; pub(crate) struct Diff<'a>(&'a surf::diff::Diff); impl<'a> Diff<'a> { pub fn new(diff: &'a surf::diff::Diff) -> Self { Self(diff) } pub fn as_json(&self) -> Value { let s = self.0.stats(); json!({ "files": self.0.files().map(|f| { match f { surf::diff::FileDiff::Added(added) => json!({ "status": "added", "path": added.path, "diff": DiffContent::new(&added.diff).as_json(), "new": DiffFile::new(&added.new).as_json(), }), surf::diff::FileDiff::Deleted(deleted) => json!({ "status": "deleted", "path": deleted.path, "diff": DiffContent::new(&deleted.diff).as_json(), "old": DiffFile::new(&deleted.old).as_json(), }), surf::diff::FileDiff::Modified(modified) => json!({ "status": "modified", "path": modified.path, "diff": DiffContent::new(&modified.diff).as_json(), "old": DiffFile::new(&modified.old).as_json(), "new": DiffFile::new(&modified.new).as_json(), }), surf::diff::FileDiff::Moved(moved) => { if moved.old == moved.new { json!({ "status": "moved", "oldPath": moved.old_path, "newPath": moved.new_path, "current": DiffFile::new(&moved.new).as_json(), }) } else { json!({ "status": "moved", "oldPath": moved.old_path, "newPath": moved.new_path, "old": DiffFile::new(&moved.old).as_json(), "new": DiffFile::new(&moved.new).as_json(), "diff": DiffContent::new(&moved.diff).as_json() }) } }, surf::diff::FileDiff::Copied(copied) => { if copied.old == copied.new { json!({ "status": "copied", "oldPath": copied.old_path, "newPath": copied.new_path, "current": DiffFile::new(&copied.new).as_json() }) } else { json!({ "status": "copied", "oldPath": copied.old_path, "newPath": copied.new_path, "old": DiffFile::new(&copied.old).as_json(), "new": DiffFile::new(&copied.new).as_json(), "diff": DiffContent::new(&copied.diff).as_json() }) } }, } }).collect::>(), "stats": json!({ "filesChanged": s.files_changed, "insertions": s.insertions, "deletions": s.deletions, }), }) } } pub(crate) struct CodeLocation<'a>(&'a cob::CodeLocation); impl<'a> CodeLocation<'a> { pub fn new(location: &'a cob::CodeLocation) -> Self { Self(location) } pub fn as_json(&self) -> Value { match (&self.0.old, &self.0.new) { (Some(old), Some(new)) => json!({ "commit": self.0.commit, "path": self.0.path, "old": code_range(old), "new": code_range(new) }), (None, Some(new)) => json!({ "commit": self.0.commit, "path": self.0.path, "new": code_range(new) }), (Some(old), None) => json!({ "commit": self.0.commit, "path": self.0.path, "old": code_range(old) }), (None, None) => json!({ "commit": self.0.commit, "path": self.0.path }), } } } fn code_range(range: &cob::CodeRange) -> Value { match range { cob::CodeRange::Lines { range } => json!({ "type": "lines", "range": range }), cob::CodeRange::Chars { line, range } => { json!({ "type": "chars", "line": line, "range": range }) } } } pub(crate) struct DiffContent<'a>(&'a surf::diff::DiffContent); impl<'a> DiffContent<'a> { pub fn new(value: &'a surf::diff::DiffContent) -> Self { Self(value) } pub fn as_json(&self) -> Value { match self.0 { surf::diff::DiffContent::Binary => json!({ "type": "binary" }), surf::diff::DiffContent::Empty => json!({ "type": "empty" }), surf::diff::DiffContent::Plain { hunks, stats, eof } => { let hunks = hunks .iter() .map(|h| Hunk::new(h).as_json()) .collect::>(); json!({ "type": "plain", "hunks": hunks, "stats": json!({ "additions": stats.additions, "deletions": stats.deletions }), "eof": match eof { surf::diff::EofNewLine::OldMissing => "oldMissing", surf::diff::EofNewLine::NewMissing => "newMissing", surf::diff::EofNewLine::BothMissing => "bothMissing", surf::diff::EofNewLine::NoneMissing => "noneMissing", } }) } } } } pub(crate) struct DiffFile<'a>(&'a surf::diff::DiffFile); impl<'a> DiffFile<'a> { pub fn new(value: &'a surf::diff::DiffFile) -> Self { Self(value) } pub fn as_json(&self) -> Value { json!({ "oid": self.0.oid, "mode": match self.0.mode { surf::diff::FileMode::Blob => "blob", surf::diff::FileMode::BlobExecutable => "blobExecutable", surf::diff::FileMode::Tree => "tree", surf::diff::FileMode::Link => "link", surf::diff::FileMode::Commit => "commit", } }) } } pub(crate) struct Modification<'a>(&'a surf::diff::Modification); impl<'a> Modification<'a> { pub fn new(value: &'a surf::diff::Modification) -> Self { Self(value) } pub fn as_json(&self) -> Value { match self.0 { surf::diff::Modification::Addition(addition) => { json!({ "type": "addition", "line": addition.line.clone().from_utf8().ok(), "lineNo": addition.line_no }) } surf::diff::Modification::Deletion(deletion) => { json!({ "type": "deletion", "line": deletion.line.clone().from_utf8().ok(), "lineNo": deletion.line_no }) } surf::diff::Modification::Context { line, line_no_old, line_no_new, } => { json!({ "type": "context", "line": line.clone().from_utf8().ok(), "lineNoOld": line_no_old, "lineNoNew": line_no_new }) } } } } pub(crate) struct Hunk<'a>(&'a surf::diff::Hunk); impl<'a> Hunk<'a> { pub fn new(value: &'a surf::diff::Hunk) -> Self { Self(value) } pub fn as_json(&self) -> Value { let lines = self .0 .lines .iter() .map(|line| Modification::new(line).as_json()) .collect::>(); json!({ "header": self.0.header, "lines": lines, "old": self.0.old, "new": self.0.new, }) } }