Skip to content
This repository was archived by the owner on May 20, 2020. It is now read-only.

Commit 7b1f532

Browse files
authored
Merge pull request #111 from euclio/infer-target
Infer target to document
2 parents 57b008d + e7e2c22 commit 7b1f532

File tree

3 files changed

+205
-69
lines changed

3 files changed

+205
-69
lines changed

src/cargo.rs

Lines changed: 196 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,54 @@ use serde_json;
77

88
use error::*;
99

10-
/// Invoke cargo to generate the save-analysis data for the crate being documented.
10+
/// The kinds of targets that we can document.
11+
#[derive(Debug, PartialEq, Eq)]
12+
pub enum TargetKind {
13+
/// A `bin` target.
14+
Binary,
15+
16+
/// A `lib` target.
17+
Library,
18+
}
19+
20+
/// A target of documentation.
21+
#[derive(Debug, PartialEq, Eq)]
22+
pub struct Target {
23+
/// The kind of the target.
24+
pub kind: TargetKind,
25+
26+
/// The name of the target.
27+
///
28+
/// This is *not* the name of the target's crate, which is used to retrieve the analysis data.
29+
/// Use the [`crate_name`] method instead.
30+
///
31+
/// [`crate_name`]: ./struct.Target.html#method.crate_name
32+
pub name: String,
33+
}
34+
35+
impl Target {
36+
/// Returns the name of the target's crate.
37+
///
38+
/// This name is equivalent to the target's name, with dashes replaced by underscores.
39+
pub fn crate_name(&self) -> String {
40+
self.name.replace('-', "_")
41+
}
42+
}
43+
44+
/// Generate and parse the metadata of a cargo project.
1145
///
1246
/// ## Arguments
1347
///
1448
/// - `manifest_path`: The path containing the `Cargo.toml` of the crate
15-
pub fn generate_analysis(manifest_path: &Path) -> Result<()> {
16-
// FIXME: Here we assume that we are documenting a library. This could be wrong, but it's the
17-
// common case, and it ensures that we are documenting the right target in the case that the
18-
// crate contains a binary and a library with the same name.
19-
//
20-
// Maybe we could use Cargo.toml's `doc = false` attribute to figure out the right target?
21-
let mut command = Command::new("cargo");
22-
command
23-
.arg("check")
24-
.arg("--lib")
49+
pub fn retrieve_metadata(manifest_path: &Path) -> Result<serde_json::Value> {
50+
let output = Command::new("cargo")
51+
.arg("metadata")
2552
.arg("--manifest-path")
2653
.arg(manifest_path.join("Cargo.toml"))
27-
.env("RUSTFLAGS", "-Z save-analysis")
28-
.env("CARGO_TARGET_DIR", manifest_path.join("target/rls"));
29-
30-
let output = command.output()?;
54+
.arg("--no-deps")
55+
.arg("--format-version")
56+
.arg("1")
57+
.output()?;
3158

3259
if !output.status.success() {
3360
return Err(
@@ -38,24 +65,33 @@ pub fn generate_analysis(manifest_path: &Path) -> Result<()> {
3865
);
3966
}
4067

41-
Ok(())
68+
Ok(serde_json::from_slice(&output.stdout)?)
4269
}
4370

44-
/// Grab the name of the binary or library from it's `Cargo.toml` file.
71+
/// Invoke cargo to generate the save-analysis data for the crate being documented.
4572
///
4673
/// ## Arguments
4774
///
48-
/// - `manifest_path`: The path to the location of `Cargo.toml` of the crate being documented
49-
pub fn crate_name_from_manifest_path(manifest_path: &Path) -> Result<String> {
75+
/// - `manifest_path`: The path containing the `Cargo.toml` of the crate
76+
/// - `target`: The target that we should generate the analysis data for
77+
pub fn generate_analysis(manifest_path: &Path, target: &Target) -> Result<()> {
5078
let mut command = Command::new("cargo");
5179

5280
command
53-
.arg("metadata")
81+
.arg("check")
5482
.arg("--manifest-path")
5583
.arg(manifest_path.join("Cargo.toml"))
56-
.arg("--no-deps")
57-
.arg("--format-version")
58-
.arg("1");
84+
.env("RUSTFLAGS", "-Z save-analysis")
85+
.env("CARGO_TARGET_DIR", manifest_path.join("target/rls"));
86+
87+
match target.kind {
88+
TargetKind::Library => {
89+
command.arg("--lib");
90+
}
91+
TargetKind::Binary => {
92+
command.args(&["--bin", &target.name]);
93+
}
94+
}
5995

6096
let output = command.output()?;
6197

@@ -68,83 +104,181 @@ pub fn crate_name_from_manifest_path(manifest_path: &Path) -> Result<String> {
68104
);
69105
}
70106

71-
let metadata = serde_json::from_slice(&output.stdout)?;
72-
crate_name_from_metadata(&metadata)
107+
Ok(())
73108
}
74109

75-
/// Parse the crate name of the binary or library from crate metadata.
110+
/// Parse the library target from the crate metadata.
76111
///
77112
/// ## Arguments
78113
///
79-
/// - `metadata`: The JSON metadata of the crate.
80-
fn crate_name_from_metadata(metadata: &serde_json::Value) -> Result<String> {
81-
let targets = match metadata["packages"][0]["targets"].as_array() {
82-
Some(targets) => targets,
83-
None => return Err(ErrorKind::Json("targets is not an array").into()),
84-
};
85-
86-
for target in targets {
87-
let crate_types = match target["crate_types"].as_array() {
88-
Some(crate_types) => crate_types,
89-
None => return Err(ErrorKind::Json("crate types is not an array").into()),
90-
};
91-
92-
for crate_type in crate_types {
93-
let ty = match crate_type.as_str() {
94-
Some(t) => t,
95-
None => {
96-
return Err(
97-
ErrorKind::Json("crate type contents are not a string").into(),
98-
)
99-
}
100-
};
114+
/// - metadata: The JSON metadata of the crate.
115+
pub fn target_from_metadata(metadata: &serde_json::Value) -> Result<Target> {
116+
// We can expect at least one package and target, otherwise the metadata generation would have
117+
// failed.
118+
let targets = metadata["packages"][0]["targets"].as_array().expect(
119+
"`targets` is not an array",
120+
);
121+
122+
let mut targets = targets
123+
.into_iter()
124+
.flat_map(|target| {
125+
let name = target["name"].as_str().expect("`name` is not a string");
126+
let kinds = target["kind"].as_array().expect("`kind` is not an array");
101127

102-
if ty == "lib" {
103-
match target["name"].as_str() {
104-
Some(name) => return Ok(name.replace('-', "_")),
105-
None => return Err(ErrorKind::Json("target name is not a string").into()),
106-
}
128+
if kinds.len() != 1 {
129+
return Some(Err(
130+
ErrorKind::Json(
131+
format!("expected one kind for target '{}'", name),
132+
).into(),
133+
));
107134
}
135+
136+
let kind = match kinds[0].as_str().unwrap() {
137+
"lib" => TargetKind::Library,
138+
"bin" => TargetKind::Binary,
139+
_ => return None,
140+
};
141+
142+
let target = Target {
143+
name: name.to_owned(),
144+
kind,
145+
};
146+
147+
Some(Ok(target))
148+
})
149+
.collect::<Result<Vec<_>>>()?;
150+
151+
if targets.is_empty() {
152+
bail!(ErrorKind::Json(
153+
"no targets with supported kinds (`bin`, `lib`) found"
154+
.into(),
155+
));
156+
} else if targets.len() == 1 {
157+
Ok(targets.remove(0))
158+
} else {
159+
// FIXME(#105): Handle more than one target.
160+
print!("warning: Found more than one target to document. ");
161+
let (mut libs, mut bins): (Vec<_>, Vec<_>) =
162+
targets.into_iter().partition(|target| match target.kind {
163+
TargetKind::Library => true,
164+
TargetKind::Binary => false,
165+
});
166+
167+
if !libs.is_empty() {
168+
println!("Documenting the library.");
169+
Ok(libs.remove(0))
170+
} else {
171+
let target = bins.remove(0);
172+
println!("Documenting the first binary: {}", target.name);
173+
Ok(target)
108174
}
109175
}
110-
111-
Err(
112-
ErrorKind::Json("cargo metadata contained no targets").into(),
113-
)
114176
}
115177

116178
#[cfg(test)]
117179
mod tests {
180+
use super::{Target, TargetKind};
181+
118182
#[test]
119-
fn crate_name_from_metadata() {
183+
fn target_from_metadata() {
120184
let metadata = json!({
121185
"packages": [
122186
{
123187
"name": "underscored_name",
124188
"targets": [
125189
{
126-
"crate_types": [ "lib" ],
190+
"kind": [ "lib" ],
127191
"name": "underscored_name",
128192
},
129193
],
130194
},
131195
],
132196
});
133-
assert_eq!(&super::crate_name_from_metadata(&metadata).unwrap(), "underscored_name");
197+
let target = super::target_from_metadata(&metadata).unwrap();
198+
assert_eq!(target, Target { kind: TargetKind::Library, name: "underscored_name".into() });
199+
assert_eq!(&target.crate_name(), "underscored_name");
134200

135201
let metadata = json!({
136202
"packages": [
137203
{
138204
"name": "dashed-name",
139205
"targets": [
140206
{
141-
"crate_types": [ "lib" ],
207+
"kind": [ "lib" ],
142208
"name": "dashed-name",
143209
},
144210
],
145211
},
146212
],
147213
});
148-
assert_eq!(&super::crate_name_from_metadata(&metadata).unwrap(), "dashed_name");
214+
let target = super::target_from_metadata(&metadata).unwrap();
215+
assert_eq!(target, Target { kind: TargetKind::Library, name: "dashed-name".into() });
216+
assert_eq!(&target.crate_name(), "dashed_name");
217+
218+
let metadata = json!({
219+
"packages": [
220+
{
221+
"name": "underscored_name",
222+
"targets": [
223+
{
224+
"kind": [ "bin" ],
225+
"name": "underscored_name",
226+
},
227+
],
228+
},
229+
],
230+
});
231+
let target = super::target_from_metadata(&metadata).unwrap();
232+
assert_eq!(target, Target { kind: TargetKind::Binary, name: "underscored_name".into() });
233+
assert_eq!(&target.crate_name(), "underscored_name");
234+
235+
let metadata = json!({
236+
"packages": [
237+
{
238+
"name": "library",
239+
"targets": [
240+
{
241+
"kind": [ "lib" ],
242+
"name": "library",
243+
},
244+
],
245+
},
246+
],
247+
});
248+
assert_eq!(super::target_from_metadata(&metadata).unwrap().kind, TargetKind::Library);
249+
250+
let metadata = json!({
251+
"packages": [
252+
{
253+
"name": "binary",
254+
"targets": [
255+
{
256+
"kind": [ "bin" ],
257+
"name": "binary",
258+
},
259+
],
260+
},
261+
],
262+
});
263+
assert_eq!(super::target_from_metadata(&metadata).unwrap().kind, TargetKind::Binary);
264+
265+
let metadata = json!({
266+
"packages": [
267+
{
268+
"name": "library",
269+
"targets": [
270+
{
271+
"kind": [ "lib" ],
272+
"name": "library",
273+
},
274+
{
275+
"kind": [ "test" ],
276+
"name": "other_kind",
277+
},
278+
],
279+
},
280+
],
281+
});
282+
assert_eq!(super::target_from_metadata(&metadata).unwrap().kind, TargetKind::Library);
149283
}
150284
}

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ error_chain! {
1616

1717
/// Thrown whenever the `JSON` grabbed from somewhere else is not what is expected.
1818
/// This is usually thrown when grabbing data output from `Cargo`
19-
Json(location: &'static str) {
19+
Json(location: String) {
2020
description("Unexpected JSON response")
2121
display("Unexpected JSON response from {}", location)
2222
}

src/lib.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use indicatif::ProgressBar;
3030
use rayon::prelude::*;
3131

3232
use assets::Asset;
33+
use cargo::Target;
3334
use error::*;
3435
use json::*;
3536

@@ -74,9 +75,9 @@ impl Config {
7475
/// - `config`: The `Config` struct that contains the data needed to generate the documentation
7576
/// - `artifacts`: A slice containing what assets should be output at the end
7677
pub fn build(config: &Config, artifacts: &[&str]) -> Result<()> {
77-
generate_and_load_analysis(config)?;
78-
79-
let crate_name = cargo::crate_name_from_manifest_path(&config.manifest_path)?;
78+
let metadata = cargo::retrieve_metadata(&config.manifest_path)?;
79+
let target = cargo::target_from_metadata(&metadata)?;
80+
generate_and_load_analysis(config, &target)?;
8081

8182
let output_path = config.manifest_path.join("target/doc");
8283
fs::create_dir_all(&output_path)?;
@@ -86,7 +87,7 @@ pub fn build(config: &Config, artifacts: &[&str]) -> Result<()> {
8687
spinner.enable_steady_tick(50);
8788
spinner.set_message("Generating JSON: In Progress");
8889

89-
let json = create_json(&config.host, &crate_name)?;
90+
let json = create_json(&config.host, &target.crate_name())?;
9091

9192
let mut json_path = output_path.clone();
9293
json_path.push("data.json");
@@ -123,14 +124,15 @@ pub fn build(config: &Config, artifacts: &[&str]) -> Result<()> {
123124
///
124125
/// - `config`: Contains data for what needs to be output or used. In this case the path to the
125126
/// `Cargo.toml` file
126-
fn generate_and_load_analysis(config: &Config) -> Result<()> {
127+
/// - `target`: The target to document
128+
fn generate_and_load_analysis(config: &Config, target: &Target) -> Result<()> {
127129
let manifest_path = &config.manifest_path;
128130

129131
let spinner = ProgressBar::new_spinner();
130132
spinner.enable_steady_tick(50);
131133
spinner.set_message("Generating save analysis data: In Progress");
132134

133-
if let Err(e) = cargo::generate_analysis(manifest_path) {
135+
if let Err(e) = cargo::generate_analysis(manifest_path, target) {
134136
spinner.finish_with_message("Generating save analysis data: Error");
135137
return Err(e);
136138
}

0 commit comments

Comments
 (0)