Skip to content

Commit b9e968b

Browse files
authored
Merge pull request #2019 from dorfsmay/open_doc_for_topic
Open doc for specific topic
2 parents cba68ab + c50626e commit b9e968b

File tree

7 files changed

+230
-8
lines changed

7 files changed

+230
-8
lines changed

src/cli/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod rustup_mode;
2727
mod self_update;
2828
mod setup_mode;
2929
mod term2;
30+
mod topical_doc;
3031

3132
use crate::errors::*;
3233
use rustup::env_var::RUST_RECURSION_COUNT_MAX;

src/cli/rustup_mode.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::help::*;
44
use crate::self_update;
55
use crate::term2;
66
use crate::term2::Terminal;
7+
use crate::topical_doc;
78
use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, Shell, SubCommand};
89
use rustup::dist::dist::{PartialTargetTriple, PartialToolchainDesc, Profile, TargetTriple};
910
use rustup::dist::manifest::Component;
@@ -510,7 +511,11 @@ pub fn cli() -> App<'static, 'static> {
510511
.map(|(name, _, _)| *name)
511512
.collect::<Vec<_>>(),
512513
),
513-
),
514+
)
515+
.arg(
516+
Arg::with_name("topic")
517+
.help("Topic such as 'core', 'fn', 'usize', 'eprintln!', 'core::arch', 'alloc::format!', 'std::fs', 'std::fs::read_dir', 'std::io::Bytes', 'std::iter::Sum', 'std::io::error::Result' etc..."),
518+
),
514519
);
515520

