Skip to content

Open doc for specific topic #2019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod rustup_mode;
mod self_update;
mod setup_mode;
mod term2;
mod topical_doc;

use crate::errors::*;
use rustup::env_var::RUST_RECURSION_COUNT_MAX;
Expand Down
22 changes: 15 additions & 7 deletions src/cli/rustup_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::help::*;
use crate::self_update;
use crate::term2;
use crate::term2::Terminal;
use crate::topical_doc;
use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, Shell, SubCommand};
use rustup::dist::dist::{PartialTargetTriple, PartialToolchainDesc, Profile, TargetTriple};
use rustup::dist::manifest::Component;
Expand Down Expand Up @@ -510,7 +511,11 @@ pub fn cli() -> App<'static, 'static> {
.map(|(name, _, _)| *name)
.collect::<Vec<_>>(),
),
),
)
.arg(
Arg::with_name("topic")
.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..."),
),
);

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

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

let doc_url =
if let Some((_, _, path)) = DOCS_DATA.iter().find(|(name, _, _)| m.is_present(name)) {
path
} else {
"index.html"
};
let doc_url = if let Some(topic) = m.value_of("topic") {
topical_path = topical_doc::local_path(&toolchain.doc_path("").unwrap(), topic)?;
topical_path.to_str().unwrap()
} else if let Some((_, _, path)) = DOCS_DATA.iter().find(|(name, _, _)| m.is_present(name)) {
path
} else {
"index.html"
};

if m.is_present("path") {
let doc_path = toolchain.doc_path(doc_url)?;
Expand Down
141 changes: 141 additions & 0 deletions src/cli/topical_doc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use crate::errors::*;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};

struct DocData<'a> {
topic: &'a str,
subtopic: &'a str,
root: &'a Path,
}

fn no_document(topic: &str) -> Result<PathBuf> {
Err(format!("No document for '{}'", topic).into())
}

fn index_html(doc: &DocData, wpath: &Path) -> Option<PathBuf> {
let indexhtml = wpath.join("index.html");
match &doc.root.join(&indexhtml).exists() {
true => Some(indexhtml),
false => None,
}
}

fn dir_into_vec(dir: &PathBuf) -> Result<Vec<OsString>> {
let entries = fs::read_dir(dir).chain_err(|| format!("Opening directory {:?}", dir))?;
let mut v = Vec::new();
for entry in entries {
let entry = entry?;
v.push(entry.file_name());
}
Ok(v)
}

fn search_path(doc: &DocData, wpath: &Path, keywords: &[&str]) -> Result<PathBuf> {
let dir = &doc.root.join(&wpath);
if dir.is_dir() {
let entries = dir_into_vec(dir)?;
for k in keywords {
let filename = &format!("{}.{}.html", k, doc.subtopic);
if entries.contains(&OsString::from(filename)) {
return Ok(dir.join(filename));
}
}
no_document(doc.topic)
} else {
no_document(doc.topic)
}
}

pub fn local_path(root: &Path, topic: &str) -> Result<PathBuf> {
let keywords_top = ["macro", "keyword", "primitive"];
let keywords_mod = ["fn", "struct", "trait", "enum", "type", "constant"];

let topic_vec: Vec<&str> = topic.split("::").collect();
let work_path = topic_vec.iter().fold(PathBuf::new(), |acc, e| acc.join(e));

let doc = DocData {
topic: &topic,
subtopic: topic_vec[topic_vec.len() - 1],
root,
};

/**************************
* Please ensure tests/mock/topical_doc_data.rs is UPDATED to reflect
* any change in functionality.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be worth referencing your tests/mock/topical_doc_data.rs file here to say that if additional functionality is added, it should be added to the tests so that it can be verified. That way we'll keep support for new doc sections properly supported if std is split up further.

Argument File directory

# len() == 1 Return index.html
std std/index.html root/std
core core/index.html root/core
alloc alloc/index.html root/core
KKK std/keyword.KKK.html root/std
PPP std/primitive.PPP.html root/std
MMM std/macro.MMM.html root/std


# len() == 2 not ending in ::
MMM std/macro.MMM.html root/std
KKK std/keyword.KKK.html root/std
PPP std/primitive.PPP.html root/std
MMM core/macro.MMM.html root/core
MMM alloc/macro.MMM.html root/alloc
# If above fail, try module
std::module std/module/index.html root/std/module
core::module core/module/index.html root/core/module
alloc::module alloc/module/index.html alloc/core/module

# len() == 2, ending with ::
std::module std/module/index.html root/std/module
core::module core/module/index.html root/core/module
alloc::module alloc/module/index.html alloc/core/module

# len() > 2
# search for index.html in rel_path
std::AAA::MMM std/AAA/MMM/index.html root/std/AAA/MMM

# OR check if parent() dir exists and search for fn/sturct/etc
std::AAA::FFF std/AAA/fn.FFF9.html root/std/AAA
std::AAA::SSS std/AAA/struct.SSS.html root/std/AAA
core:AAA::SSS std/AAA/struct.SSS.html root/coreAAA
alloc:AAA::SSS std/AAA/struct.SSS.html root/coreAAA
std::AAA::TTT std/2222/trait.TTT.html root/std/AAA
std::AAA::EEE std/2222/enum.EEE.html root/std/AAA
std::AAA::TTT std/2222/type.TTT.html root/std/AAA
std::AAA::CCC std/2222/constant.CCC.html root/std/AAA

**************************/

// topic.split.count cannot be 0
let subpath_os_path = match topic_vec.len() {
1 => match topic {
"std" | "core" | "alloc" => match index_html(&doc, &work_path) {
Some(f) => f,
None => no_document(doc.topic)?,
},
_ => {
let std = PathBuf::from("std");
search_path(&doc, &std, &keywords_top)?
}
},
2 => match index_html(&doc, &work_path) {
Some(f) => f,
None => {
let parent = work_path.parent().unwrap();
search_path(&doc, &parent, &keywords_top)?
}
},
_ => match index_html(&doc, &work_path) {
Some(f) => f,
None => {
// len > 2, guaranteed to have a parent, safe to unwrap
let parent = work_path.parent().unwrap();
search_path(&doc, &parent, &keywords_mod)?
}
},
};
// The path and filename were validated to be existing on the filesystem.
// It should be safe to unwrap, or worth panicking.
Ok(subpath_os_path)
}
33 changes: 33 additions & 0 deletions tests/cli-rustup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1577,6 +1577,39 @@ fn docs_with_path() {
});
}

