cargo_release/ops/
replace.rs1use std::collections::BTreeMap;
2use std::path::Path;
3
4use crate::config::Replace;
5use crate::error::CargoResult;
6
7pub static NOW: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| {
8 time::OffsetDateTime::now_utc()
9 .format(time::macros::format_description!("[year]-[month]-[day]"))
10 .unwrap()
11});
12
13#[derive(Clone, Default, Debug)]
14pub struct Template<'a> {
15 pub prev_version: Option<&'a str>,
16 pub prev_metadata: Option<&'a str>,
17 pub version: Option<&'a str>,
18 pub metadata: Option<&'a str>,
19 pub crate_name: Option<&'a str>,
20 pub repository: Option<&'a str>,
21 pub date: Option<&'a str>,
22
23 pub prefix: Option<&'a str>,
24 pub tag_name: Option<&'a str>,
25}
26
27impl Template<'_> {
28 pub fn render(&self, input: &str) -> String {
29 const PREV_VERSION: &str = "{{prev_version}}";
30 const PREV_METADATA: &str = "{{prev_metadata}}";
31 const VERSION: &str = "{{version}}";
32 const METADATA: &str = "{{metadata}}";
33 const CRATE_NAME: &str = "{{crate_name}}";
34 const REPOSITORY: &str = "{{repository}}";
35 const DATE: &str = "{{date}}";
36
37 const PREFIX: &str = "{{prefix}}";
38 const TAG_NAME: &str = "{{tag_name}}";
39
40 let mut s = input.to_owned();
41 s = render_var(s, PREV_VERSION, self.prev_version);
42 s = render_var(s, PREV_METADATA, self.prev_metadata);
43 s = render_var(s, VERSION, self.version);
44 s = render_var(s, METADATA, self.metadata);
45 s = render_var(s, CRATE_NAME, self.crate_name);
46 s = render_var(s, REPOSITORY, self.repository);
47 s = render_var(s, DATE, self.date);
48
49 s = render_var(s, PREFIX, self.prefix);
50 s = render_var(s, TAG_NAME, self.tag_name);
51 s
52 }
53}
54
55fn render_var(mut template: String, var_name: &str, var_value: Option<&str>) -> String {
56 if let Some(var_value) = var_value {
57 template = template.replace(var_name, var_value);
58 } else if template.contains(var_name) {
59 log::warn!("Unrendered {var_name} present in template {template:?}");
60 }
61 template
62}
63
64pub fn do_file_replacements(
65 replace_config: &[Replace],
66 template: &Template<'_>,
67 cwd: &Path,
68 prerelease: bool,
69 noisy: bool,
70 dry_run: bool,
71) -> CargoResult<bool> {
72 let mut by_file = BTreeMap::new();
74 for replace in replace_config {
75 let file = replace.file.clone();
76 by_file.entry(file).or_insert_with(Vec::new).push(replace);
77 }
78
79 for (path, replaces) in by_file {
80 let file = cwd.join(&path);
81 log::debug!("processing replacements for file {}", file.display());
82 if !file.exists() {
83 anyhow::bail!("unable to find file {} to perform replace", file.display());
84 }
85 let data = std::fs::read_to_string(&file)?;
86 let mut replaced = data.clone();
87
88 for replace in replaces {
89 if prerelease && !replace.prerelease {
90 log::debug!("pre-release, not replacing {}", replace.search);
91 continue;
92 }
93
94 let pattern = replace.search.as_str();
95 let r = regex::RegexBuilder::new(pattern).multi_line(true).build()?;
96
97 let min = replace.min.or(replace.exactly).unwrap_or(1);
98 let max = replace.max.or(replace.exactly).unwrap_or(usize::MAX);
99 let actual = r.find_iter(&replaced).count();
100 if actual < min {
101 anyhow::bail!(
102 "for `{}` in '{}', at least {} replacements expected, found {}",
103 pattern,
104 path.display(),
105 min,
106 actual
107 );
108 } else if max < actual {
109 anyhow::bail!(
110 "for `{}` in '{}', at most {} replacements expected, found {}",
111 pattern,
112 path.display(),
113 max,
114 actual
115 );
116 }
117
118 let to_replace = replace.replace.as_str();
119 let replacer = template.render(to_replace);
120
121 replaced = r.replace_all(&replaced, replacer.as_str()).into_owned();
122 }
123
124 if data != replaced {
125 if dry_run {
126 if noisy {
127 let _ = crate::ops::shell::status(
128 "Replacing",
129 format!(
130 "in {}\n{}",
131 path.display(),
132 crate::ops::diff::unified_diff(&data, &replaced, &path, "replaced")
133 ),
134 );
135 } else {
136 let _ =
137 crate::ops::shell::status("Replacing", format!("in {}", path.display()));
138 }
139 } else {
140 std::fs::write(&file, replaced)?;
141 }
142 } else {
143 log::trace!("{} is unchanged", file.display());
144 }
145 }
146 Ok(true)
147}