516521
if cfg!(not(target_os = "windows")) {
@@ -1240,13 +1245,16 @@ const DOCS_DATA: &[(&str, &str, &str,)] = &[
12401245

12411246
fn doc(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<()> {
12421247
let toolchain = explicit_or_dir_toolchain(cfg, m)?;
1248+
let topical_path: PathBuf;
12431249

1244-
let doc_url =
1245-
if let Some((_, _, path)) = DOCS_DATA.iter().find(|(name, _, _)| m.is_present(name)) {
1246-
path
1247-
} else {
1248-
"index.html"
1249-
};
1250+
let doc_url = if let Some(topic) = m.value_of("topic") {
1251+
topical_path = topical_doc::local_path(&toolchain.doc_path("").unwrap(), topic)?;
1252+
topical_path.to_str().unwrap()
1253+
} else if let Some((_, _, path)) = DOCS_DATA.iter().find(|(name, _, _)| m.is_present(name)) {
1254+
path
1255+
} else {
1256+
"index.html"
1257+
};
12501258

12511259
if m.is_present("path") {
12521260
let doc_path = toolchain.doc_path(doc_url)?;

src/cli/topical_doc.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use crate::errors::*;
2+
use std::ffi::OsString;
3+
use std::fs;
4+
use std::path::{Path, PathBuf};
5+
6+
struct DocData<'a> {
7+
topic: &'a str,
8+
subtopic: &'a str,
9+
root: &'a Path,
10+
}
11+
12+
fn no_document(topic: &str) -> Result<PathBuf> {
13+
Err(format!("No document for '{}'", topic).into())
14+
}
15+
16+
fn index_html(doc: &DocData, wpath: &Path) -> Option<PathBuf> {
17+
let indexhtml = wpath.join("index.html");
18+
match &doc.root.join(&indexhtml).exists() {
19+
true => Some(indexhtml),
20+
false => None,
21+
}
22+
}
23+
24+
fn dir_into_vec(dir: &PathBuf) -> Result<Vec<OsString>> {
25+
let entries = fs::read_dir(dir).chain_err(|| format!("Opening directory {:?}", dir))?;
26+
let mut v = Vec::new();
27+
for entry in entries {
28+
let entry = entry?;
29+
v.push(entry.file_name());
30+
}
31+
Ok(v)
32+
}
33+
34+
fn search_path(doc: &DocData, wpath: &Path, keywords: &[&str]) -> Result<PathBuf> {
35+
let dir = &doc.root.join(&wpath);
36+
if dir.is_dir() {
37+
let entries = dir_into_vec(dir)?;
38+
for k in keywords {
39+
let filename = &format!("{}.{}.html", k, doc.subtopic);
40+
if entries.contains(&OsString::from(filename)) {
41+
return Ok(dir.join(filename));
42+
}
43+
}
44+
no_document(doc.topic)
45+
} else {
46+
no_document(doc.topic)
47+
}
48+
}
49+
50+
pub fn local_path(root: &Path, topic: &str) -> Result<PathBuf> {
51+
let keywords_top = ["macro", "keyword", "primitive"];
52+
let keywords_mod = ["fn", "struct", "trait", "enum", "type", "constant"];
53+
54+
let topic_vec: Vec<&str> = topic.split("::").collect();
55+
let work_path = topic_vec.iter().fold(PathBuf::new(), |acc, e| acc.join(e));
56+
57+
let doc = DocData {
58+
topic: &topic,
59+
subtopic: topic_vec[topic_vec.len() - 1],
60+
root,
61+
};
62+
63+
/**************************
64+
* Please ensure tests/mock/topical_doc_data.rs is UPDATED to reflect
65+
* any change in functionality.
66+
67+
Argument File directory
68+
69+
# len() == 1 Return index.html
70+
std std/index.html root/std
71+
core core/index.html root/core
72+
alloc alloc/index.html root/core
73+
KKK std/keyword.KKK.html root/std
74+
PPP std/primitive.PPP.html root/std
75+
MMM std/macro.MMM.html root/std
76+
77+
78+
# len() == 2 not ending in ::
79+
MMM std/macro.MMM.html root/std
80+
KKK std/keyword.KKK.html root/std
81+
PPP std/primitive.PPP.html root/std
82+
MMM core/macro.MMM.html root/core
83+
MMM alloc/macro.MMM.html root/alloc
84+
# If above fail, try module
85+
std::module std/module/index.html root/std/module
86+
core::module core/module/index.html root/core/module
87+
alloc::module alloc/module/index.html alloc/core/module
88+
89+
# len() == 2, ending with ::
90+
std::module std/module/index.html root/std/module
91+
core::module core/module/index.html root/core/module
92+
alloc::module alloc/module/index.html alloc/core/module
93+
94+
# len() > 2
95+
# search for index.html in rel_path
96+
std::AAA::MMM std/AAA/MMM/index.html root/std/AAA/MMM
97+
98+
# OR check if parent() dir exists and search for fn/sturct/etc
99+
std::AAA::FFF std/AAA/fn.FFF9.html root/std/AAA
100+
std::AAA::SSS std/AAA/struct.SSS.html root/std/AAA
101+
core:AAA::SSS std/AAA/struct.SSS.html root/coreAAA
102+
alloc:AAA::SSS std/AAA/struct.SSS.html root/coreAAA
103+
std::AAA::TTT std/2222/trait.TTT.html root/std/AAA
104+
std::AAA::EEE std/2222/enum.EEE.html root/std/AAA
105+
std::AAA::TTT std/2222/type.TTT.html root/std/AAA
106+
std::AAA::CCC std/2222/constant.CCC.html root/std/AAA
107+
108+
**************************/
109+
110+
// topic.split.count cannot be 0
111+
let subpath_os_path = match topic_vec.len() {
112+
1 => match topic {
113+
"std" | "core" | "alloc" => match index_html(&doc, &work_path) {
114+
Some(f) => f,
115+
None => no_document(doc.topic)?,
116+
},
117+
_ => {
118+
let std = PathBuf::from("std");
119+
search_path(&doc, &std, &keywords_top)?
120+
}
121+
},
122+
2 => match index_html(&doc, &work_path) {
123+
Some(f) => f,
124+
None => {
125+
let parent = work_path.parent().unwrap();
126+
search_path(&doc, &parent, &keywords_top)?
127+
}
128+
},
129+
_ => match index_html(&doc, &work_path) {
130+
Some(f) => f,
131+
None => {
132+
// len > 2, guaranteed to have a parent, safe to unwrap
133+
let parent = work_path.parent().unwrap();
134+
search_path(&doc, &parent, &keywords_mod)?
135+
}
136+
},
137+
};
138+
// The path and filename were validated to be existing on the filesystem.
139+
// It should be safe to unwrap, or worth panicking.
140+
Ok(subpath_os_path)
141+
}

tests/cli-rustup.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,6 +1577,39 @@ fn docs_with_path() {
15771577
});
15781578
}
15791579

1580+
#[test]
1581+
fn docs_topical_with_path() {
1582+
setup(&|config| {
1583+
expect_ok(config, &["rustup", "default", "stable"]);
1584+
expect_ok(
1585+
config,
1586+
&[
1587+
"rustup",
1588+
"toolchain",
1589+
"install",
1590+
"nightly",
1591+
"--no-self-update",
1592+
],
1593+
);
1594+
1595+
for (topic, path) in mock::topical_doc_data::test_cases() {
1596+
let mut cmd = clitools::cmd(config, "rustup", &["doc", "--path", topic]);
1597+
clitools::env(config, &mut cmd);
1598+
1599+
let out = cmd.output().unwrap();
1600+
eprintln!("{:?}", String::from_utf8(out.stderr).unwrap());
1601+
let out_str = String::from_utf8(out.stdout).unwrap();
1602+
assert!(
1603+
out_str.contains(&path),
1604+
"comparing path\ntopic: '{}'\npath: '{}'\noutput: {}\n\n\n",
1605+
topic,
1606+
path,
1607+
out_str,
1608+
);
1609+
}
1610+
});
1611+
}
1612+
15801613
#[cfg(unix)]
15811614
#[test]
15821615
fn non_utf8_arg() {

tests/mock/clitools.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::mock::dist::{
55
change_channel_date, ManifestVersion, MockChannel, MockComponent, MockDistServer, MockPackage,
66
MockTargetedPackage,
77
};
8+
use crate::mock::topical_doc_data;
89
use crate::mock::{MockComponentBuilder, MockFile, MockInstallerBuilder};
910
use lazy_static::lazy_static;
1011
use std::cell::RefCell;
@@ -960,10 +961,14 @@ fn build_mock_rls_installer(
960961
}
961962

962963
fn build_mock_rust_doc_installer() -> MockInstallerBuilder {
964+
let mut files: Vec<MockFile> = topical_doc_data::paths()
965+
.map(|x| MockFile::new(x, b""))
966+
.collect();
967+
files.insert(0, MockFile::new("share/doc/rust/html/index.html", b""));
963968
MockInstallerBuilder {
964969
components: vec![MockComponentBuilder {
965970
name: "rust-docs".to_string(),
966-
files: vec![MockFile::new("share/doc/rust/html/index.html", b"")],
971+
files: files,
967972
}],
968973
}
969974
}

tests/mock/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
pub mod clitools;
44
pub mod dist;
5+
pub mod topical_doc_data;
56

67
use std::fs::{self, File, OpenOptions};
78
use std::io::Write;

tests/mock/topical_doc_data.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use std::path::PathBuf;
2+
3+
// Paths are written as a string in the UNIX format to make it easy
4+
// to maintain.
5+
static TEST_CASES: &[&[&str]] = &[
6+
&["core", "core/index.html"],
7+
&["core::arch", "core/arch/index.html"],
8+
&["fn", "std/keyword.fn.html"],
9+
&["std::fs", "std/fs/index.html"],
10+
&["std::fs::read_dir", "std/fs/fn.read_dir.html"],
11+
&["std::io::Bytes", "std/io/struct.Bytes.html"],
12+
&["std::iter::Sum", "std/iter/trait.Sum.html"],
13+
&["std::io::error::Result", "std/io/error/type.Result.html"],
14+
&["usize", "std/primitive.usize.html"],
15+
&["eprintln", "std/macro.eprintln.html"],
16+
&["alloc::format", "alloc/macro.format.html"],
17+
];
18+
19+
fn repath(origin: &str) -> String {
20+
// Add doc prefix and rewrite string paths for the current platform
21+
let with_prefix = "share/doc/rust/html/".to_owned() + origin;
22+
let splitted = with_prefix.split("/");
23+
let repathed = splitted.fold(PathBuf::new(), |acc, e| acc.join(e));
24+
repathed.into_os_string().into_string().unwrap()
25+
}
26+
27+
pub fn test_cases<'a>() -> impl Iterator<Item = (&'a str, String)> {
28+
TEST_CASES.iter().map(|x| (x[0], repath(x[1])))
29+
}
30+
31+
pub fn paths() -> impl Iterator<Item = String> {
32+
TEST_CASES.iter().map(|x| repath(x[1]))
33+
}

0 commit comments

Comments
 (0)