use prikk_store::{ShowBlobContent, ShowDeletePreimage, ShowOperationContent, ShowPathResolution};
use prikk_store::{ShowOperation, ShowPatch};
use crate::stdout::println;
use super::verification::escape_json_string;
fn print_path(label: &str, path: &ShowPathResolution) {
match path {
ShowPathResolution::Path(path) => println!(" {label}: {path}"),
ShowPathResolution::Unresolved { node_id } => {
println!(" {label}: <unresolved node {node_id}>");
}
}
}
fn print_text(label: &str, bytes: &[u8]) {
println!(" {label}:");
println!("{}", String::from_utf8_lossy(bytes));
}
fn print_blob_content(label: &str, content: &ShowBlobContent) {
match content {
ShowBlobContent::Text(bytes) => print_text(label, bytes),
ShowBlobContent::Binary { blob_id, size } => {
println!(" {label}: <binary blob {blob_id}, {size} bytes>");
}
ShowBlobContent::Unavailable { blob_id } => {
println!(" {label}: <unavailable blob {blob_id}>");
}
}
}
fn print_operation(index: usize, operation: &ShowOperation) {
println!(" operation {}: {}", index + 1, operation.kind);
match &operation.content {
ShowOperationContent::CreateFile { content, mode } => {
let [path] = operation.paths.as_slice() else {
println!(" (expected exactly one path)");
return;
};
print_path("path", path);
println!(" mode: {:o}", mode & 0o7777);
print_blob_content("content", content);
}
ShowOperationContent::DeleteNode { preimage } => {
let [path] = operation.paths.as_slice() else {
println!(" (expected exactly one path)");
return;
};
print_path("path", path);
match preimage {
ShowDeletePreimage::File(content) => print_blob_content("content", content),
ShowDeletePreimage::Symlink { old_target } => {
println!(" target: {old_target}");
}
}
}
ShowOperationContent::EditText {
old_span_text,
replacement_text,
} => {
let [path] = operation.paths.as_slice() else {
println!(" (expected exactly one path)");
return;
};
print_path("path", path);
print_text("old", old_span_text);
print_text("new", replacement_text);
}
ShowOperationContent::ReplaceBinary { old, new } => {
let [path] = operation.paths.as_slice() else {
println!(" (expected exactly one path)");
return;
};
print_path("path", path);
print_blob_content("old", old);
print_blob_content("new", new);
}
ShowOperationContent::RenamePath { author_key_id } => {
let [old_path, new_path] = operation.paths.as_slice() else {
println!(" (expected exactly two paths)");
return;
};
print_path("old path", old_path);
print_path("new path", new_path);
println!(" asserted by (AUTHOR key id): {author_key_id}");
}
ShowOperationContent::ChangePerm { old_mode, new_mode } => {
let [path] = operation.paths.as_slice() else {
println!(" (expected exactly one path)");
return;
};
print_path("path", path);
println!(
" mode: {:o} -> {:o}",
old_mode & 0o7777,
new_mode & 0o7777
);
}
ShowOperationContent::CreateSymlink { target } => {
let [path] = operation.paths.as_slice() else {
println!(" (expected exactly one path)");
return;
};
print_path("path", path);
println!(" target: {target}");
}
}
}
pub(crate) fn print_show(patches: &[ShowPatch]) {
println!("patches: {}", patches.len());
for patch in patches {
println!();
println!("patch {}", patch.patch_id);
for (index, operation) in patch.operations.iter().enumerate() {
print_operation(index, operation);
}
}
}
fn push_path(json: &mut String, path: &ShowPathResolution) {
match path {
ShowPathResolution::Path(path) => {
json.push_str("{\"path\": ");
json.push_str(&escape_json_string(path));
json.push('}');
}
ShowPathResolution::Unresolved { node_id } => {
json.push_str("{\"unresolved_node_id\": ");
json.push_str(&escape_json_string(node_id));
json.push('}');
}
}
}
fn push_blob_content(json: &mut String, content: &ShowBlobContent) {
match content {
ShowBlobContent::Text(bytes) => {
json.push_str("{\"kind\": \"text\", \"text\": ");
json.push_str(&escape_json_string(&String::from_utf8_lossy(bytes)));
json.push('}');
}
ShowBlobContent::Binary { blob_id, size } => {
json.push_str("{\"kind\": \"binary\", \"blob_id\": ");
json.push_str(&escape_json_string(&blob_id.to_string()));
json.push_str(&format!(", \"size\": {size}}}"));
}
ShowBlobContent::Unavailable { blob_id } => {
json.push_str("{\"kind\": \"unavailable\", \"blob_id\": ");
json.push_str(&escape_json_string(&blob_id.to_string()));
json.push('}');
}
}
}
fn push_operation(json: &mut String, operation: &ShowOperation) {
json.push_str("{\"kind\": ");
json.push_str(&escape_json_string(operation.kind));
json.push_str(", \"paths\": [");
for (index, path) in operation.paths.iter().enumerate() {
if index > 0 {
json.push(',');
}
push_path(json, path);
}
json.push_str("], \"content\": ");
match &operation.content {
ShowOperationContent::CreateFile { content, mode } => {
json.push_str("{\"kind\": \"create-file\", \"content\": ");
push_blob_content(json, content);
json.push_str(&format!(", \"mode\": {mode}}}"));
}
ShowOperationContent::DeleteNode { preimage } => {
json.push_str("{\"kind\": \"delete-node\", \"preimage\": ");
match preimage {
ShowDeletePreimage::File(content) => {
json.push_str("{\"kind\": \"file\", \"content\": ");
push_blob_content(json, content);
json.push('}');
}
ShowDeletePreimage::Symlink { old_target } => {
json.push_str("{\"kind\": \"symlink\", \"old_target\": ");
json.push_str(&escape_json_string(old_target));
json.push('}');
}
}
json.push('}');
}
ShowOperationContent::EditText {
old_span_text,
replacement_text,
} => {
json.push_str("{\"kind\": \"edit-text\", \"old_span_text\": ");
json.push_str(&escape_json_string(&String::from_utf8_lossy(old_span_text)));
json.push_str(", \"replacement_text\": ");
json.push_str(&escape_json_string(&String::from_utf8_lossy(
replacement_text,
)));
json.push('}');
}
ShowOperationContent::ReplaceBinary { old, new } => {
json.push_str("{\"kind\": \"replace-binary\", \"old\": ");
push_blob_content(json, old);
json.push_str(", \"new\": ");
push_blob_content(json, new);
json.push('}');
}
ShowOperationContent::RenamePath { author_key_id } => {
json.push_str("{\"kind\": \"rename-path\", \"author_key_id\": ");
json.push_str(&escape_json_string(author_key_id));
json.push('}');
}
ShowOperationContent::ChangePerm { old_mode, new_mode } => {
json.push_str(&format!(
"{{\"kind\": \"change-perm\", \"old_mode\": {old_mode}, \"new_mode\": {new_mode}}}"
));
}
ShowOperationContent::CreateSymlink { target } => {
json.push_str("{\"kind\": \"create-symlink\", \"target\": ");
json.push_str(&escape_json_string(target));
json.push('}');
}
}
json.push('}');
}
pub(crate) fn print_show_json(patches: &[ShowPatch]) {
let mut json = String::new();
json.push_str("{\n");
json.push_str(" \"schema_version\": \"show-report-v1\",\n");
json.push_str(" \"patches\": [");
for (index, patch) in patches.iter().enumerate() {
if index > 0 {
json.push(',');
}
json.push_str("\n {\"patch_id\": ");
json.push_str(&escape_json_string(&patch.patch_id.to_string()));
json.push_str(", \"operations\": [");
for (op_index, operation) in patch.operations.iter().enumerate() {
if op_index > 0 {
json.push(',');
}
json.push_str("\n ");
push_operation(&mut json, operation);
}
if !patch.operations.is_empty() {
json.push_str("\n ");
}
json.push_str("]}");
}
if !patches.is_empty() {
json.push_str("\n ");
}
json.push_str("]\n");
json.push('}');
println!("{json}");
}