about summary refs log tree commit diff
path: root/src/tools
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2017-06-16 14:41:15 +0000
committerbors <bors@rust-lang.org>2017-06-16 14:41:15 +0000
commitb40be00a0cac84d23f51c5c5109c8f824ab19ab3 (patch)
treeb0ccb328f6bc81c24818214b4a65185890d6abf4 /src/tools
parentebbc9ea914a1cefa48afb9cc6c9f7a14ff7c6857 (diff)
parentb34ac5dbdab8221a227238f2ec8089df3a2aa06d (diff)
downloadrust-b40be00a0cac84d23f51c5c5109c8f824ab19ab3.tar.gz
rust-b40be00a0cac84d23f51c5c5109c8f824ab19ab3.zip
Auto merge of #42612 - est31:master, r=nagisa
Autogenerate stubs and SUMMARY.md in the unstable book

Removes a speed bump in compiler development by autogenerating stubs for features in the unstable book. See #42454 for discussion.

The PR contains three commits, separated in order to make review easy:

* The first commit converts the tidy tool from a binary crate to a crate that contains both a library and a binary. In the second commit, we'll use the tidy library
* The second and main commit introduces autogeneration of SUMMARY.md and feature stub files
* The third commit turns off the tidy lint that checks for features without a stub, and removes the stub files. A separate commit due to the large number of files touched

Members of the doc team who wish to document some features can either do this (where `$rustsrc` is the root of the rust repo git checkout):

1. cd to `$rustsrc/src/tools/unstable-book-gen` and then do `cargo run $rustsrc/src $rustsrc/src/doc/unstable-book` to put the stubs into the unstable book
2. cd to `$rustsrc` and run `git ls-files --others --exclude-standard` to list the newly added stubs
3. choose a file to edit, then `git add` it and `git commit`
4. afterwards, remove all changes by the tool by doing `git --reset hard` and `git clean -f`

Or they can do this:

1. remove the comment marker in `src/tools/tidy/src/unstable_book.rs` line 122
2. run `./x.py test src/tools/tidy` to list the unstable features which only have stubs
3. revert the change in 1
3. document one of the chosen unstable features

The changes done by this PR also allow for further development:

* tidy obtains information about tracking issues. We can now forbid differing tracking issues between differing `#![unstable]` annotations. I haven't done this but plan to in a future PR
* we now have a general framework for generating stuff for the unstable book at build time. Further changes can autogenerate a list of the API a given library feature exposes.

The old way to simply click through the documentation after it has been uploaded to rust-lang.org works as well.

r? @nagisa

