+++ /dev/null
-// Copyright 2012-2014 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.
-
-use rustc::hir::def_id::DefId;
-use rustc::middle::privacy::AccessLevels;
-use rustc::util::nodemap::DefIdSet;
-use std::cmp;
-use std::mem;
-use std::string::String;
-use std::usize;
-
-use clean::{self, Attributes, GetDefId};
-use clean::Item;
-use plugins;
-use fold;
-use fold::DocFolder;
-use fold::FoldItem::Strip;
-
-/// Strip items marked `#[doc(hidden)]`
-pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
- let mut retained = DefIdSet();
-
- // strip all #[doc(hidden)] items
- let krate = {
- struct Stripper<'a> {
- retained: &'a mut DefIdSet,
- update_retained: bool,
- }
- impl<'a> fold::DocFolder for Stripper<'a> {
- fn fold_item(&mut self, i: Item) -> Option<Item> {
- if i.attrs.list("doc").has_word("hidden") {
- debug!("found one in strip_hidden; removing");
- // use a dedicated hidden item for given item type if any
- match i.inner {
- clean::StructFieldItem(..) | clean::ModuleItem(..) => {
- // We need to recurse into stripped modules to
- // strip things like impl methods but when doing so
- // we must not add any items to the `retained` set.
- let old = mem::replace(&mut self.update_retained, false);
- let ret = Strip(self.fold_item_recur(i).unwrap()).fold();
- self.update_retained = old;
- return ret;
- }
- _ => return None,
- }
- } else {
- if self.update_retained {
- self.retained.insert(i.def_id);
- }
- }
- self.fold_item_recur(i)
- }
- }
- let mut stripper = Stripper{ retained: &mut retained, update_retained: true };
- stripper.fold_crate(krate)
- };
-
- // strip all impls referencing stripped items
- let mut stripper = ImplStripper { retained: &retained };
- stripper.fold_crate(krate)
-}
-
-/// Strip private items from the point of view of a crate or externally from a
-/// crate, specified by the `xcrate` flag.
-pub fn strip_private(mut krate: clean::Crate) -> plugins::PluginResult {
- // This stripper collects all *retained* nodes.
- let mut retained = DefIdSet();
- let access_levels = krate.access_levels.clone();
-
- // strip all private items
- {
- let mut stripper = Stripper {
- retained: &mut retained,
- access_levels: &access_levels,
- update_retained: true,
- };
- krate = ImportStripper.fold_crate(stripper.fold_crate(krate));
- }
-
- // strip all impls referencing private items
- let mut stripper = ImplStripper { retained: &retained };
- stripper.fold_crate(krate)
-}
-
-struct Stripper<'a> {
- retained: &'a mut DefIdSet,
- access_levels: &'a AccessLevels<DefId>,
- update_retained: bool,
-}
-
-impl<'a> fold::DocFolder for Stripper<'a> {
- fn fold_item(&mut self, i: Item) -> Option<Item> {
- match i.inner {
- clean::StrippedItem(..) => {
- // We need to recurse into stripped modules to strip things
- // like impl methods but when doing so we must not add any
- // items to the `retained` set.
- let old = mem::replace(&mut self.update_retained, false);
- let ret = self.fold_item_recur(i);
- self.update_retained = old;
- return ret;
- }
- // These items can all get re-exported
- clean::TypedefItem(..) | clean::StaticItem(..) |
- clean::StructItem(..) | clean::EnumItem(..) |
- clean::TraitItem(..) | clean::FunctionItem(..) |
- clean::VariantItem(..) | clean::MethodItem(..) |
- clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) |
- clean::ConstantItem(..) | clean::UnionItem(..) => {
- if i.def_id.is_local() {
- if !self.access_levels.is_exported(i.def_id) {
- return None;
- }
- }
- }
-
- clean::StructFieldItem(..) => {
- if i.visibility != Some(clean::Public) {
- return Strip(i).fold();
- }
- }
-
- clean::ModuleItem(..) => {
- if i.def_id.is_local() && i.visibility != Some(clean::Public) {
- let old = mem::replace(&mut self.update_retained, false);
- let ret = Strip(self.fold_item_recur(i).unwrap()).fold();
- self.update_retained = old;
- return ret;
- }
- }
-
- // handled in the `strip-priv-imports` pass
- clean::ExternCrateItem(..) | clean::ImportItem(..) => {}
-
- clean::DefaultImplItem(..) | clean::ImplItem(..) => {}
-
- // tymethods/macros have no control over privacy
- clean::MacroItem(..) | clean::TyMethodItem(..) => {}
-
- // Primitives are never stripped
- clean::PrimitiveItem(..) => {}
-
- // Associated consts and types are never stripped
- clean::AssociatedConstItem(..) |
- clean::AssociatedTypeItem(..) => {}
- }
-
- let fastreturn = match i.inner {
- // nothing left to do for traits (don't want to filter their
- // methods out, visibility controlled by the trait)
- clean::TraitItem(..) => true,
-
- // implementations of traits are always public.
- clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
- // Struct variant fields have inherited visibility
- clean::VariantItem(clean::Variant {
- kind: clean::StructVariant(..)
- }) => true,
- _ => false,
- };
-
- let i = if fastreturn {
- if self.update_retained {
- self.retained.insert(i.def_id);
- }
- return Some(i);
- } else {
- self.fold_item_recur(i)
- };
-
- i.and_then(|i| {
- match i.inner {
- // emptied modules have no need to exist
- clean::ModuleItem(ref m)
- if m.items.is_empty() &&
- i.doc_value().is_none() => None,
- _ => {
- if self.update_retained {
- self.retained.insert(i.def_id);
- }
- Some(i)
- }
- }
- })
- }
-}
-
-// This stripper discards all impls which reference stripped items
-struct ImplStripper<'a> {
- retained: &'a DefIdSet
-}
-
-impl<'a> fold::DocFolder for ImplStripper<'a> {
- fn fold_item(&mut self, i: Item) -> Option<Item> {
- if let clean::ImplItem(ref imp) = i.inner {
- // emptied none trait impls can be stripped
- if imp.trait_.is_none() && imp.items.is_empty() {
- return None;
- }
- if let Some(did) = imp.for_.def_id() {
- if did.is_local() && !imp.for_.is_generic() &&
- !self.retained.contains(&did)
- {
- return None;
- }
- }
- if let Some(did) = imp.trait_.def_id() {
- if did.is_local() && !self.retained.contains(&did) {
- return None;
- }
- }
- }
- self.fold_item_recur(i)
- }
-}
-
-// This stripper discards all private import statements (`use`, `extern crate`)
-struct ImportStripper;
-impl fold::DocFolder for ImportStripper {
- fn fold_item(&mut self, i: Item) -> Option<Item> {
- match i.inner {
- clean::ExternCrateItem(..) |
- clean::ImportItem(..) if i.visibility != Some(clean::Public) => None,
- _ => self.fold_item_recur(i)
- }
- }
-}
-
-pub fn strip_priv_imports(krate: clean::Crate) -> plugins::PluginResult {
- ImportStripper.fold_crate(krate)
-}
-
-pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
- struct CommentCleaner;
- impl fold::DocFolder for CommentCleaner {
- fn fold_item(&mut self, mut i: Item) -> Option<Item> {
- let mut avec: Vec<clean::Attribute> = Vec::new();
- for attr in &i.attrs {
- match attr {
- &clean::NameValue(ref x, ref s)
- if "doc" == *x => {
- avec.push(clean::NameValue("doc".to_string(),
- unindent(s)))
- }
- x => avec.push(x.clone())
- }
- }
- i.attrs = avec;
- self.fold_item_recur(i)
- }
- }
- let mut cleaner = CommentCleaner;
- let krate = cleaner.fold_crate(krate);
- krate
-}
-
-pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
- struct Collapser;
- impl fold::DocFolder for Collapser {
- fn fold_item(&mut self, mut i: Item) -> Option<Item> {
- let mut docstr = String::new();
- for attr in &i.attrs {
- if let clean::NameValue(ref x, ref s) = *attr {
- if "doc" == *x {
- docstr.push_str(s);
- docstr.push('\n');
- }
- }
- }
- let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
- &clean::NameValue(ref x, _) if "doc" == *x => false,
- _ => true
- }).cloned().collect();
- if !docstr.is_empty() {
- a.push(clean::NameValue("doc".to_string(), docstr));
- }
- i.attrs = a;
- self.fold_item_recur(i)
- }
- }
- let mut collapser = Collapser;
- let krate = collapser.fold_crate(krate);
- krate
-}
-
-fn unindent(s: &str) -> String {
- let lines = s.lines().collect::<Vec<&str> >();
- let mut saw_first_line = false;
- let mut saw_second_line = false;
- let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| {
-
- // After we see the first non-whitespace line, look at
- // the line we have. If it is not whitespace, and therefore
- // part of the first paragraph, then ignore the indentation
- // level of the first line
- let ignore_previous_indents =
- saw_first_line &&
- !saw_second_line &&
- !line.chars().all(|c| c.is_whitespace());
-
- let min_indent = if ignore_previous_indents {
- usize::MAX
- } else {
- min_indent
- };
-
- if saw_first_line {
- saw_second_line = true;
- }
-
- if line.chars().all(|c| c.is_whitespace()) {
- min_indent
- } else {
- saw_first_line = true;
- let mut whitespace = 0;
- line.chars().all(|char| {
- // Compare against either space or tab, ignoring whether they
- // are mixed or not
- if char == ' ' || char == '\t' {
- whitespace += 1;
- true
- } else {
- false
- }
- });
- cmp::min(min_indent, whitespace)
- }
- });
-
- if !lines.is_empty() {
- let mut unindented = vec![ lines[0].trim().to_string() ];
- unindented.extend_from_slice(&lines[1..].iter().map(|&line| {
- if line.chars().all(|c| c.is_whitespace()) {
- line.to_string()
- } else {
- assert!(line.len() >= min_indent);
- line[min_indent..].to_string()
- }
- }).collect::<Vec<_>>());
- unindented.join("\n")
- } else {
- s.to_string()
- }
-}
-
-#[cfg(test)]
-mod unindent_tests {
- use super::unindent;
-
- #[test]
- fn should_unindent() {
- let s = " line1\n line2".to_string();
- let r = unindent(&s);
- assert_eq!(r, "line1\nline2");
- }
-
- #[test]
- fn should_unindent_multiple_paragraphs() {
- let s = " line1\n\n line2".to_string();
- let r = unindent(&s);
- assert_eq!(r, "line1\n\nline2");
- }
-
- #[test]
- fn should_leave_multiple_indent_levels() {
- // Line 2 is indented another level beyond the
- // base indentation and should be preserved
- let s = " line1\n\n line2".to_string();
- let r = unindent(&s);
- assert_eq!(r, "line1\n\n line2");
- }
-
- #[test]
- fn should_ignore_first_line_indent() {
- // The first line of the first paragraph may not be indented as
- // far due to the way the doc string was written:
- //
- // #[doc = "Start way over here
- // and continue here"]
- let s = "line1\n line2".to_string();
- let r = unindent(&s);
- assert_eq!(r, "line1\nline2");
- }
-
- #[test]
- fn should_not_ignore_first_line_indent_in_a_single_line_para() {
- let s = "line1\n\n line2".to_string();
- let r = unindent(&s);
- assert_eq!(r, "line1\n\n line2");
- }
-
- #[test]
- fn should_unindent_tabs() {
- let s = "\tline1\n\tline2".to_string();
- let r = unindent(&s);
- assert_eq!(r, "line1\nline2");
- }
-
- #[test]
- fn should_trim_mixed_indentation() {
- let s = "\t line1\n\t line2".to_string();
- let r = unindent(&s);
- assert_eq!(r, "line1\nline2");
-
- let s = " \tline1\n \tline2".to_string();
- let r = unindent(&s);
- assert_eq!(r, "line1\nline2");
- }
-}
--- /dev/null
+// Copyright 2012-2014 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.
+
+use std::string::String;
+
+use clean::{self, Item};
+use plugins;
+use fold;
+use fold::DocFolder;
+
+pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
+ struct Collapser;
+ impl fold::DocFolder for Collapser {
+ fn fold_item(&mut self, mut i: Item) -> Option<Item> {
+ let mut docstr = String::new();
+ for attr in &i.attrs {
+ if let clean::NameValue(ref x, ref s) = *attr {
+ if "doc" == *x {
+ docstr.push_str(s);
+ docstr.push('\n');
+ }
+ }
+ }
+ let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
+ &clean::NameValue(ref x, _) if "doc" == *x => false,
+ _ => true
+ }).cloned().collect();
+ if !docstr.is_empty() {
+ a.push(clean::NameValue("doc".to_string(), docstr));
+ }
+ i.attrs = a;
+ self.fold_item_recur(i)
+ }
+ }
+ let mut collapser = Collapser;
+ let krate = collapser.fold_crate(krate);
+ krate
+}
--- /dev/null
+// Copyright 2012-2014 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.
+
+use rustc::hir::def_id::DefId;
+use rustc::middle::privacy::AccessLevels;
+use rustc::util::nodemap::DefIdSet;
+use std::mem;
+
+use clean::{self, GetDefId, Item};
+use fold;
+use fold::FoldItem::Strip;
+
+mod collapse_docs;
+pub use self::collapse_docs::collapse_docs;
+
+mod strip_hidden;
+pub use self::strip_hidden::strip_hidden;
+
+mod strip_private;
+pub use self::strip_private::strip_private;
+
+mod strip_priv_imports;
+pub use self::strip_priv_imports::strip_priv_imports;
+
+mod unindent_comments;
+pub use self::unindent_comments::unindent_comments;
+
+struct Stripper<'a> {
+ retained: &'a mut DefIdSet,
+ access_levels: &'a AccessLevels<DefId>,
+ update_retained: bool,
+}
+
+impl<'a> fold::DocFolder for Stripper<'a> {
+ fn fold_item(&mut self, i: Item) -> Option<Item> {
+ match i.inner {
+ clean::StrippedItem(..) => {
+ // We need to recurse into stripped modules to strip things
+ // like impl methods but when doing so we must not add any
+ // items to the `retained` set.
+ let old = mem::replace(&mut self.update_retained, false);
+ let ret = self.fold_item_recur(i);
+ self.update_retained = old;
+ return ret;
+ }
+ // These items can all get re-exported
+ clean::TypedefItem(..) | clean::StaticItem(..) |
+ clean::StructItem(..) | clean::EnumItem(..) |
+ clean::TraitItem(..) | clean::FunctionItem(..) |
+ clean::VariantItem(..) | clean::MethodItem(..) |
+ clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) |
+ clean::ConstantItem(..) | clean::UnionItem(..) => {
+ if i.def_id.is_local() {
+ if !self.access_levels.is_exported(i.def_id) {
+ return None;
+ }
+ }
+ }
+
+ clean::StructFieldItem(..) => {
+ if i.visibility != Some(clean::Public) {
+ return Strip(i).fold();
+ }
+ }
+
+ clean::ModuleItem(..) => {
+ if i.def_id.is_local() && i.visibility != Some(clean::Public) {
+ let old = mem::replace(&mut self.update_retained, false);
+ let ret = Strip(self.fold_item_recur(i).unwrap()).fold();
+ self.update_retained = old;
+ return ret;
+ }
+ }
+
+ // handled in the `strip-priv-imports` pass
+ clean::ExternCrateItem(..) | clean::ImportItem(..) => {}
+
+ clean::DefaultImplItem(..) | clean::ImplItem(..) => {}
+
+ // tymethods/macros have no control over privacy
+ clean::MacroItem(..) | clean::TyMethodItem(..) => {}
+
+ // Primitives are never stripped
+ clean::PrimitiveItem(..) => {}
+
+ // Associated consts and types are never stripped
+ clean::AssociatedConstItem(..) |
+ clean::AssociatedTypeItem(..) => {}
+ }
+
+ let fastreturn = match i.inner {
+ // nothing left to do for traits (don't want to filter their
+ // methods out, visibility controlled by the trait)
+ clean::TraitItem(..) => true,
+
+ // implementations of traits are always public.
+ clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
+ // Struct variant fields have inherited visibility
+ clean::VariantItem(clean::Variant {
+ kind: clean::StructVariant(..)
+ }) => true,
+ _ => false,
+ };
+
+ let i = if fastreturn {
+ if self.update_retained {
+ self.retained.insert(i.def_id);
+ }
+ return Some(i);
+ } else {
+ self.fold_item_recur(i)
+ };
+
+ i.and_then(|i| {
+ match i.inner {
+ // emptied modules have no need to exist
+ clean::ModuleItem(ref m)
+ if m.items.is_empty() &&
+ i.doc_value().is_none() => None,
+ _ => {
+ if self.update_retained {
+ self.retained.insert(i.def_id);
+ }
+ Some(i)
+ }
+ }
+ })
+ }
+}
+
+// This stripper discards all impls which reference stripped items
+struct ImplStripper<'a> {
+ retained: &'a DefIdSet
+}
+
+impl<'a> fold::DocFolder for ImplStripper<'a> {
+ fn fold_item(&mut self, i: Item) -> Option<Item> {
+ if let clean::ImplItem(ref imp) = i.inner {
+ // emptied none trait impls can be stripped
+ if imp.trait_.is_none() && imp.items.is_empty() {
+ return None;
+ }
+ if let Some(did) = imp.for_.def_id() {
+ if did.is_local() && !imp.for_.is_generic() &&
+ !self.retained.contains(&did)
+ {
+ return None;
+ }
+ }
+ if let Some(did) = imp.trait_.def_id() {
+ if did.is_local() && !self.retained.contains(&did) {
+ return None;
+ }
+ }
+ }
+ self.fold_item_recur(i)
+ }
+}
+
+// This stripper discards all private import statements (`use`, `extern crate`)
+struct ImportStripper;
+impl fold::DocFolder for ImportStripper {
+ fn fold_item(&mut self, i: Item) -> Option<Item> {
+ match i.inner {
+ clean::ExternCrateItem(..) |
+ clean::ImportItem(..) if i.visibility != Some(clean::Public) => None,
+ _ => self.fold_item_recur(i)
+ }
+ }
+}
--- /dev/null
+// Copyright 2012-2014 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.
+
+use rustc::util::nodemap::DefIdSet;
+use std::mem;
+
+use clean::{self, Attributes};
+use clean::Item;
+use plugins;
+use fold;
+use fold::DocFolder;
+use fold::FoldItem::Strip;
+use passes::ImplStripper;
+
+/// Strip items marked `#[doc(hidden)]`
+pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
+ let mut retained = DefIdSet();
+
+ // strip all #[doc(hidden)] items
+ let krate = {
+ struct Stripper<'a> {
+ retained: &'a mut DefIdSet,
+ update_retained: bool,
+ }
+ impl<'a> fold::DocFolder for Stripper<'a> {
+ fn fold_item(&mut self, i: Item) -> Option<Item> {
+ if i.attrs.list("doc").has_word("hidden") {
+ debug!("found one in strip_hidden; removing");
+ // use a dedicated hidden item for given item type if any
+ match i.inner {
+ clean::StructFieldItem(..) | clean::ModuleItem(..) => {
+ // We need to recurse into stripped modules to
+ // strip things like impl methods but when doing so
+ // we must not add any items to the `retained` set.
+ let old = mem::replace(&mut self.update_retained, false);
+ let ret = Strip(self.fold_item_recur(i).unwrap()).fold();
+ self.update_retained = old;
+ return ret;
+ }
+ _ => return None,
+ }
+ } else {
+ if self.update_retained {
+ self.retained.insert(i.def_id);
+ }
+ }
+ self.fold_item_recur(i)
+ }
+ }
+ let mut stripper = Stripper{ retained: &mut retained, update_retained: true };
+ stripper.fold_crate(krate)
+ };
+
+ // strip all impls referencing stripped items
+ let mut stripper = ImplStripper { retained: &retained };
+ stripper.fold_crate(krate)
+}
--- /dev/null
+// Copyright 2012-2014 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.
+
+use clean;
+use fold::DocFolder;
+use plugins;
+use passes::ImportStripper;
+
+pub fn strip_priv_imports(krate: clean::Crate) -> plugins::PluginResult {
+ ImportStripper.fold_crate(krate)
+}
--- /dev/null
+// Copyright 2012-2014 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.
+
+use rustc::util::nodemap::DefIdSet;
+
+use clean;
+use plugins;
+use fold::DocFolder;
+use passes::{ImplStripper, ImportStripper, Stripper};
+
+/// Strip private items from the point of view of a crate or externally from a
+/// crate, specified by the `xcrate` flag.
+pub fn strip_private(mut krate: clean::Crate) -> plugins::PluginResult {
+ // This stripper collects all *retained* nodes.
+ let mut retained = DefIdSet();
+ let access_levels = krate.access_levels.clone();
+
+ // strip all private items
+ {
+ let mut stripper = Stripper {
+ retained: &mut retained,
+ access_levels: &access_levels,
+ update_retained: true,
+ };
+ krate = ImportStripper.fold_crate(stripper.fold_crate(krate));
+ }
+
+ // strip all impls referencing private items
+ let mut stripper = ImplStripper { retained: &retained };
+ stripper.fold_crate(krate)
+}
--- /dev/null
+// Copyright 2012-2014 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.
+
+use std::cmp;
+use std::string::String;
+use std::usize;
+
+use clean::{self, Item};
+use plugins;
+use fold::{self, DocFolder};
+
+pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
+ struct CommentCleaner;
+ impl fold::DocFolder for CommentCleaner {
+ fn fold_item(&mut self, mut i: Item) -> Option<Item> {
+ let mut avec: Vec<clean::Attribute> = Vec::new();
+ for attr in &i.attrs {
+ match attr {
+ &clean::NameValue(ref x, ref s)
+ if "doc" == *x => {
+ avec.push(clean::NameValue("doc".to_string(),
+ unindent(s)))
+ }
+ x => avec.push(x.clone())
+ }
+ }
+ i.attrs = avec;
+ self.fold_item_recur(i)
+ }
+ }
+ let mut cleaner = CommentCleaner;
+ let krate = cleaner.fold_crate(krate);
+ krate
+}
+
+fn unindent(s: &str) -> String {
+ let lines = s.lines().collect::<Vec<&str> >();
+ let mut saw_first_line = false;
+ let mut saw_second_line = false;
+ let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| {
+
+ // After we see the first non-whitespace line, look at
+ // the line we have. If it is not whitespace, and therefore
+ // part of the first paragraph, then ignore the indentation
+ // level of the first line
+ let ignore_previous_indents =
+ saw_first_line &&
+ !saw_second_line &&
+ !line.chars().all(|c| c.is_whitespace());
+
+ let min_indent = if ignore_previous_indents {
+ usize::MAX
+ } else {
+ min_indent
+ };
+
+ if saw_first_line {
+ saw_second_line = true;
+ }
+
+ if line.chars().all(|c| c.is_whitespace()) {
+ min_indent
+ } else {
+ saw_first_line = true;
+ let mut whitespace = 0;
+ line.chars().all(|char| {
+ // Compare against either space or tab, ignoring whether they
+ // are mixed or not
+ if char == ' ' || char == '\t' {
+ whitespace += 1;
+ true
+ } else {
+ false
+ }
+ });
+ cmp::min(min_indent, whitespace)
+ }
+ });
+
+ if !lines.is_empty() {
+ let mut unindented = vec![ lines[0].trim().to_string() ];
+ unindented.extend_from_slice(&lines[1..].iter().map(|&line| {
+ if line.chars().all(|c| c.is_whitespace()) {
+ line.to_string()
+ } else {
+ assert!(line.len() >= min_indent);
+ line[min_indent..].to_string()
+ }
+ }).collect::<Vec<_>>());
+ unindented.join("\n")
+ } else {
+ s.to_string()
+ }
+}
+
+#[cfg(test)]
+mod unindent_tests {
+ use super::unindent;
+
+ #[test]
+ fn should_unindent() {
+ let s = " line1\n line2".to_string();
+ let r = unindent(&s);
+ assert_eq!(r, "line1\nline2");
+ }
+
+ #[test]
+ fn should_unindent_multiple_paragraphs() {
+ let s = " line1\n\n line2".to_string();
+ let r = unindent(&s);
+ assert_eq!(r, "line1\n\nline2");
+ }
+
+ #[test]
+ fn should_leave_multiple_indent_levels() {
+ // Line 2 is indented another level beyond the
+ // base indentation and should be preserved
+ let s = " line1\n\n line2".to_string();
+ let r = unindent(&s);
+ assert_eq!(r, "line1\n\n line2");
+ }
+
+ #[test]
+ fn should_ignore_first_line_indent() {
+ // The first line of the first paragraph may not be indented as
+ // far due to the way the doc string was written:
+ //
+ // #[doc = "Start way over here
+ // and continue here"]
+ let s = "line1\n line2".to_string();
+ let r = unindent(&s);
+ assert_eq!(r, "line1\nline2");
+ }
+
+ #[test]
+ fn should_not_ignore_first_line_indent_in_a_single_line_para() {
+ let s = "line1\n\n line2".to_string();
+ let r = unindent(&s);
+ assert_eq!(r, "line1\n\n line2");
+ }
+
+ #[test]
+ fn should_unindent_tabs() {
+ let s = "\tline1\n\tline2".to_string();
+ let r = unindent(&s);
+ assert_eq!(r, "line1\nline2");
+ }
+
+ #[test]
+ fn should_trim_mixed_indentation() {
+ let s = "\t line1\n\t line2".to_string();
+ let r = unindent(&s);
+ assert_eq!(r, "line1\nline2");
+
+ let s = " \tline1\n \tline2".to_string();
+ let r = unindent(&s);
+ assert_eq!(r, "line1\nline2");
+ }
+}