#[test]
fn docs_topical_with_path() {
setup(&|config| {
expect_ok(config, &["rustup", "default", "stable"]);
expect_ok(
config,
&[
"rustup",
"toolchain",
"install",
"nightly",
"--no-self-update",
],
);

for (topic, path) in mock::topical_doc_data::test_cases() {
let mut cmd = clitools::cmd(config, "rustup", &["doc", "--path", topic]);
clitools::env(config, &mut cmd);

let out = cmd.output().unwrap();
eprintln!("{:?}", String::from_utf8(out.stderr).unwrap());
let out_str = String::from_utf8(out.stdout).unwrap();
assert!(
out_str.contains(&path),
"comparing path\ntopic: '{}'\npath: '{}'\noutput: {}\n\n\n",
topic,
path,
out_str,
);
}
});
}

#[cfg(unix)]
#[test]
fn non_utf8_arg() {
Expand Down
7 changes: 6 additions & 1 deletion tests/mock/clitools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::mock::dist::{
change_channel_date, ManifestVersion, MockChannel, MockComponent, MockDistServer, MockPackage,
MockTargetedPackage,
};
use crate::mock::topical_doc_data;
use crate::mock::{MockComponentBuilder, MockFile, MockInstallerBuilder};
use lazy_static::lazy_static;
use std::cell::RefCell;
Expand Down Expand Up @@ -960,10 +961,14 @@ fn build_mock_rls_installer(
}

fn build_mock_rust_doc_installer() -> MockInstallerBuilder {
let mut files: Vec<MockFile> = topical_doc_data::paths()
.map(|x| MockFile::new(x, b""))
.collect();
files.insert(0, MockFile::new("share/doc/rust/html/index.html", b""));
MockInstallerBuilder {
components: vec![MockComponentBuilder {
name: "rust-docs".to_string(),
files: vec![MockFile::new("share/doc/rust/html/index.html", b"")],
files: files,
}],
}
}
Expand Down
1 change: 1 addition & 0 deletions tests/mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod clitools;
pub mod dist;
pub mod topical_doc_data;

use std::fs::{self, File, OpenOptions};
use std::io::Write;
Expand Down
33 changes: 33 additions & 0 deletions tests/mock/topical_doc_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::path::PathBuf;

// Paths are written as a string in the UNIX format to make it easy
// to maintain.
static TEST_CASES: &[&[&str]] = &[
&["core", "core/index.html"],
&["core::arch", "core/arch/index.html"],
&["fn", "std/keyword.fn.html"],
&["std::fs", "std/fs/index.html"],
&["std::fs::read_dir", "std/fs/fn.read_dir.html"],
&["std::io::Bytes", "std/io/struct.Bytes.html"],
&["std::iter::Sum", "std/iter/trait.Sum.html"],
&["std::io::error::Result", "std/io/error/type.Result.html"],
&["usize", "std/primitive.usize.html"],
&["eprintln", "std/macro.eprintln.html"],
&["alloc::format", "alloc/macro.format.html"],
];

fn repath(origin: &str) -> String {
// Add doc prefix and rewrite string paths for the current platform
let with_prefix = "share/doc/rust/html/".to_owned() + origin;
let splitted = with_prefix.split("/");
let repathed = splitted.fold(PathBuf::new(), |acc, e| acc.join(e));
repathed.into_os_string().into_string().unwrap()
}

pub fn test_cases<'a>() -> impl Iterator<Item = (&'a str, String)> {
TEST_CASES.iter().map(|x| (x[0], repath(x[1])))
}

pub fn paths() -> impl Iterator<Item = String> {
TEST_CASES.iter().map(|x| repath(x[1]))
}