Fixes #42454
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/tidy/src/features.rs66
-rw-r--r--src/tools/tidy/src/lib.rs88
-rw-r--r--src/tools/tidy/src/main.rs80
-rw-r--r--src/tools/tidy/src/unstable_book.rs79
-rw-r--r--src/tools/unstable-book-gen/Cargo.toml9
-rw-r--r--src/tools/unstable-book-gen/src/SUMMARY.md8
-rw-r--r--src/tools/unstable-book-gen/src/main.rs149
-rw-r--r--src/tools/unstable-book-gen/src/stub-issue.md7
-rw-r--r--src/tools/unstable-book-gen/src/stub-no-issue.md5
9 files changed, 362 insertions, 129 deletions
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
index e34821e3584..81db23ccceb 100644
--- a/src/tools/tidy/src/features.rs
+++ b/src/tools/tidy/src/features.rs
@@ -24,7 +24,7 @@ use std::fs::File;
 use std::io::prelude::*;
 use std::path::Path;
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone)]
 pub enum Status {
     Stable,
     Removed,
@@ -42,13 +42,16 @@ impl fmt::Display for Status {
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct Feature {
     pub level: Status,
     pub since: String,
     pub has_gate_test: bool,
+    pub tracking_issue: Option<u32>,
 }
 
+pub type Features = HashMap<String, Feature>;
+
 pub fn check(path: &Path, bad: &mut bool, quiet: bool) {
     let mut features = collect_lang_features(path);
     assert!(!features.is_empty());
@@ -168,8 +171,7 @@ fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
         .map(|(i, j)| &line[i..j])
 }
 
-fn test_filen_gate(filen_underscore: &str,
-                   features: &mut HashMap<String, Feature>) -> bool {
+fn test_filen_gate(filen_underscore: &str, features: &mut Features) -> bool {
     if filen_underscore.starts_with("feature_gate") {
         for (n, f) in features.iter_mut() {
             if filen_underscore == format!("feature_gate_{}", n) {
@@ -181,7 +183,7 @@ fn test_filen_gate(filen_underscore: &str,
     return false;
 }
 
-pub fn collect_lang_features(base_src_path: &Path) -> HashMap<String, Feature> {
+pub fn collect_lang_features(base_src_path: &Path) -> Features {
     let mut contents = String::new();
     let path = base_src_path.join("libsyntax/feature_gate.rs");
     t!(t!(File::open(path)).read_to_string(&mut contents));
@@ -197,11 +199,19 @@ pub fn collect_lang_features(base_src_path: &Path) -> HashMap<String, Feature> {
             };
             let name = parts.next().unwrap().trim();
             let since = parts.next().unwrap().trim().trim_matches('"');
+            let issue_str = parts.next().unwrap().trim();
+            let tracking_issue = if issue_str.starts_with("None") {
+                None
+            } else {
+                let s = issue_str.split("(").nth(1).unwrap().split(")").nth(0).unwrap();
+                Some(s.parse().unwrap())
+            };
             Some((name.to_owned(),
                 Feature {
-                    level: level,
+                    level,
                     since: since.to_owned(),
                     has_gate_test: false,
+                    tracking_issue,
                 }))
         })
         .collect()
@@ -209,8 +219,8 @@ pub fn collect_lang_features(base_src_path: &Path) -> HashMap<String, Feature> {
 
 pub fn collect_lib_features(base_src_path: &Path,
                             bad: &mut bool,
-                            features: &HashMap<String, Feature>) -> HashMap<String, Feature> {
-    let mut lib_features = HashMap::<String, Feature>::new();
+                            features: &Features) -> Features {
+    let mut lib_features = Features::new();
     let mut contents = String::new();
     super::walk(base_src_path,
                 &mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
@@ -224,10 +234,32 @@ pub fn collect_lib_features(base_src_path: &Path,
         contents.truncate(0);
         t!(t!(File::open(&file), &file).read_to_string(&mut contents));
 
+        let mut becoming_feature: Option<(String, Feature)> = None;
         for (i, line) in contents.lines().enumerate() {
             let mut err = |msg: &str| {
                 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
             };
+            if let Some((ref name, ref mut f)) = becoming_feature {
+                if f.tracking_issue.is_none() {
+                    f.tracking_issue = find_attr_val(line, "issue")
+                    .map(|s| s.parse().unwrap());
+                }
+                if line.ends_with("]") {
+                    lib_features.insert(name.to_owned(), f.clone());
+                } else if !line.ends_with(",") && !line.ends_with("\\") {
+                    // We need to bail here because we might have missed the
+                    // end of a stability attribute above because the "]"
+                    // might not have been at the end of the line.
+                    // We could then get into the very unfortunate situation that
+                    // we continue parsing the file assuming the current stability
+                    // attribute has not ended, and ignoring possible feature
+                    // attributes in the process.
+                    err("malformed stability attribute");
+                } else {
+                    continue;
+                }
+            }
+            becoming_feature = None;
             let level = if line.contains("[unstable(") {
                 Status::Unstable
             } else if line.contains("[stable(") {
@@ -250,6 +282,7 @@ pub fn collect_lib_features(base_src_path: &Path,
                 }
                 None => "None",
             };
+            let tracking_issue = find_attr_val(line, "issue").map(|s| s.parse().unwrap());
 
             if features.contains_key(feature_name) {
                 err("duplicating a lang feature");
@@ -263,12 +296,17 @@ pub fn collect_lib_features(base_src_path: &Path,
                 }
                 continue;
             }
-            lib_features.insert(feature_name.to_owned(),
-                                Feature {
-                                    level: level,
-                                    since: since.to_owned(),
-                                    has_gate_test: false,
-                                });
+            let feature = Feature {
+                level,
+                since: since.to_owned(),
+                has_gate_test: false,
+                tracking_issue,
+            };
+            if line.contains("]") {
+                lib_features.insert(feature_name.to_owned(), feature);
+            } else {
+                becoming_feature = Some((feature_name.to_owned(), feature));
+            }
         }
     });
     lib_features
diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs
new file mode 100644
index 00000000000..bcf86e4489b
--- /dev/null
+++ b/src/tools/tidy/src/lib.rs
@@ -0,0 +1,88 @@
+// Copyright 2017 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.
+
+//! Library used by tidy and other tools
+//!
+//! This library contains the tidy lints and exposes it
+//! to be used by tools.
+
+#![deny(warnings)]
+
+use std::fs;
+
+use std::path::Path;
+
+macro_rules! t {
+    ($e:expr, $p:expr) => (match $e {
+        Ok(e) => e,
+        Err(e) => panic!("{} failed on {} with {}", stringify!($e), ($p).display(), e),
+    });
+
+    ($e:expr) => (match $e {
+        Ok(e) => e,
+        Err(e) => panic!("{} failed with {}", stringify!($e), e),
+    })
+}
+
+macro_rules! tidy_error {
+    ($bad:expr, $fmt:expr, $($arg:tt)*) => ({
+        use std::io::Write;
+        *$bad = true;
+        write!(::std::io::stderr(), "tidy error: ").expect("could not write to stderr");
+        writeln!(::std::io::stderr(), $fmt, $($arg)*).expect("could not write to stderr");
+    });
+}
+
+pub mod bins;
+pub mod style;
+pub mod errors;
+pub mod features;
+pub mod cargo;
+pub mod pal;
+pub mod deps;
+pub mod unstable_book;
+
+fn filter_dirs(path: &Path) -> bool {
+    let skip = [
+        "src/jemalloc",
+        "src/llvm",
+        "src/libbacktrace",
+        "src/compiler-rt",
+        "src/rustllvm",
+        "src/liblibc",
+        "src/vendor",
+        "src/rt/hoedown",
+        "src/tools/cargo",
+        "src/tools/rls",
+        "src/tools/rust-installer",
+    ];
+    skip.iter().any(|p| path.ends_with(p))
+}
+
+fn walk_many(paths: &[&Path], skip: &mut FnMut(&Path) -> bool, f: &mut FnMut(&Path)) {
+    for path in paths {
+        walk(path, skip, f);
+    }
+}
+
+fn walk(path: &Path, skip: &mut FnMut(&Path) -> bool, f: &mut FnMut(&Path)) {
+    for entry in t!(fs::read_dir(path), path) {
+        let entry = t!(entry);
+        let kind = t!(entry.file_type());
+        let path = entry.path();
+        if kind.is_dir() {
+            if !skip(&path) {
+                walk(&path, skip, f);
+            }
+        } else {
+            f(&path);
+        }
+    }
+}
diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs
index 23a31131f7a..433192a21ec 100644
--- a/src/tools/tidy/src/main.rs
+++ b/src/tools/tidy/src/main.rs
@@ -8,47 +8,21 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-//! Tidy checks for source code in this repository
+//! Tidy checks source code in this repository
 //!
 //! This program runs all of the various tidy checks for style, cleanliness,
 //! etc. This is run by default on `make check` and as part of the auto
 //! builders.
 
-use std::env;
-use std::fs;
-use std::io::{self, Write};
-use std::path::{PathBuf, Path};
-use std::process;
+#![deny(warnings)]
 
-macro_rules! t {
-    ($e:expr, $p:expr) => (match $e {
-        Ok(e) => e,
-        Err(e) => panic!("{} failed on {} with {}", stringify!($e), ($p).display(), e),
-    });
-
-    ($e:expr) => (match $e {
-        Ok(e) => e,
-        Err(e) => panic!("{} failed with {}", stringify!($e), e),
-    })
-}
+extern crate tidy;
+use tidy::*;
 
-macro_rules! tidy_error {
-    ($bad:expr, $fmt:expr, $($arg:tt)*) => ({
-        use std::io::Write;
-        *$bad = true;
-        write!(::std::io::stderr(), "tidy error: ").expect("could not write to stderr");
-        writeln!(::std::io::stderr(), $fmt, $($arg)*).expect("could not write to stderr");
-    });
-}
-
-mod bins;
-mod style;
-mod errors;
-mod features;
-mod cargo;
-mod pal;
-mod deps;
-mod unstable_book;
+use std::process;
+use std::path::PathBuf;
+use std::env;
+use std::io::{self, Write};
 
 fn main() {
     let path = env::args_os().skip(1).next().expect("need an argument");
@@ -74,41 +48,3 @@ fn main() {
         process::exit(1);
     }
 }
-
-fn filter_dirs(path: &Path) -> bool {
-    let skip = [
-        "src/jemalloc",
-        "src/llvm",
-        "src/libbacktrace",
-        "src/compiler-rt",
-        "src/rustllvm",
-        "src/liblibc",
-        "src/vendor",
-        "src/rt/hoedown",
-        "src/tools/cargo",
-        "src/tools/rls",
-        "src/tools/rust-installer",
-    ];
-    skip.iter().any(|p| path.ends_with(p))
-}
-
-fn walk_many(paths: &[&Path], skip: &mut FnMut(&Path) -> bool, f: &mut FnMut(&Path)) {
-    for path in paths {
-        walk(path, skip, f);
-    }
-}
-
-fn walk(path: &Path, skip: &mut FnMut(&Path) -> bool, f: &mut FnMut(&Path)) {
-    for entry in t!(fs::read_dir(path), path) {
-        let entry = t!(entry);
-        let kind = t!(entry.file_type());
-        let path = entry.path();
-        if kind.is_dir() {
-            if !skip(&path) {
-                walk(&path, skip, f);
-            }
-        } else {
-            f(&path);
-        }
-    }
-}
diff --git a/src/tools/tidy/src/unstable_book.rs b/src/tools/tidy/src/unstable_book.rs
index 5a6524b3e88..fd3ffc685d9 100644
--- a/src/tools/tidy/src/unstable_book.rs
+++ b/src/tools/tidy/src/unstable_book.rs
@@ -11,26 +11,28 @@
 use std::collections::HashSet;
 use std::fs;
 use std::path;
-use features::{collect_lang_features, collect_lib_features, Status};
+use features::{collect_lang_features, collect_lib_features, Features, Status};
 
-const PATH_STR: &'static str = "doc/unstable-book/src";
+pub const PATH_STR: &str = "doc/unstable-book/src";
 
-const LANG_FEATURES_DIR: &'static str = "language-features";
+pub const COMPILER_FLAGS_DIR: &str = "compiler-flags";
 
-const LIB_FEATURES_DIR: &'static str = "library-features";
+pub const LANG_FEATURES_DIR: &str = "language-features";
+
+pub const LIB_FEATURES_DIR: &str = "library-features";
 
 /// Build the path to the Unstable Book source directory from the Rust 'src' directory
-fn unstable_book_path(base_src_path: &path::Path) -> path::PathBuf {
+pub fn unstable_book_path(base_src_path: &path::Path) -> path::PathBuf {
     base_src_path.join(PATH_STR)
 }
 
 /// Directory where the features are documented within the Unstable Book source directory
-fn unstable_book_lang_features_path(base_src_path: &path::Path) -> path::PathBuf {
+pub fn unstable_book_lang_features_path(base_src_path: &path::Path) -> path::PathBuf {
     unstable_book_path(base_src_path).join(LANG_FEATURES_DIR)
 }
 
 /// Directory where the features are documented within the Unstable Book source directory
-fn unstable_book_lib_features_path(base_src_path: &path::Path) -> path::PathBuf {
+pub fn unstable_book_lib_features_path(base_src_path: &path::Path) -> path::PathBuf {
     unstable_book_path(base_src_path).join(LIB_FEATURES_DIR)
 }
 
@@ -42,27 +44,16 @@ fn dir_entry_is_file(dir_entry: &fs::DirEntry) -> bool {
         .is_file()
 }
 
-/// Retrieve names of all lang-related unstable features
-fn collect_unstable_lang_feature_names(base_src_path: &path::Path) -> HashSet<String> {
-    collect_lang_features(base_src_path)
-        .into_iter()
-        .filter(|&(_, ref f)| f.level == Status::Unstable)
-        .map(|(ref name, _)| name.to_owned())
-        .collect()
-}
-
-/// Retrieve names of all lib-related unstable features
-fn collect_unstable_lib_feature_names(base_src_path: &path::Path) -> HashSet<String> {
-    let mut bad = true;
-    let lang_features = collect_lang_features(base_src_path);
-    collect_lib_features(base_src_path, &mut bad, &lang_features)
-        .into_iter()
+/// Retrieve names of all unstable features
+pub fn collect_unstable_feature_names(features: &Features) -> HashSet<String> {
+    features
+        .iter()
         .filter(|&(_, ref f)| f.level == Status::Unstable)
-        .map(|(ref name, _)| name.to_owned())
+        .map(|(name, _)| name.to_owned())
         .collect()
 }
 
-fn collect_unstable_book_section_file_names(dir: &path::Path) -> HashSet<String> {
+pub fn collect_unstable_book_section_file_names(dir: &path::Path) -> HashSet<String> {
     fs::read_dir(dir)
         .expect("could not read directory")
         .into_iter()
@@ -95,19 +86,13 @@ pub fn check(path: &path::Path, bad: &mut bool) {
 
     // Library features
 
-    let unstable_lib_feature_names = collect_unstable_lib_feature_names(path);
+    let lang_features = collect_lang_features(path);
+    let lib_features = collect_lib_features(path, bad, &lang_features);
+
+    let unstable_lib_feature_names = collect_unstable_feature_names(&lib_features);
     let unstable_book_lib_features_section_file_names =
         collect_unstable_book_lib_features_section_file_names(path);
 
-    // Check for unstable features that don't have Unstable Book sections
-    for feature_name in &unstable_lib_feature_names -
-                        &unstable_book_lib_features_section_file_names {
-        tidy_error!(bad,
-                    "Unstable library feature '{}' needs to have a section within the \
-                     'library features' section of The Unstable Book",
-                    feature_name);
-    }
-
     // Check for Unstable Book sections that don't have a corresponding unstable feature
     for feature_name in &unstable_book_lib_features_section_file_names -
                         &unstable_lib_feature_names {
@@ -119,18 +104,10 @@ pub fn check(path: &path::Path, bad: &mut bool) {
 
     // Language features
 
-    let unstable_lang_feature_names = collect_unstable_lang_feature_names(path);
+    let unstable_lang_feature_names = collect_unstable_feature_names(&lang_features);
     let unstable_book_lang_features_section_file_names =
         collect_unstable_book_lang_features_section_file_names(path);
 
-    for feature_name in &unstable_lang_feature_names -
-                        &unstable_book_lang_features_section_file_names {
-        tidy_error!(bad,
-                    "Unstable language feature '{}' needs to have a section within the \
-                     'language features' section of The Unstable Book",
-                    feature_name);
-    }
-
     // Check for Unstable Book sections that don't have a corresponding unstable feature
     for feature_name in &unstable_book_lang_features_section_file_names -
                         &unstable_lang_feature_names {
@@ -139,4 +116,20 @@ pub fn check(path: &path::Path, bad: &mut bool) {
                      correspond to an unstable language feature",
                     feature_name)
     }
+
+    // List unstable features that don't have Unstable Book sections
+    // Remove the comment marker if you want the list printed
+    /*
+    println!("Lib features without unstable book sections:");
+    for feature_name in &unstable_lang_feature_names -
+                        &unstable_book_lang_features_section_file_names {
+        println!("    * {} {:?}", feature_name, lib_features[&feature_name].tracking_issue);
+    }
+
+    println!("Lang features without unstable book sections:");
+    for feature_name in &unstable_lib_feature_names-
+                        &unstable_book_lib_features_section_file_names {
+        println!("    * {} {:?}", feature_name, lang_features[&feature_name].tracking_issue);
+    }
+    // */
 }
diff --git a/src/tools/unstable-book-gen/Cargo.toml b/src/tools/unstable-book-gen/Cargo.toml
new file mode 100644
index 00000000000..4751a5e4151
--- /dev/null
+++ b/src/tools/unstable-book-gen/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+authors = ["est31 <MTest31@outlook.com>",
+           "The Rust Project Developers"]
+name = "unstable-book-gen"
+version = "0.1.0"
+license = "MIT/Apache-2.0"
+
+[dependencies]
+tidy = { path = "../tidy" }
diff --git a/src/tools/unstable-book-gen/src/SUMMARY.md b/src/tools/unstable-book-gen/src/SUMMARY.md
new file mode 100644
index 00000000000..933c928e2f0
--- /dev/null
+++ b/src/tools/unstable-book-gen/src/SUMMARY.md
@@ -0,0 +1,8 @@
+[The Unstable Book](the-unstable-book.md)
+
+- [Compiler flags](compiler-flags.md)
+{compiler_flags}
+- [Language features](language-features.md)
+{language_features}
+- [Library Features](library-features.md)
+{library_features}
diff --git a/src/tools/unstable-book-gen/src/main.rs b/src/tools/unstable-book-gen/src/main.rs
new file mode 100644
index 00000000000..adec73d4a69
--- /dev/null
+++ b/src/tools/unstable-book-gen/src/main.rs
@@ -0,0 +1,149 @@
+// Copyright 2017 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.
+
+//! Auto-generate stub docs for the unstable book
+
+#![deny(warnings)]
+
+extern crate tidy;
+
+use tidy::features::{Feature, Features, collect_lib_features, collect_lang_features};
+use tidy::unstable_book::{collect_unstable_feature_names, collect_unstable_book_section_file_names,
+                          PATH_STR, LANG_FEATURES_DIR, LIB_FEATURES_DIR};
+use std::collections::HashSet;
+use std::io::Write;
+use std::fs::{self, File};
+use std::env;
+use std::path::Path;
+
+/// A helper macro to `unwrap` a result except also print out details like:
+///
+/// * The file/line of the panic
+/// * The expression that failed
+/// * The error itself
+macro_rules! t {
+    ($e:expr) => (match $e {
+        Ok(e) => e,
+        Err(e) => panic!("{} failed with {}", stringify!($e), e),
+    })
+}
+
+fn generate_stub_issue(path: &Path, name: &str, issue: u32) {
+    let mut file = t!(File::create(path));
+    t!(file.write_fmt(format_args!(include_str!("stub-issue.md"),
+                                   name = name,
+                                   issue = issue)));
+}
+
+fn generate_stub_no_issue(path: &Path, name: &str) {
+    let mut file = t!(File::create(path));
+    t!(file.write_fmt(format_args!(include_str!("stub-no-issue.md"),
+                                   name = name)));
+}
+
+fn hset_to_summary_str(hset: HashSet<String>, dir: &str
+) -> String {
+    hset
+        .iter()
+        .map(|ref n| format!("    - [{}]({}/{}.md)",
+                                      n,
+                                      dir,
+                                      n.replace('_', "-")))
+        .fold("".to_owned(), |s, a| s + &a + "\n")
+}
+
+fn generate_summary(path: &Path, lang_features: &Features, lib_features: &Features) {
+    let compiler_flags = collect_unstable_book_section_file_names(
+        &path.join("compiler-flags"));
+
+    let compiler_flags_str = hset_to_summary_str(compiler_flags,
+                                                 "compiler-flags");
+
+    let unstable_lang_features = collect_unstable_feature_names(&lang_features);
+    let unstable_lib_features = collect_unstable_feature_names(&lib_features);
+
+    let lang_features_str = hset_to_summary_str(unstable_lang_features,
+                                                LANG_FEATURES_DIR);
+    let lib_features_str = hset_to_summary_str(unstable_lib_features,
+                                               LIB_FEATURES_DIR);
+
+    let mut file = t!(File::create(&path.join("SUMMARY.md")));
+    t!(file.write_fmt(format_args!(include_str!("SUMMARY.md"),
+                                   compiler_flags = compiler_flags_str,
+                                   language_features = lang_features_str,
+                                   library_features = lib_features_str)));
+
+}
+
+fn has_valid_tracking_issue(f: &Feature) -> bool {
+    if let Some(n) = f.tracking_issue {
+        if n > 0 {
+            return true;
+        }
+    }
+    false
+}
+
+fn generate_unstable_book_files(src :&Path, out: &Path, features :&Features) {
+    let unstable_features = collect_unstable_feature_names(features);
+    let unstable_section_file_names = collect_unstable_book_section_file_names(src);
+    t!(fs::create_dir_all(&out));
+    for feature_name in &unstable_features - &unstable_section_file_names {
+        let file_name = format!("{}.md", feature_name.replace('_', "-"));
+        let out_file_path = out.join(&file_name);
+        let feature = &features[&feature_name];
+
+        if has_valid_tracking_issue(&feature) {
+            generate_stub_issue(&out_file_path, &feature_name, feature.tracking_issue.unwrap());
+        } else {
+            generate_stub_no_issue(&out_file_path, &feature_name);
+        }
+    }
+}
+
+fn copy_recursive(path: &Path, to: &Path) {
+    for entry in t!(fs::read_dir(path)) {
+        let e = t!(entry);
+        let t = t!(e.metadata());
+        let dest = &to.join(e.file_name());
+        if t.is_file() {
+            t!(fs::copy(&e.path(), dest));
+        } else if t.is_dir() {
+            t!(fs::create_dir_all(dest));
+            copy_recursive(&e.path(), dest);
+        }
+    }
+}
+
+fn main() {
+    let src_path_str = env::args_os().skip(1).next().expect("source path required");
+    let dest_path_str = env::args_os().skip(2).next().expect("destination path required");
+    let src_path = Path::new(&src_path_str);
+    let dest_path = Path::new(&dest_path_str).join("src");
+
+    let lang_features = collect_lang_features(src_path);
+    let mut bad = false;
+    let lib_features = collect_lib_features(src_path, &mut bad, &lang_features);
+
+    let doc_src_path = src_path.join(PATH_STR);
+
+    t!(fs::create_dir_all(&dest_path));
+
+    generate_unstable_book_files(&doc_src_path.join(LANG_FEATURES_DIR),
+                                 &dest_path.join(LANG_FEATURES_DIR),
+                                 &lang_features);
+    generate_unstable_book_files(&doc_src_path.join(LIB_FEATURES_DIR),
+                                 &dest_path.join(LIB_FEATURES_DIR),
+                                 &lib_features);
+
+    copy_recursive(&doc_src_path, &dest_path);
+
+    generate_summary(&dest_path, &lang_features, &lib_features);
+}
diff --git a/src/tools/unstable-book-gen/src/stub-issue.md b/src/tools/unstable-book-gen/src/stub-issue.md
new file mode 100644
index 00000000000..8698fb7278f
--- /dev/null
+++ b/src/tools/unstable-book-gen/src/stub-issue.md
@@ -0,0 +1,7 @@
+# `{name}`
+
+The tracking issue for this feature is: [#{issue}]
+
+[#{issue}]: https://github.com/rust-lang/rust/issues/{issue}
+
+------------------------
diff --git a/src/tools/unstable-book-gen/src/stub-no-issue.md b/src/tools/unstable-book-gen/src/stub-no-issue.md
new file mode 100644
index 00000000000..3da140633d0
--- /dev/null
+++ b/src/tools/unstable-book-gen/src/stub-no-issue.md
@@ -0,0 +1,5 @@
+# `{name}`
+
+This feature has no tracking issue, and is therefore likely internal to the compiler, not being intended for general use.
+
+------------------------