Auto merge of #47620 - GuillaumeGomez:multiple-themes, r=QuietMisdreavus

Multiple themes for rustdoc

r? @QuietMisdreavus
This commit is contained in:
bors 2018-01-23 13:23:58 +00:00
commit 3a39b2aa5a
10 changed files with 603 additions and 13 deletions

View File

@ -14,7 +14,7 @@ use std::str;
use html::markdown::{Markdown, RenderType}; use html::markdown::{Markdown, RenderType};
#[derive(Clone)] #[derive(Clone)]
pub struct ExternalHtml{ pub struct ExternalHtml {
/// Content that will be included inline in the <head> section of a /// Content that will be included inline in the <head> section of a
/// rendered Markdown file or generated documentation /// rendered Markdown file or generated documentation
pub in_header: String, pub in_header: String,

View File

@ -10,6 +10,7 @@
use std::fmt; use std::fmt;
use std::io; use std::io;
use std::path::PathBuf;
use externalfiles::ExternalHtml; use externalfiles::ExternalHtml;
@ -31,7 +32,7 @@ pub struct Page<'a> {
pub fn render<T: fmt::Display, S: fmt::Display>( pub fn render<T: fmt::Display, S: fmt::Display>(
dst: &mut io::Write, layout: &Layout, page: &Page, sidebar: &S, t: &T, dst: &mut io::Write, layout: &Layout, page: &Page, sidebar: &S, t: &T,
css_file_extension: bool) css_file_extension: bool, themes: &[PathBuf])
-> io::Result<()> -> io::Result<()>
{ {
write!(dst, write!(dst,
@ -47,8 +48,11 @@ r##"<!DOCTYPE html>
<title>{title}</title> <title>{title}</title>
<link rel="stylesheet" type="text/css" href="{root_path}normalize.css"> <link rel="stylesheet" type="text/css" href="{root_path}normalize.css">
<link rel="stylesheet" type="text/css" href="{root_path}rustdoc.css"> <link rel="stylesheet" type="text/css" href="{root_path}rustdoc.css" id="mainThemeStyle">
<link rel="stylesheet" type="text/css" href="{root_path}main.css"> {themes}
<link rel="stylesheet" type="text/css" href="{root_path}dark.css">
<link rel="stylesheet" type="text/css" href="{root_path}main.css" id="themeStyle">
<script src="{root_path}storage.js"></script>
{css_extension} {css_extension}
{favicon} {favicon}
@ -70,6 +74,11 @@ r##"<!DOCTYPE html>
{sidebar} {sidebar}
</nav> </nav>
<button id="theme-picker">
<img src="{root_path}brush.svg" width="18" alt="Pick another theme!">
<div id="theme-choices"></div>
</button>
<script src="{root_path}theme.js"></script>
<nav class="sub"> <nav class="sub">
<form class="search-form js-only"> <form class="search-form js-only">
<div class="search-container"> <div class="search-container">
@ -176,6 +185,12 @@ r##"<!DOCTYPE html>
after_content = layout.external_html.after_content, after_content = layout.external_html.after_content,
sidebar = *sidebar, sidebar = *sidebar,
krate = layout.krate, krate = layout.krate,
themes = themes.iter()
.filter_map(|t| t.file_stem())
.filter_map(|t| t.to_str())
.map(|t| format!(r#"<link rel="stylesheet" type="text/css" href="{}{}">"#,
page.root_path, t))
.collect::<String>(),
) )
} }

View File

@ -132,6 +132,8 @@ pub struct SharedContext {
/// This flag indicates whether listings of modules (in the side bar and documentation itself) /// This flag indicates whether listings of modules (in the side bar and documentation itself)
/// should be ordered alphabetically or in order of appearance (in the source code). /// should be ordered alphabetically or in order of appearance (in the source code).
pub sort_modules_alphabetically: bool, pub sort_modules_alphabetically: bool,
/// Additional themes to be added to the generated docs.
pub themes: Vec<PathBuf>,
} }
impl SharedContext { impl SharedContext {
@ -219,6 +221,17 @@ impl Error {
} }
} }
macro_rules! try_none {
($e:expr, $file:expr) => ({
use std::io;
match $e {
Some(e) => e,
None => return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"),
$file))
}
})
}
macro_rules! try_err { macro_rules! try_err {
($e:expr, $file:expr) => ({ ($e:expr, $file:expr) => ({
match $e { match $e {
@ -489,7 +502,8 @@ pub fn run(mut krate: clean::Crate,
renderinfo: RenderInfo, renderinfo: RenderInfo,
render_type: RenderType, render_type: RenderType,
sort_modules_alphabetically: bool, sort_modules_alphabetically: bool,
deny_render_differences: bool) -> Result<(), Error> { deny_render_differences: bool,
themes: Vec<PathBuf>) -> Result<(), Error> {
let src_root = match krate.src { let src_root = match krate.src {
FileName::Real(ref p) => match p.parent() { FileName::Real(ref p) => match p.parent() {
Some(p) => p.to_path_buf(), Some(p) => p.to_path_buf(),
@ -513,6 +527,7 @@ pub fn run(mut krate: clean::Crate,
markdown_warnings: RefCell::new(vec![]), markdown_warnings: RefCell::new(vec![]),
created_dirs: RefCell::new(FxHashSet()), created_dirs: RefCell::new(FxHashSet()),
sort_modules_alphabetically, sort_modules_alphabetically,
themes,
}; };
// If user passed in `--playground-url` arg, we fill in crate name here // If user passed in `--playground-url` arg, we fill in crate name here
@ -859,12 +874,65 @@ fn write_shared(cx: &Context,
// Add all the static files. These may already exist, but we just // Add all the static files. These may already exist, but we just
// overwrite them anyway to make sure that they're fresh and up-to-date. // overwrite them anyway to make sure that they're fresh and up-to-date.
write(cx.dst.join("main.js"),
include_bytes!("static/main.js"))?;
write(cx.dst.join("rustdoc.css"), write(cx.dst.join("rustdoc.css"),
include_bytes!("static/rustdoc.css"))?; include_bytes!("static/rustdoc.css"))?;
// To avoid "main.css" to be overwritten, we'll first run over the received themes and only
// then we'll run over the "official" styles.
let mut themes: HashSet<String> = HashSet::new();
for entry in &cx.shared.themes {
let mut content = Vec::with_capacity(100000);
let mut f = try_err!(File::open(&entry), &entry);
try_err!(f.read_to_end(&mut content), &entry);
write(cx.dst.join(try_none!(entry.file_name(), &entry)), content.as_slice())?;
themes.insert(try_none!(try_none!(entry.file_stem(), &entry).to_str(), &entry).to_owned());
}
write(cx.dst.join("brush.svg"),
include_bytes!("static/brush.svg"))?;
write(cx.dst.join("main.css"), write(cx.dst.join("main.css"),
include_bytes!("static/styles/main.css"))?; include_bytes!("static/themes/main.css"))?;
themes.insert("main".to_owned());
write(cx.dst.join("dark.css"),
include_bytes!("static/themes/dark.css"))?;
themes.insert("dark".to_owned());
let mut themes: Vec<&String> = themes.iter().collect();
themes.sort();
// To avoid theme switch latencies as much as possible, we put everything theme related
// at the beginning of the html files into another js file.
write(cx.dst.join("theme.js"), format!(
r#"var themes = document.getElementById("theme-choices");
var themePicker = document.getElementById("theme-picker");
themePicker.onclick = function() {{
if (themes.style.display === "block") {{
themes.style.display = "none";
themePicker.style.borderBottomRightRadius = "3px";
themePicker.style.borderBottomLeftRadius = "3px";
}} else {{
themes.style.display = "block";
themePicker.style.borderBottomRightRadius = "0";
themePicker.style.borderBottomLeftRadius = "0";
}}
}};
[{}].forEach(function(item) {{
var div = document.createElement('div');
div.innerHTML = item;
div.onclick = function(el) {{
switchTheme(currentTheme, mainTheme, item);
}};
themes.appendChild(div);
}});
"#, themes.iter()
.map(|s| format!("\"{}\"", s))
.collect::<Vec<String>>()
.join(",")).as_bytes())?;
write(cx.dst.join("main.js"), include_bytes!("static/main.js"))?;
write(cx.dst.join("storage.js"), include_bytes!("static/storage.js"))?;
if let Some(ref css) = cx.shared.css_file_extension { if let Some(ref css) = cx.shared.css_file_extension {
let out = cx.dst.join("theme.css"); let out = cx.dst.join("theme.css");
try_err!(fs::copy(css, out), css); try_err!(fs::copy(css, out), css);
@ -1156,7 +1224,8 @@ impl<'a> SourceCollector<'a> {
}; };
layout::render(&mut w, &self.scx.layout, layout::render(&mut w, &self.scx.layout,
&page, &(""), &Source(contents), &page, &(""), &Source(contents),
self.scx.css_file_extension.is_some())?; self.scx.css_file_extension.is_some(),
&self.scx.themes)?;
w.flush()?; w.flush()?;
self.scx.local_sources.insert(p.clone(), href); self.scx.local_sources.insert(p.clone(), href);
Ok(()) Ok(())
@ -1520,7 +1589,8 @@ impl Context {
layout::render(writer, &self.shared.layout, &page, layout::render(writer, &self.shared.layout, &page,
&Sidebar{ cx: self, item: it }, &Sidebar{ cx: self, item: it },
&Item{ cx: self, item: it }, &Item{ cx: self, item: it },
self.shared.css_file_extension.is_some())?; self.shared.css_file_extension.is_some(),
&self.shared.themes)?;
} else { } else {
let mut url = self.root_path(); let mut url = self.root_path();
if let Some(&(ref names, ty)) = cache().paths.get(&it.def_id) { if let Some(&(ref names, ty)) = cache().paths.get(&it.def_id) {

View File

@ -0,0 +1 @@
<?xml version="1.0" ?><svg height="1792" viewBox="0 0 1792 1792" width="1792" xmlns="http://www.w3.org/2000/svg"><path d="M1615 0q70 0 122.5 46.5t52.5 116.5q0 63-45 151-332 629-465 752-97 91-218 91-126 0-216.5-92.5t-90.5-219.5q0-128 92-212l638-579q59-54 130-54zm-909 1034q39 76 106.5 130t150.5 76l1 71q4 213-129.5 347t-348.5 134q-123 0-218-46.5t-152.5-127.5-86.5-183-29-220q7 5 41 30t62 44.5 59 36.5 46 17q41 0 55-37 25-66 57.5-112.5t69.5-76 88-47.5 103-25.5 125-10.5z"/></svg>

After

Width:  |  Height:  |  Size: 477 B

View File

@ -122,6 +122,10 @@
} }
} }
document.getElementsByTagName("body")[0].style.marginTop = '45px'; document.getElementsByTagName("body")[0].style.marginTop = '45px';
var themePicker = document.getElementById("theme-picker");
if (themePicker) {
themePicker.style.position = "fixed";
}
} }
function hideSidebar() { function hideSidebar() {
@ -136,6 +140,10 @@
filler.remove(); filler.remove();
} }
document.getElementsByTagName("body")[0].style.marginTop = ''; document.getElementsByTagName("body")[0].style.marginTop = '';
var themePicker = document.getElementById("theme-picker");
if (themePicker) {
themePicker.style.position = "absolute";
}
} }
// used for special search precedence // used for special search precedence
@ -1532,7 +1540,9 @@
ul.appendChild(li); ul.appendChild(li);
} }
div.appendChild(ul); div.appendChild(ul);
sidebar.appendChild(div); if (sidebar) {
sidebar.appendChild(div);
}
} }
block("primitive", "Primitive Types"); block("primitive", "Primitive Types");

View File

@ -360,7 +360,8 @@ ul.item-list > li > .out-of-band {
} }
h4 > code, h3 > code, .invisible > code { h4 > code, h3 > code, .invisible > code {
position: inherit; max-width: calc(100% - 41px);
display: block;
} }
.in-band, code { .in-band, code {
@ -376,6 +377,7 @@ h4 > code, h3 > code, .invisible > code {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
display: inline-block; display: inline-block;
max-width: calc(100% - 43px);
} }
.in-band > code { .in-band > code {
@ -1140,3 +1142,37 @@ kbd {
border-radius: 3px; border-radius: 3px;
box-shadow: inset 0 -1px 0; box-shadow: inset 0 -1px 0;
} }
#theme-picker {
position: absolute;
left: 211px;
top: 17px;
padding: 4px;
border: 1px solid;
border-radius: 3px;
cursor: pointer;
}
#theme-choices {
display: none;
position: absolute;
left: -1px;
top: 30px;
border: 1px solid;
border-radius: 3px;
z-index: 1;
}
#theme-choices > div {
border-top: 1px solid;
padding: 4px;
text-align: center;
}
@media (max-width: 700px) {
#theme-picker {
left: 109px;
top: 7px;
z-index: 1;
}
}

View File

@ -0,0 +1,36 @@
/*!
* Copyright 2018 The Rust Project Developers. See the COPYRIGHT
* file at the top-level directory of this distribution and at
* http://rust-lang.org/COPYRIGHT.
*
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
* http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
* <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
* option. This file may not be copied, modified, or distributed
* except according to those terms.
*/
var currentTheme = document.getElementById("themeStyle");
var mainTheme = document.getElementById("mainThemeStyle");
function updateLocalStorage(name, value) {
if (typeof(Storage) !== "undefined") {
localStorage[name] = value;
} else {
// No Web Storage support so we do nothing
}
}
function getCurrentValue(name) {
if (typeof(Storage) !== "undefined" && localStorage[name] !== undefined) {
return localStorage[name];
}
return null;
}
function switchTheme(styleElem, mainStyleElem, newTheme) {
styleElem.href = mainStyleElem.href.replace("rustdoc.css", newTheme + ".css");
updateLocalStorage('theme', newTheme);
}
switchTheme(currentTheme, mainTheme, getCurrentValue('theme') || 'main');

View File

@ -0,0 +1,384 @@
/**
* Copyright 2015 The Rust Project Developers. See the COPYRIGHT
* file at the top-level directory of this distribution and at
* http://rust-lang.org/COPYRIGHT.
*
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
* http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
* <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
* option. This file may not be copied, modified, or distributed
* except according to those terms.
*/
body {
background-color: #353535;
color: #ddd;
}
h1, h2, h3:not(.impl):not(.method):not(.type):not(.tymethod), h4:not(.method):not(.type):not(.tymethod) {
color: #ddd;
}
h1.fqn {
border-bottom-color: #d2d2d2;
}
h2, h3:not(.impl):not(.method):not(.type):not(.tymethod), h4:not(.method):not(.type):not(.tymethod) {
border-bottom-color: #d2d2d2;
}
.in-band {
background-color: #353535;
}
.invisible {
background: rgba(0, 0, 0, 0);
}
.docblock code, .docblock-short code {
background-color: #2A2A2A;
}
pre {
background-color: #2A2A2A;
}
.sidebar {
background-color: #505050;
}
.sidebar .current {
background-color: #333;
}
.source .sidebar {
background-color: #353535;
}
.sidebar .location {
border-color: #fff;
background: #575757;
color: #DDD;
}
.sidebar .version {
border-bottom-color: #DDD;
}
.sidebar-title {
border-top-color: #777;
border-bottom-color: #777;
}
.block a:hover {
background: #444;
}
.line-numbers span { color: #3B91E2; }
.line-numbers .line-highlighted {
background-color: #0a042f !important;
}
.docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5 {
border-bottom-color: #DDD;
}
.docblock table {
border-color: #ddd;
}
.docblock table td {
border-top-color: #ddd;
border-bottom-color: #ddd;
}
.docblock table th {
border-top-color: #ddd;
border-bottom-color: #ddd;
}
:target { background: #494a3d; }
:target > .in-band {
background: #494a3d;
}
.content .method .where,
.content .fn .where,
.content .where.fmt-newline {
color: #ddd;
}
.content .highlighted {
color: #eee !important;
background-color: #333;
}
.content .highlighted a, .content .highlighted span { color: #eee !important; }
.content .highlighted.trait { background-color: #013191; }
.content .highlighted.mod { background-color: #803a1b; }
.content .highlighted.externcrate { background-color: #396bac; }
.content .highlighted.enum { background-color: #5b4e68; }
.content .highlighted.struct { background-color: #194e9f; }
.content .highlighted.fn,
.content .highlighted.method,
.content .highlighted.tymethod { background-color: #4950ed; }
.content .highlighted.type { background-color: #38902c; }
.content .highlighted.foreigntype { background-color: #b200d6; }
.content .highlighted.macro { background-color: #217d1c; }
.content .highlighted.constant,
.content .highlighted.static { background-color: #0063cc; }
.content .highlighted.primitive { background-color: #00708a; }
.content span.enum, .content a.enum, .block a.current.enum { color: #82b089; }
.content span.struct, .content a.struct, .block a.current.struct { color: #ff794d; }
.content span.type, .content a.type, .block a.current.type { color: #ff7f00; }
.content span.foreigntype, .content a.foreigntype, .block a.current.foreigntype { color: #dd7de8; }
.content span.macro, .content a.macro, .block a.current.macro { color: #09bd00; }
.content span.union, .content a.union, .block a.current.union { color: #a6ae37; }
.content span.constant, .content a.constant, .block a.current.constant,
.content span.static, .content a.static, .block a.current.static { color: #82a5c9; }
.content span.primitive, .content a.primitive, .block a.current.primitive { color: #43aec7; }
.content span.externcrate,
.content span.mod, .content a.mod, .block a.current.mod { color: #bda000; }
.content span.trait, .content a.trait, .block a.current.trait { color: #b78cf2; }
.content span.fn, .content a.fn, .block a.current.fn,
.content span.method, .content a.method, .block a.current.method,
.content span.tymethod, .content a.tymethod, .block a.current.tymethod,
.content .fnname{ color: #2BAB63; }
pre.rust .comment { color: #8d8d8b; }
pre.rust .doccomment { color: #8ca375; }
nav {
border-bottom-color: #4e4e4e;
}
nav.main .current {
border-top-color: #eee;
border-bottom-color: #eee;
}
nav.main .separator {
border-color: #eee;
}
a {
color: #ddd;
}
.docblock a, .docblock-short a, .stability a {
color: #D2991D;
}
a.test-arrow {
color: #dedede;
}
.collapse-toggle {
color: #999;
}
.search-input {
color: #111;
box-shadow: 0 0 0 1px #000, 0 0 0 2px transparent;
background-color: #f0f0f0;
}
.search-input:focus {
border-color: #008dfd;
}
.stab.unstable { background: #FFF5D6; border-color: #FFC600; color: #404040; }
.stab.deprecated { background: #F3DFFF; border-color: #7F0087; color: #404040; }
.stab.portability { background: #C4ECFF; border-color: #7BA5DB; color: #404040; }
.module-item .stab {
color: #ddd;
}
#help > div {
background: #4d4d4d;
border-color: #bfbfbf;
}
#help dt {
border-color: #bfbfbf;
background: #fff;
color: black;
}
.since {
color: grey;
}
tr.result span.primitive::after {
color: #ddd;
}
.line-numbers :target { background-color: transparent; }
/* Code highlighting */
pre.rust .kw { color: #ab8ac1; }
pre.rust .kw-2, pre.rust .prelude-ty { color: #769acb; }
pre.rust .number, pre.rust .string { color: #83a300; }
pre.rust .self, pre.rust .bool-val, pre.rust .prelude-val,
pre.rust .attribute, pre.rust .attribute .ident { color: #ee6868; }
pre.rust .macro, pre.rust .macro-nonterminal { color: #3E999F; }
pre.rust .lifetime { color: #d97f26; }
pre.rust .question-mark {
color: #ff9011;
}
a.test-arrow {
background-color: rgba(78, 139, 202, 0.2);
}
a.test-arrow:hover{
background-color: #4e8bca;
}
.toggle-label {
color: #999;
}
:target > code {
background: #FDFFD3;
}
pre.compile_fail {
border-left: 2px solid rgba(255,0,0,.6);
}
pre.compile_fail:hover, .information:hover + pre.compile_fail {
border-left: 2px solid #f00;
}
pre.ignore {
border-left: 2px solid rgba(255,142,0,.6);
}
pre.ignore:hover, .information:hover + pre.ignore {
border-left: 2px solid #ff9200;
}
.tooltip.compile_fail {
color: rgba(255,0,0,.6);
}
.information > .compile_fail:hover {
color: #f00;
}
.tooltip.ignore {
color: rgba(255,142,0,.6);
}
.information > .ignore:hover {
color: rgba(255,142,0,1);
}
.search-failed > a {
color: #0089ff;
}
.tooltip .tooltiptext {
background-color: black;
color: #fff;
}
.tooltip .tooltiptext::after {
border-color: transparent black transparent transparent;
}
.important-traits .tooltip .tooltiptext {
background-color: white;
color: black;
border-color: black;
}
#titles > div {
border-bottom-color: #ccc;
}
#titles > div.selected {
border-bottom-color: #0078ee;
}
#titles > div:hover {
border-bottom-color: #0089ff;
}
#titles > div > div.count {
color: #888;
}
.modal {
background-color: rgba(0,0,0,0.3);
}
.modal-content {
background-color: #272727;
border-color: #999;
}
.modal-content > .close {
background-color: #272727;
border-color: #999;
}
.modal-content > .close:hover {
background-color: #ff1f1f;
color: white;
}
.modal-content > .whiter {
background-color: #272727;
}
.modal-content > .close:hover + .whiter {
background-color: #ff1f1f;
}
@media (max-width: 700px) {
.sidebar-menu {
background-color: #505050;
border-bottom-color: #e0e0e0;
border-right-color: #e0e0e0;
}
.sidebar-elems {
background-color: #505050;
border-right-color: #000;
}
#sidebar-filler {
background-color: #505050;
border-bottom-color: #e0e0e0;
}
}
kbd {
color: #444d56;
background-color: #fafbfc;
border-color: #d1d5da;
border-bottom-color: #c6cbd1;
box-shadow-color: #c6cbd1;
}
#theme-picker {
border-color: #e0e0e0;
background: #f0f0f0;
}
#theme-choices {
border-color: #e0e0e0;
background-color: #353535;
}
#theme-choices > div {
border-top: #e0e0e0;
}
#theme-choices > div:hover {
background-color: #444;
}
@media (max-width: 700px) {
#theme-picker {
background: #353535;
}
}

View File

@ -351,3 +351,26 @@ kbd {
border-bottom-color: #c6cbd1; border-bottom-color: #c6cbd1;
box-shadow-color: #c6cbd1; box-shadow-color: #c6cbd1;
} }
#theme-picker {
border-color: #e0e0e0;
}
#theme-choices {
border-color: #ccc;
background-color: #fff;
}
#theme-choices > div {
border-top: #e0e0e0;
}
#theme-choices > div:hover {
background-color: #eee;
}
@media (max-width: 700px) {
#theme-picker {
background: #fff;
}
}

View File

@ -264,6 +264,11 @@ pub fn opts() -> Vec<RustcOptGroup> {
o.optflag("", "deny-render-differences", "abort doc runs when markdown rendering \ o.optflag("", "deny-render-differences", "abort doc runs when markdown rendering \
differences are found") differences are found")
}), }),
unstable("themes", |o| {
o.optmulti("", "themes",
"additional themes which will be added to the generated docs",
"FILES")
}),
] ]
} }
@ -365,6 +370,15 @@ pub fn main_args(args: &[String]) -> isize {
} }
} }
let mut themes = Vec::new();
for theme in matches.opt_strs("themes").iter().map(|s| PathBuf::from(&s)) {
if !theme.is_file() {
eprintln!("rustdoc: option --themes arguments must all be files");
return 1;
}
themes.push(theme);
}
let external_html = match ExternalHtml::load( let external_html = match ExternalHtml::load(
&matches.opt_strs("html-in-header"), &matches.opt_strs("html-in-header"),
&matches.opt_strs("html-before-content"), &matches.opt_strs("html-before-content"),
@ -413,7 +427,8 @@ pub fn main_args(args: &[String]) -> isize {
renderinfo, renderinfo,
render_type, render_type,
sort_modules_alphabetically, sort_modules_alphabetically,
deny_render_differences) deny_render_differences,
themes)
.expect("failed to generate documentation"); .expect("failed to generate documentation");
0 0
} }