Skip to content

Commit

Permalink
Merge pull request #2019 from dorfsmay/open_doc_for_topic
Browse files Browse the repository at this point in the history
Open doc for specific topic
  • Loading branch information
kinnison authored Oct 8, 2019
2 parents cba68ab + c50626e commit b9e968b
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 8 deletions.
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.
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]))
}

0 comments on commit b9e968b

Please sign in to comment.