use crate::manifest::{Manifest, PatchKind};
pub fn render(manifest: &Manifest) -> String {
let mut output = format!(
"# Downstream Patches\n\n> Generated by `forkctl`; edit the manifest, not this file.\n\nBase: `{}` (`{}`)\n\n| Order | Patch | Kind | Purpose | Upstream status | Drop condition |\n|--:|:--|:--|:--|:--|:--|\n",
escape(&manifest.base.label),
manifest.base.stack
);
for (index, patch) in manifest.patches.iter().enumerate() {
let kind = match patch.kind {
PatchKind::Source => "source",
PatchKind::Tooling => "tooling",
};
output.push_str("| ");
output.push_str(&(index + 1).to_string());
output.push_str(" | `");
output.push_str(&escape(&patch.name));
output.push_str("` | ");
output.push_str(kind);
output.push_str(" | ");
output.push_str(&escape(&patch.purpose));
output.push_str(" | ");
output.push_str(&escape(&patch.upstream_status));
output.push_str(" | ");
output.push_str(&escape(&patch.drop_when));
output.push_str(" |\n");
}
output
}
fn escape(value: &str) -> String {
value
.replace('\\', "\\\\")
.replace('|', "\\|")
.replace('`', "\\`")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manifest::{Allow, Base, Downstream, Patch, Upstream};
#[test]
fn renders_stable_escaped_table() {
let manifest = Manifest {
schema: 1,
downstream: Downstream {
remote: "origin".into(),
branch: "main".into(),
backup_tag_prefix: "vsh/pre-sync".into(),
},
upstream: Upstream {
remote: "upstream".into(),
url: "https://example.com/upstream.git".into(),
fetch_ref: "refs/heads/main".into(),
},
base: Base {
label: "refs/tags/v1".into(),
canonical: "0".repeat(40),
stack: "0".repeat(40),
},
ledger: "PATCHES.md".into(),
bookkeeping_patch: "fork-tooling".into(),
patches: vec![Patch {
name: "fork-tooling".into(),
kind: PatchKind::Tooling,
purpose: "Own A | B.".into(),
upstream_status: "downstream-only".into(),
drop_when: "The fork is retired.".into(),
paths: vec!["fork.json".into(), "PATCHES.md".into()],
export: None,
}],
allow: Allow::default(),
required: Vec::new(),
};
let first = render(&manifest);
assert_eq!(first, render(&manifest));
assert!(first.contains("Own A \\| B."));
assert!(first.ends_with('\n'));
}
}