Skip to content

Commit 1ec223e

Browse files
committed
Auto merge of #8277 - tverghis:default-readme, r=alexcrichton
Default values for `readme` if not specified If the a value for `readme` is not specified in Cargo.toml, we will now check for the existence of files named `README.md`, `README.txt` or `README`. If one does exist, the name of that file will be defaulted in to the manifest for the project. This behavior can be suppressed if `readme` is set to `false`. Closes #8133
2 parents 5eb53f7 + 58ee013 commit 1ec223e

File tree

4 files changed

+159
-12
lines changed

4 files changed

+159
-12
lines changed

src/cargo/util/toml/mod.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ pub struct TomlProject {
808808
description: Option<String>,
809809
homepage: Option<String>,
810810
documentation: Option<String>,
811-
readme: Option<String>,
811+
readme: Option<StringOrBool>,
812812
keywords: Option<Vec<String>>,
813813
categories: Option<Vec<String>>,
814814
license: Option<String>,
@@ -1208,11 +1208,19 @@ impl TomlManifest {
12081208
project.links.as_deref(),
12091209
project.namespaced_features.unwrap_or(false),
12101210
)?;
1211+
1212+
let readme = readme_for_project(package_root, project);
1213+
if let Some(ref r) = readme {
1214+
if !package_root.join(r).is_file() {
1215+
bail!("readme file with name '{}' was not found", r);
1216+
}
1217+
};
1218+
12111219
let metadata = ManifestMetadata {
12121220
description: project.description.clone(),
12131221
homepage: project.homepage.clone(),
12141222
documentation: project.documentation.clone(),
1215-
readme: project.readme.clone(),
1223+
readme,
12161224
authors: project.authors.clone().unwrap_or_default(),
12171225
license: project.license.clone(),
12181226
license_file: project.license_file.clone(),
@@ -1523,6 +1531,32 @@ impl TomlManifest {
15231531
}
15241532
}
15251533

1534+
/// Returns the name of the README file for a `TomlProject`.
1535+
fn readme_for_project(package_root: &Path, project: &TomlProject) -> Option<String> {
1536+
match &project.readme {
1537+
None => default_readme_from_package_root(package_root),
1538+
Some(value) => match value {
1539+
StringOrBool::Bool(false) => None,
1540+
StringOrBool::Bool(true) => Some("README.md".to_string()),
1541+
StringOrBool::String(v) => Some(v.clone()),
1542+
},
1543+
}
1544+
}
1545+
1546+
const DEFAULT_README_FILES: [&str; 3] = ["README.md", "README.txt", "README"];
1547+
1548+
/// Checks if a file with any of the default README file names exists in the package root.
1549+
/// If so, returns a `String` representing that name.
1550+
fn default_readme_from_package_root(package_root: &Path) -> Option<String> {
1551+
for &readme_filename in DEFAULT_README_FILES.iter() {
1552+
if package_root.join(readme_filename).is_file() {
1553+
return Some(readme_filename.to_string());
1554+
}
1555+
}
1556+
1557+
None
1558+
}
1559+
15261560
/// Checks a list of build targets, and ensures the target names are unique within a vector.
15271561
/// If not, the name of the offending build target is returned.
15281562
fn unique_build_targets(targets: &[Target], package_root: &Path) -> Result<(), String> {

src/doc/src/reference/manifest.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ will interpret it as Markdown and render it on the crate's page.
165165
readme = "README.md"
166166
```
167167

168+
If no value is specified for this field, and a file named `README.md`,
169+
`README.txt` or `README` exists in the package root, then the name of that
170+
file will be used. You can suppress this behavior by setting this field to
171+
`false`. If the field is set to `true`, a default value of `README.md` will
172+
be assumed.
173+
168174
#### The `homepage` field
169175

170176
The `homepage` field should be a URL to a site that is the home page for your

tests/testsuite/metadata.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,7 @@ fn package_metadata() {
11151115
baz = "quux"
11161116
"#,
11171117
)
1118+
.file("README.md", "")
11181119
.file("src/lib.rs", "")
11191120
.build();
11201121

@@ -1186,6 +1187,7 @@ fn package_publish() {
11861187
publish = ["my-registry"]
11871188
"#,
11881189
)
1190+
.file("README.md", "")
11891191
.file("src/lib.rs", "")
11901192
.build();
11911193

tests/testsuite/read_manifest.rs

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
33
use cargo_test_support::{basic_bin_manifest, main_file, project};
44

5-
static MANIFEST_OUTPUT: &str = r#"
6-
{
5+
fn manifest_output(readme_value: &str) -> String {
6+
format!(
7+
r#"
8+
{{
79
"authors": [
810
911
],
1012
"categories": [],
1113
"name":"foo",
12-
"readme": null,
14+
"readme": {},
1315
"repository": null,
1416
"version":"0.5.0",
1517
"id":"foo[..]0.5.0[..](path+file://[..]/foo)",
@@ -21,19 +23,44 @@ static MANIFEST_OUTPUT: &str = r#"
2123
"edition": "2015",
2224
"source":null,
2325
"dependencies":[],
24-
"targets":[{
26+
"targets":[{{
2527
"kind":["bin"],
2628
"crate_types":["bin"],
2729
"doctest": false,
2830
"edition": "2015",
2931
"name":"foo",
3032
"src_path":"[..]/foo/src/foo.rs"
31-
}],
32-
"features":{},
33+
}}],
34+
"features":{{}},
3335
"manifest_path":"[..]Cargo.toml",
3436
"metadata": null,
3537
"publish": null
36-
}"#;
38+
}}"#,
39+
readme_value
40+
)
41+
}
42+
43+
fn manifest_output_no_readme() -> String {
44+
manifest_output("null")
45+
}
46+
47+
pub fn basic_bin_manifest_with_readme(name: &str, readme_filename: &str) -> String {
48+
format!(
49+
r#"
50+
[package]
51+
52+
name = "{}"
53+
version = "0.5.0"
54+
authors = ["[email protected]"]
55+
readme = {}
56+
57+
[[bin]]
58+
59+
name = "{}"
60+
"#,
61+
name, readme_filename, name
62+
)
63+
}
3764

3865
#[cargo_test]
3966
fn cargo_read_manifest_path_to_cargo_toml_relative() {
@@ -44,7 +71,7 @@ fn cargo_read_manifest_path_to_cargo_toml_relative() {
4471

4572
p.cargo("read-manifest --manifest-path foo/Cargo.toml")
4673
.cwd(p.root().parent().unwrap())
47-
.with_json(MANIFEST_OUTPUT)
74+
.with_json(&manifest_output_no_readme())
4875
.run();
4976
}
5077

@@ -58,7 +85,7 @@ fn cargo_read_manifest_path_to_cargo_toml_absolute() {
5885
p.cargo("read-manifest --manifest-path")
5986
.arg(p.root().join("Cargo.toml"))
6087
.cwd(p.root().parent().unwrap())
61-
.with_json(MANIFEST_OUTPUT)
88+
.with_json(&manifest_output_no_readme())
6289
.run();
6390
}
6491

@@ -104,5 +131,83 @@ fn cargo_read_manifest_cwd() {
104131
.file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
105132
.build();
106133

107-
p.cargo("read-manifest").with_json(MANIFEST_OUTPUT).run();
134+
p.cargo("read-manifest")
135+
.with_json(&manifest_output_no_readme())
136+
.run();
137+
}
138+
139+
#[cargo_test]
140+
fn cargo_read_manifest_with_specified_readme() {
141+
let p = project()
142+
.file(
143+
"Cargo.toml",
144+
&basic_bin_manifest_with_readme("foo", r#""SomeReadme.txt""#),
145+
)
146+
.file("SomeReadme.txt", "Sample Project")
147+
.file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
148+
.build();
149+
150+
p.cargo("read-manifest")
151+
.with_json(&manifest_output(&format!(r#""{}""#, "SomeReadme.txt")))
152+
.run();
153+
}
154+
155+
#[cargo_test]
156+
fn cargo_read_manifest_default_readme() {
157+
let readme_filenames = ["README.md", "README.txt", "README"];
158+
159+
for readme in readme_filenames.iter() {
160+
let p = project()
161+
.file("Cargo.toml", &basic_bin_manifest("foo"))
162+
.file(readme, "Sample project")
163+
.file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
164+
.build();
165+
166+
p.cargo("read-manifest")
167+
.with_json(&manifest_output(&format!(r#""{}""#, readme)))
168+
.run();
169+
}
170+
}
171+
172+
#[cargo_test]
173+
fn cargo_read_manifest_suppress_default_readme() {
174+
let p = project()
175+
.file(
176+
"Cargo.toml",
177+
&basic_bin_manifest_with_readme("foo", "false"),
178+
)
179+
.file("README.txt", "Sample project")
180+
.file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
181+
.build();
182+
183+
p.cargo("read-manifest")
184+
.with_json(&manifest_output_no_readme())
185+
.run();
186+
}
187+
188+
// If a file named README.md exists, and `readme = true`, the value `README.md` should be defaulted in.
189+
#[cargo_test]
190+
fn cargo_read_manifest_defaults_readme_if_true() {
191+
let p = project()
192+
.file("Cargo.toml", &basic_bin_manifest_with_readme("foo", "true"))
193+
.file("README.md", "Sample project")
194+
.file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
195+
.build();
196+
197+
p.cargo("read-manifest")
198+
.with_json(&manifest_output(&format!(r#""{}""#, "README.md")))
199+
.run();
200+
}
201+
202+
// If a file named README.md does not exist, and `readme = true`, it should panic.
203+
#[cargo_test]
204+
#[should_panic]
205+
fn cargo_read_manifest_panics_if_default_readme_not_found() {
206+
let p = project()
207+
.file("Cargo.toml", &basic_bin_manifest_with_readme("foo", "true"))
208+
.file("README.txt", "Sample project")
209+
.file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
210+
.build();
211+
212+
p.cargo("read-manifest").run();
108213
}

0 commit comments

Comments
 (0)