Skip to content

Commit e3429e2

Browse files
Add new rename-from field to ensure checks when source URL file differs from name
1 parent 5e2683e commit e3429e2

File tree

2 files changed

+113
-24
lines changed

2 files changed

+113
-24
lines changed

src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,21 @@ async fn add_file(args: AddFileArgs) -> anyhow::Result<()> {
167167
.append(true)
168168
.open(&args.toml_file)?;
169169

170+
let rename_from = if let Some(file_name) = args.url.path().split('/').last()
171+
&& let Some(path_name) = args.path.split('/').last()
172+
&& file_name != path_name
173+
{
174+
Some(file_name.to_string())
175+
} else {
176+
None
177+
};
178+
170179
let entry = ManifestFileManaged::new(
171180
args.path,
172181
to_hex(&hash),
173182
args.url,
174183
args.license.unwrap_or(String::new()),
184+
rename_from,
175185
);
176186
let entry = toml::to_string(&entry)?;
177187

src/manifest.rs

Lines changed: 103 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,44 +44,106 @@ struct LocationCache {
4444
pub(crate) fn load_manifests(load_from: &Path) -> Result<(Vec<MirrorFile>, Vec<String>), Error> {
4545
let mut result = Vec::new();
4646
let mut cache = LocationCache::default();
47+
let mut errors = Vec::new();
4748

4849
fn load_inner(
4950
load_from: &Path,
5051
result: &mut Vec<MirrorFile>,
5152
cache: &mut LocationCache,
53+
errors: &mut Vec<String>,
5254
) -> anyhow::Result<()> {
5355
for entry in load_from.read_dir()? {
5456
let path = entry?.path();
5557
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("toml") {
56-
let manifest = std::fs::read_to_string(&path)
58+
let file_source = std::fs::read_to_string(&path)
59+
.map_err(Error::from)
60+
.with_context(|| format!("failed to read {}", path.display()))?;
61+
let manifest = toml::from_str::<Manifest>(&file_source)
5762
.map_err(Error::from)
58-
.and_then(|raw| toml::from_str::<Manifest>(&raw).map_err(Error::from))
5963
.with_context(|| format!("failed to read {}", path.display()))?;
6064
record_locations(&path, &manifest, cache);
6165

6266
for file in manifest.files {
63-
result.push(match file.into_inner() {
67+
let mirror_file = match file.into_inner() {
6468
ManifestFile::Legacy(legacy) => MirrorFile {
6569
name: legacy.name,
6670
sha256: legacy.sha256,
6771
source: Source::Legacy,
72+
rename_from: None,
6873
},
6974
ManifestFile::Managed(managed) => MirrorFile {
7075
name: managed.name,
7176
sha256: managed.sha256,
7277
source: Source::Url(managed.source),
78+
rename_from: managed.rename_from,
7379
},
74-
});
80+
};
81+
if let Source::Url(ref source) = mirror_file.source {
82+
if let Some(file_name) = source.path().split('/').last()
83+
&& let Some(path_name) = mirror_file.name.split('/').last()
84+
{
85+
match mirror_file.rename_from {
86+
Some(ref rename_from) => {
87+
if path_name == file_name {
88+
let location = cache
89+
.seen_paths
90+
.get(&mirror_file.name)
91+
.unwrap()
92+
.first()
93+
.unwrap();
94+
let (src_line, snippet) = span_info(&file_source, location);
95+
errors.push(format!(
96+
"`rename-from` field isn't needed since `source` and `name` field have the same file name (`{file_name}`):\n\
97+
# {} (line {src_line})\n{snippet}\n",
98+
location.file.display()
99+
));
100+
} else if path_name != file_name && rename_from != file_name {
101+
let location = cache
102+
.seen_paths
103+
.get(&mirror_file.name)
104+
.unwrap()
105+
.first()
106+
.unwrap();
107+
let (src_line, snippet) = span_info(&file_source, location);
108+
errors.push(format!(
109+
"`rename-from` field value doesn't match name from the URL `{source}` (`{file_name}` != `{rename_from}`):\n\
110+
# {} (line {src_line})\n{snippet}\n",
111+
location.file.display()
112+
));
113+
}
114+
}
115+
None => {
116+
if path_name != file_name {
117+
let location = cache
118+
.seen_paths
119+
.get(&mirror_file.name)
120+
.unwrap()
121+
.first()
122+
.unwrap();
123+
let (src_line, snippet) = span_info(&file_source, location);
124+
errors.push(format!(
125+
"The name from the URL `{source}` doesn't match the `name` field (`{file_name}` != `{path_name}`). \
126+
Add `rename-from = {file_name:?}` to fix this error:\n\
127+
# {} (line {src_line})\n{snippet}\n",
128+
location.file.display()
129+
));
130+
}
131+
}
132+
}
133+
}
134+
}
135+
result.push(mirror_file);
75136
}
76137
} else if path.is_dir() {
77-
load_inner(&path, result, cache)?;
138+
load_inner(&path, result, cache, errors)?;
78139
}
79140
}
80141
Ok(())
81142
}
82143

83-
load_inner(load_from, &mut result, &mut cache)?;
84-
Ok((result, find_errors(cache)))
144+
load_inner(load_from, &mut result, &mut cache, &mut errors)?;
145+
find_errors(cache, &mut errors);
146+
Ok((result, errors))
85147
}
86148

87149
fn record_locations(toml_path: &Path, manifest: &Manifest, cache: &mut LocationCache) {
@@ -114,12 +176,32 @@ fn record_locations(toml_path: &Path, manifest: &Manifest, cache: &mut LocationC
114176
.or_default()
115177
.insert(location.clone());
116178
if let Some(url) = url {
117-
cache.seen_urls.entry(url).or_default().insert(location);
179+
cache
180+
.seen_urls
181+
.entry(url)
182+
.or_default()
183+
.insert(location.clone());
184+
}
185+
}
186+
}
187+
188+
fn span_info<'a>(content: &'a str, location: &Location) -> (usize, &'a str) {
189+
// Find the corresponding line number
190+
let mut accumulated_chars = 0;
191+
let mut src_line = 0;
192+
for (index, line) in content.lines().enumerate() {
193+
accumulated_chars += line.len() + 1; // +1 for newline
194+
if accumulated_chars > location.span.0.start {
195+
src_line = index + 1;
196+
break;
118197
}
119198
}
199+
200+
let snippet = &content[location.span.0.start..location.span.0.end];
201+
(src_line, snippet)
120202
}
121203

122-
fn find_errors(cache: LocationCache) -> Vec<String> {
204+
fn find_errors(cache: LocationCache, errors: &mut Vec<String>) {
123205
let mut file_cache: HashMap<PathBuf, String> = HashMap::new();
124206

125207
fn format_locations(
@@ -136,18 +218,7 @@ fn find_errors(cache: LocationCache) -> Vec<String> {
136218
})
137219
});
138220

139-
// Find the corresponding line number
140-
let mut accumulated_chars = 0;
141-
let mut src_line = 0;
142-
for (index, line) in content.lines().enumerate() {
143-
accumulated_chars += line.len() + 1; // +1 for newline
144-
if accumulated_chars > location.span.0.start {
145-
src_line = index + 1;
146-
break;
147-
}
148-
}
149-
150-
let snippet = &content[location.span.0.start..location.span.0.end];
221+
let (src_line, snippet) = span_info(&content, location);
151222
writeln!(
152223
output,
153224
"# {} (line {src_line})\n{snippet}\n",
@@ -159,7 +230,6 @@ fn find_errors(cache: LocationCache) -> Vec<String> {
159230
output
160231
}
161232

162-
let mut errors = Vec::new();
163233
for (path, locations) in cache.seen_paths {
164234
if locations.len() > 1 {
165235
errors.push(format!(
@@ -184,13 +254,13 @@ fn find_errors(cache: LocationCache) -> Vec<String> {
184254
));
185255
}
186256
}
187-
errors
188257
}
189258

190259
pub(crate) struct MirrorFile {
191260
pub(crate) name: String,
192261
pub(crate) sha256: String,
193262
pub(crate) source: Source,
263+
pub(crate) rename_from: Option<String>,
194264
}
195265

196266
pub(crate) enum Source {
@@ -233,15 +303,24 @@ pub struct ManifestFileManaged {
233303
// This field is not considered at all by the automation, we just enforce its presence so that
234304
// people adding new entries think about the licensing implications.
235305
license: String,
306+
#[serde(default, rename = "rename-from")]
307+
rename_from: Option<String>,
236308
}
237309

238310
impl ManifestFileManaged {
239-
pub fn new(name: String, sha256: String, source: Url, license: String) -> Self {
311+
pub fn new(
312+
name: String,
313+
sha256: String,
314+
source: Url,
315+
license: String,
316+
rename_from: Option<String>,
317+
) -> Self {
240318
Self {
241319
name,
242320
sha256,
243321
source,
244322
license,
323+
rename_from,
245324
}
246325
}
247326
}

0 commit comments

Comments
 (0)