use super::*;
pub(super) fn hash_edit_success_content(
results: &[SectionCommitResult],
prepared: &[PreparedSection],
) -> String {
let mut lines = vec![format!("applied {} hash_edit section(s)", results.len())];
for result in results {
lines.push(format!("{} {}", result.op, result.header));
}
let warnings = prepared
.iter()
.flat_map(|entry| entry.warnings.iter())
.map(format_warning)
.collect::<Vec<_>>();
if !warnings.is_empty() {
lines.push("warnings:".to_string());
lines.extend(
warnings
.into_iter()
.take(20)
.map(|warning| format!("- {warning}")),
);
}
let resolutions = prepared
.iter()
.flat_map(|entry| entry.block_resolutions.iter())
.collect::<Vec<_>>();
if !resolutions.is_empty() {
lines.push("block resolutions:".to_string());
lines.extend(resolutions.into_iter().take(20).map(|resolution| {
format!(
"- {:?} anchor {} -> {}..{}",
resolution.op, resolution.anchor_line, resolution.start, resolution.end
)
}));
}
for entry in prepared {
if let Some(line) = entry.first_changed_line {
lines.push(format!(
"first_changed_line {}: {}",
entry.source.display(),
line
));
}
}
lines.join("\n")
}
pub(super) fn line_stats(before: &str, after: &str, deleted: bool) -> (usize, usize) {
if deleted {
return (0, before.lines().count());
}
let diff = similar::TextDiff::from_lines(before, after);
diff.ops()
.iter()
.filter(|op| op.tag() != similar::DiffTag::Equal)
.fold((0, 0), |(added, removed), op| {
(added + op.new_range().len(), removed + op.old_range().len())
})
}
fn format_warning(warning: &ParseWarning) -> String {
match warning {
ParseWarning::BareBodyAutoPiped => {
"bare body rows were treated as payload lines".to_string()
}
ParseWarning::StrayDotSkipped { line_num } => {
format!("stray dot skipped at line {line_num}")
}
ParseWarning::ApplyRepair { message } => message.clone(),
}
}
pub(super) fn append_diff(
out: &mut String,
source: &Path,
dest: Option<&Path>,
before: &str,
after: &str,
) -> bool {
if before == after && dest.is_none() {
return false;
}
if !out.is_empty() {
out.push('\n');
}
let display = dest.unwrap_or(source);
out.push_str(&format!(
"diff --git a/{} b/{}\n--- a/{}\n+++ b/{}",
source.display(),
display.display(),
source.display(),
display.display()
));
if out.len() > HASH_EDIT_OUTPUT_MAX_BYTES {
return true;
}
let diff = similar::TextDiff::from_lines(before, after);
for op in diff.ops() {
if op.tag() == similar::DiffTag::Equal {
continue;
}
let old_start = if op.old_range().is_empty() {
op.old_range().start
} else {
op.old_range().start + 1
};
let new_start = if op.new_range().is_empty() {
op.new_range().start
} else {
op.new_range().start + 1
};
out.push_str(&format!(
"\n@@ -{},{old_len} +{},{new_len} @@",
old_start,
new_start,
old_len = op.old_range().len(),
new_len = op.new_range().len(),
));
for change in diff.iter_changes(op) {
let (prefix, value) = match change.tag() {
similar::ChangeTag::Delete => ('-', change.value()),
similar::ChangeTag::Insert => ('+', change.value()),
similar::ChangeTag::Equal => (' ', change.value()),
};
out.push('\n');
out.push(prefix);
out.push_str(value.trim_end_matches(['\r', '\n']));
if out.len() > HASH_EDIT_OUTPUT_MAX_BYTES {
return true;
}
}
}
false
}
pub(super) fn bounded_hash_edit_output(text: &str) -> String {
if text.len() <= HASH_EDIT_OUTPUT_MAX_BYTES {
return text.to_string();
}
let mut end = HASH_EDIT_OUTPUT_MAX_BYTES;
while !text.is_char_boundary(end) {
end -= 1;
}
format!(
"{}\n[truncated: hash_edit output exceeded {HASH_EDIT_OUTPUT_MAX_BYTES} bytes]",
&text[..end]
)
}