<commit_before>// 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
}

pub 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");
    }
}
<commit_msg>Privatize `unindent` function.<commit_after>// 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");
    }
}
<|endoftext|>


<commit_before>use crate::branching::BranchAnalysis;
use crate::config::{Config, RunType};
use crate::path_utils::{get_source_walker, is_source_file};
use lazy_static::lazy_static;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use regex::Regex;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::path::{Path, PathBuf};
use syn::spanned::Spanned;
use syn::*;
use tracing::{trace, warn};
use walkdir::WalkDir;

mod attributes;
mod expressions;
mod items;
mod macros;
mod statements;
#[cfg(test)]
mod tests;

pub(crate) mod prelude {
    pub(crate) use super::*;
    pub(crate) use attributes::*;
    pub(crate) use macros::*;
}

/// Enumeration representing which lines to ignore
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Lines {
    /// Ignore all lines in the file
    All,
    /// A single line to ignore in the file
    Line(usize),
}

/// Represents the results of analysis of a single file. Does not store the file
/// in question as this is expected to be maintained by the user.
#[derive(Clone, Debug, Default)]
pub struct LineAnalysis {
    /// This represents lines that should be ignored in coverage
    /// but may be identifed as coverable in the DWARF tables
    pub ignore: HashSet<Lines>,
    /// This represents lines that should be included in coverage
    /// But may be ignored. Doesn't make sense to cover ALL the lines so this
    /// is just an index.
    pub cover: HashSet<usize>,
    /// Some logical lines may be split between physical lines this shows the
    /// mapping from physical line to logical line to prevent false positives
    /// from expressions split across physical lines
    pub logical_lines: HashMap<usize, usize>,
}

/// When the `LineAnalysis` results are mapped to their files there needs to be
/// an easy way to get the information back. For the container used implement
/// this trait
pub trait SourceAnalysisQuery {
    /// Returns true if the line in the given file should be ignored
    fn should_ignore(&self, path: &Path, l: &usize) -> bool;
    /// Takes a path and line number and normalises it to the logical line
    /// that should be represented in the statistics
    fn normalise(&self, path: &Path, l: usize) -> (PathBuf, usize);
}

#[derive(Copy, Clone, Debug)]
pub(crate) enum SubResult {
    Ok,
    Unreachable,
}

impl SourceAnalysisQuery for HashMap<PathBuf, LineAnalysis> {
    fn should_ignore(&self, path: &Path, l: &usize) -> bool {
        if self.contains_key(path) {
            self.get(path).unwrap().should_ignore(*l)
        } else {
            false
        }
    }

    fn normalise(&self, path: &Path, l: usize) -> (PathBuf, usize) {
        let pb = path.to_path_buf();
        match self.get(path) {
            Some(s) => match s.logical_lines.get(&l) {
                Some(o) => (pb, *o),
                _ => (pb, l),
            },
            _ => (pb, l),
        }
    }
}

impl LineAnalysis {
    /// Creates a new LineAnalysis object
    fn new() -> LineAnalysis {
        Default::default()
    }

    /// Ignore all lines in the file
    pub fn ignore_all(&mut self) {
        self.ignore.clear();
        self.cover.clear();
        self.ignore.insert(Lines::All);
    }

    /// Ignore all tokens in the given token stream
    pub fn ignore_tokens<T>(&mut self, tokens: T)
    where
        T: ToTokens,
    {
        for token in tokens.into_token_stream() {
            self.ignore_span(token.span());
        }
    }

    /// Adds the lines of the provided span to the ignore set
    pub fn ignore_span(&mut self, span: Span) {
        // If we're already ignoring everything no need to ignore this span
        if !self.ignore.contains(&Lines::All) {
            for i in span.start().line..(span.end().line + 1) {
                self.ignore.insert(Lines::Line(i));
                if self.cover.contains(&i) {
                    self.cover.remove(&i);
                }
            }
        }
    }

    /// Cover all tokens in the given tokenstream
    pub fn cover_token_stream(&mut self, tokens: TokenStream, contents: Option<&str>) {
        for token in tokens {
            self.cover_span(token.span(), contents);
        }
    }

    /// Adds the lines of the provided span to the cover set
    pub fn cover_span(&mut self, span: Span, contents: Option<&str>) {
        // Not checking for Lines::All because I trust we've called cover_span
        // for a reason.
        let mut useful_lines: HashSet<usize> = HashSet::new();
        if let Some(ref c) = contents {
            lazy_static! {
                static ref SINGLE_LINE: Regex = Regex::new(r"\s*//").unwrap();
            }
            const MULTI_START: &str = "/*";
            const MULTI_END: &str = "*/";
            let len = span.end().line - span.start().line;
            let mut is_comment = false;
            for (i, line) in c.lines().enumerate().skip(span.start().line - 1).take(len) {
                let is_code = if line.contains(MULTI_START) {
                    if !line.contains(MULTI_END) {
                        is_comment = true;
                    }
                    false
                } else if is_comment {
                    if line.contains(MULTI_END) {
                        is_comment = false;
                    }
                    false
                } else {
                    true
                };
                if is_code && !SINGLE_LINE.is_match(line) {
                    useful_lines.insert(i + 1);
                }
            }
        }
        for i in span.start().line..(span.end().line + 1) {
            if !self.ignore.contains(&Lines::Line(i)) && useful_lines.contains(&i) {
                self.cover.insert(i);
            }
        }
    }

    /// Shows whether the line should be ignored by tarpaulin
    pub fn should_ignore(&self, line: usize) -> bool {
        self.ignore.contains(&Lines::Line(line)) || self.ignore.contains(&Lines::All)
    }

    /// Adds a line to the list of lines to ignore
    fn add_to_ignore(&mut self, lines: &[usize]) {
        if !self.ignore.contains(&Lines::All) {
            for l in lines {
                self.ignore.insert(Lines::Line(*l));
                if self.cover.contains(l) {
                    self.cover.remove(l);
                }
            }
        }
    }
}

pub struct SourceAnalysis {
    pub lines: HashMap<PathBuf, LineAnalysis>,
    pub branches: HashMap<PathBuf, BranchAnalysis>,
}

impl SourceAnalysis {
    pub fn new() -> Self {
        Self {
            lines: HashMap::new(),
            branches: HashMap::new(),
        }
    }

    pub fn get_line_analysis(&mut self, path: PathBuf) -> &mut LineAnalysis {
        self.lines.entry(path).or_insert_with(LineAnalysis::new)
    }

    pub fn get_branch_analysis(&mut self, path: PathBuf) -> &mut BranchAnalysis {
        self.branches
            .entry(path)
            .or_insert_with(BranchAnalysis::new)
    }

    pub fn get_analysis(config: &Config) -> Self {
        let mut result = Self::new();
        let mut ignored_files: HashSet<PathBuf> = HashSet::new();
        let root = config.root();

        for e in get_source_walker(config) {
            if !ignored_files.contains(e.path()) {
                result.analyse_package(e.path(), &root, &config, &mut ignored_files);
            } else {
                let mut analysis = LineAnalysis::new();
                analysis.ignore_all();
                result.lines.insert(e.path().to_path_buf(), analysis);
                ignored_files.remove(e.path());
            }
        }
        for e in &ignored_files {
            let mut analysis = LineAnalysis::new();
            analysis.ignore_all();
            result.lines.insert(e.to_path_buf(), analysis);
        }

        result.debug_printout(config);

        result
    }

    /// Analyses a package of the target crate.
    fn analyse_package(
        &mut self,
        path: &Path,
        root: &Path,
        config: &Config,
        filtered_files: &mut HashSet<PathBuf>,
    ) {
        if let Some(file) = path.to_str() {
            let skip_cause_test = config.ignore_tests && path.starts_with(root.join("tests"));
            let skip_cause_example = path.starts_with(root.join("examples"))
                && !config.run_types.contains(&RunType::Examples);
            if !(skip_cause_test || skip_cause_example) {
                let file = File::open(file);
                if let Ok(mut file) = file {
                    let mut content = String::new();
                    let res = file.read_to_string(&mut content);
                    if let Err(e) = res {
                        warn!("Unable to read file into string, skipping source analysis: {}", e);
                        return;
                    }
                    let file = parse_file(&content);
                    if let Ok(file) = file {
                        let ctx = Context {
                            config,
                            file_contents: &content,
                            file: path,
                            ignore_mods: RefCell::new(HashSet::new()),
                        };
                        if self.check_attr_list(&file.attrs, &ctx) {
                            self.find_ignorable_lines(&ctx);
                            self.process_items(&file.items, &ctx);

                            let mut ignored_files = ctx.ignore_mods.into_inner();
                            for f in ignored_files.drain() {
                                if f.is_file() {
                                    filtered_files.insert(f);
                                } else {
                                    let walker = WalkDir::new(f).into_iter();
                                    for e in
                                        walker.filter_map(|e| e.ok()).filter(|e| is_source_file(e))
                                    {
                                        filtered_files.insert(e.path().to_path_buf());
                                    }
                                }
                            }
                            maybe_ignore_first_line(path, &mut self.lines);
                        } else {
                            let analysis = self.get_line_analysis(path.to_path_buf());
                            analysis.ignore_span(file.span());
                        }
                    }
                }
            }
        }
    }

    /// Finds lines from the raw string which are ignorable.
    /// These are often things like close braces, semi colons that may regiser as
    /// false positives.
    fn find_ignorable_lines(&mut self, ctx: &Context) {
        lazy_static! {
            static ref IGNORABLE: Regex =
                Regex::new(r"^((\s*///)|([\[\]\{\}\(\)\s;\?,/]*$))").unwrap();
        }
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
        let lines = ctx
            .file_contents
            .lines()
            .enumerate()
            .filter(|&(_, x)| IGNORABLE.is_match(&x))
            .map(|(i, _)| i + 1)
            .collect::<Vec<usize>>();
        analysis.add_to_ignore(&lines);

        let lines = ctx
            .file_contents
            .lines()
            .enumerate()
            .filter(|&(_, x)| {
                let mut x = x.to_string();
                x.retain(|c| !c.is_whitespace());
                x == "}else{"
            })
            .map(|(i, _)| i + 1)
            .collect::<Vec<usize>>();
        analysis.add_to_ignore(&lines);
    }

    pub(crate) fn visit_generics(&mut self, generics: &Generics, ctx: &Context) {
        if let Some(ref wh) = generics.where_clause {
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
            analysis.ignore_tokens(wh);
        }
    }

    /// Printout a debug summary of the results of source analysis if debug logging
    /// is enabled
    pub fn debug_printout(&self, config: &Config) {
        if config.debug {
            for (ref path, ref analysis) in &self.lines {
                trace!(
                    "Source analysis for {}",
                    config.strip_base_dir(path).display()
                );
                let mut lines = Vec::new();
                for l in &analysis.ignore {
                    match l {
                        Lines::All => {
                            lines.clear();
                            trace!("All lines are ignorable");
                            break;
                        }
                        Lines::Line(i) => {
                            lines.push(i);
                        }
                    }
                }
                if !lines.is_empty() {
                    lines.sort();
                    trace!("Ignorable lines: {:?}", lines);
                    lines.clear()
                }
                for c in &analysis.cover {
                    lines.push(c);
                }

                if !lines.is_empty() {
                    lines.sort();
                    trace!("Coverable lines: {:?}", lines);
                }
            }
            if config.branch_coverage {
                trace!("Branch analysis");
                trace!("{:?}", self.branches);
            }
        }
    }
}

/// lib.rs:1 can often show up as a coverable line when it's not. This ignores
/// that line as long as it's not a real source line. This can also affect
/// the main files for binaries in a project as well.
fn maybe_ignore_first_line(file: &Path, result: &mut HashMap<PathBuf, LineAnalysis>) {
    if let Ok(f) = File::open(file) {
        let read_file = BufReader::new(f);
        if let Some(Ok(first)) = read_file.lines().next() {
            if !(first.starts_with("pub") || first.starts_with("fn")) {
                let file = file.to_path_buf();
                let line_analysis = result.entry(file).or_default();
                line_analysis.add_to_ignore(&[1]);
            }
        }
    }
}

/// Provides context to the source analysis stage including the tarpaulin
/// config and the source code being analysed.
pub(crate) struct Context<'a> {
    /// Program config
    config: &'a Config,
    /// Contents of the source file
    file_contents: &'a str,
    /// path to the file being analysed
    file: &'a Path,
    /// Other parts of context are immutable like tarpaulin config and users
    /// source code. This is discovered during hence use of interior mutability
    ignore_mods: RefCell<HashSet<PathBuf>>,
}
<commit_msg>Fix formatting<commit_after>use crate::branching::BranchAnalysis;
use crate::config::{Config, RunType};
use crate::path_utils::{get_source_walker, is_source_file};
use lazy_static::lazy_static;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use regex::Regex;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::path::{Path, PathBuf};
use syn::spanned::Spanned;
use syn::*;
use tracing::{trace, warn};
use walkdir::WalkDir;

mod attributes;
mod expressions;
mod items;
mod macros;
mod statements;
#[cfg(test)]
mod tests;

pub(crate) mod prelude {
    pub(crate) use super::*;
    pub(crate) use attributes::*;
    pub(crate) use macros::*;
}

/// Enumeration representing which lines to ignore
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Lines {
    /// Ignore all lines in the file
    All,
    /// A single line to ignore in the file
    Line(usize),
}

/// Represents the results of analysis of a single file. Does not store the file
/// in question as this is expected to be maintained by the user.
#[derive(Clone, Debug, Default)]
pub struct LineAnalysis {
    /// This represents lines that should be ignored in coverage
    /// but may be identifed as coverable in the DWARF tables
    pub ignore: HashSet<Lines>,
    /// This represents lines that should be included in coverage
    /// But may be ignored. Doesn't make sense to cover ALL the lines so this
    /// is just an index.
    pub cover: HashSet<usize>,
    /// Some logical lines may be split between physical lines this shows the
    /// mapping from physical line to logical line to prevent false positives
    /// from expressions split across physical lines
    pub logical_lines: HashMap<usize, usize>,
}

/// When the `LineAnalysis` results are mapped to their files there needs to be
/// an easy way to get the information back. For the container used implement
/// this trait
pub trait SourceAnalysisQuery {
    /// Returns true if the line in the given file should be ignored
    fn should_ignore(&self, path: &Path, l: &usize) -> bool;
    /// Takes a path and line number and normalises it to the logical line
    /// that should be represented in the statistics
    fn normalise(&self, path: &Path, l: usize) -> (PathBuf, usize);
}

#[derive(Copy, Clone, Debug)]
pub(crate) enum SubResult {
    Ok,
    Unreachable,
}

impl SourceAnalysisQuery for HashMap<PathBuf, LineAnalysis> {
    fn should_ignore(&self, path: &Path, l: &usize) -> bool {
        if self.contains_key(path) {
            self.get(path).unwrap().should_ignore(*l)
        } else {
            false
        }
    }

    fn normalise(&self, path: &Path, l: usize) -> (PathBuf, usize) {
        let pb = path.to_path_buf();
        match self.get(path) {
            Some(s) => match s.logical_lines.get(&l) {
                Some(o) => (pb, *o),
                _ => (pb, l),
            },
            _ => (pb, l),
        }
    }
}

impl LineAnalysis {
    /// Creates a new LineAnalysis object
    fn new() -> LineAnalysis {
        Default::default()
    }

    /// Ignore all lines in the file
    pub fn ignore_all(&mut self) {
        self.ignore.clear();
        self.cover.clear();
        self.ignore.insert(Lines::All);
    }

    /// Ignore all tokens in the given token stream
    pub fn ignore_tokens<T>(&mut self, tokens: T)
    where
        T: ToTokens,
    {
        for token in tokens.into_token_stream() {
            self.ignore_span(token.span());
        }
    }

    /// Adds the lines of the provided span to the ignore set
    pub fn ignore_span(&mut self, span: Span) {
        // If we're already ignoring everything no need to ignore this span
        if !self.ignore.contains(&Lines::All) {
            for i in span.start().line..(span.end().line + 1) {
                self.ignore.insert(Lines::Line(i));
                if self.cover.contains(&i) {
                    self.cover.remove(&i);
                }
            }
        }
    }

    /// Cover all tokens in the given tokenstream
    pub fn cover_token_stream(&mut self, tokens: TokenStream, contents: Option<&str>) {
        for token in tokens {
            self.cover_span(token.span(), contents);
        }
    }

    /// Adds the lines of the provided span to the cover set
    pub fn cover_span(&mut self, span: Span, contents: Option<&str>) {
        // Not checking for Lines::All because I trust we've called cover_span
        // for a reason.
        let mut useful_lines: HashSet<usize> = HashSet::new();
        if let Some(ref c) = contents {
            lazy_static! {
                static ref SINGLE_LINE: Regex = Regex::new(r"\s*//").unwrap();
            }
            const MULTI_START: &str = "/*";
            const MULTI_END: &str = "*/";
            let len = span.end().line - span.start().line;
            let mut is_comment = false;
            for (i, line) in c.lines().enumerate().skip(span.start().line - 1).take(len) {
                let is_code = if line.contains(MULTI_START) {
                    if !line.contains(MULTI_END) {
                        is_comment = true;
                    }
                    false
                } else if is_comment {
                    if line.contains(MULTI_END) {
                        is_comment = false;
                    }
                    false
                } else {
                    true
                };
                if is_code && !SINGLE_LINE.is_match(line) {
                    useful_lines.insert(i + 1);
                }
            }
        }
        for i in span.start().line..(span.end().line + 1) {
            if !self.ignore.contains(&Lines::Line(i)) && useful_lines.contains(&i) {
                self.cover.insert(i);
            }
        }
    }

    /// Shows whether the line should be ignored by tarpaulin
    pub fn should_ignore(&self, line: usize) -> bool {
        self.ignore.contains(&Lines::Line(line)) || self.ignore.contains(&Lines::All)
    }

    /// Adds a line to the list of lines to ignore
    fn add_to_ignore(&mut self, lines: &[usize]) {
        if !self.ignore.contains(&Lines::All) {
            for l in lines {
                self.ignore.insert(Lines::Line(*l));
                if self.cover.contains(l) {
                    self.cover.remove(l);
                }
            }
        }
    }
}

pub struct SourceAnalysis {
    pub lines: HashMap<PathBuf, LineAnalysis>,
    pub branches: HashMap<PathBuf, BranchAnalysis>,
}

impl SourceAnalysis {
    pub fn new() -> Self {
        Self {
            lines: HashMap::new(),
            branches: HashMap::new(),
        }
    }

    pub fn get_line_analysis(&mut self, path: PathBuf) -> &mut LineAnalysis {
        self.lines.entry(path).or_insert_with(LineAnalysis::new)
    }

    pub fn get_branch_analysis(&mut self, path: PathBuf) -> &mut BranchAnalysis {
        self.branches
            .entry(path)
            .or_insert_with(BranchAnalysis::new)
    }

    pub fn get_analysis(config: &Config) -> Self {
        let mut result = Self::new();
        let mut ignored_files: HashSet<PathBuf> = HashSet::new();
        let root = config.root();

        for e in get_source_walker(config) {
            if !ignored_files.contains(e.path()) {
                result.analyse_package(e.path(), &root, &config, &mut ignored_files);
            } else {
                let mut analysis = LineAnalysis::new();
                analysis.ignore_all();
                result.lines.insert(e.path().to_path_buf(), analysis);
                ignored_files.remove(e.path());
            }
        }
        for e in &ignored_files {
            let mut analysis = LineAnalysis::new();
            analysis.ignore_all();
            result.lines.insert(e.to_path_buf(), analysis);
        }

        result.debug_printout(config);

        result
    }

    /// Analyses a package of the target crate.
    fn analyse_package(
        &mut self,
        path: &Path,
        root: &Path,
        config: &Config,
        filtered_files: &mut HashSet<PathBuf>,
    ) {
        if let Some(file) = path.to_str() {
            let skip_cause_test = config.ignore_tests && path.starts_with(root.join("tests"));
            let skip_cause_example = path.starts_with(root.join("examples"))
                && !config.run_types.contains(&RunType::Examples);
            if !(skip_cause_test || skip_cause_example) {
                let file = File::open(file);
                if let Ok(mut file) = file {
                    let mut content = String::new();
                    let res = file.read_to_string(&mut content);
                    if let Err(e) = res {
                        warn!(
                            "Unable to read file into string, skipping source analysis: {}",
                            e
                        );
                        return;
                    }
                    let file = parse_file(&content);
                    if let Ok(file) = file {
                        let ctx = Context {
                            config,
                            file_contents: &content,
                            file: path,
                            ignore_mods: RefCell::new(HashSet::new()),
                        };
                        if self.check_attr_list(&file.attrs, &ctx) {
                            self.find_ignorable_lines(&ctx);
                            self.process_items(&file.items, &ctx);

                            let mut ignored_files = ctx.ignore_mods.into_inner();
                            for f in ignored_files.drain() {
                                if f.is_file() {
                                    filtered_files.insert(f);
                                } else {
                                    let walker = WalkDir::new(f).into_iter();
                                    for e in
                                        walker.filter_map(|e| e.ok()).filter(|e| is_source_file(e))
                                    {
                                        filtered_files.insert(e.path().to_path_buf());
                                    }
                                }
                            }
                            maybe_ignore_first_line(path, &mut self.lines);
                        } else {
                            let analysis = self.get_line_analysis(path.to_path_buf());
                            analysis.ignore_span(file.span());
                        }
                    }
                }
            }
        }
    }

    /// Finds lines from the raw string which are ignorable.
    /// These are often things like close braces, semi colons that may regiser as
    /// false positives.
    fn find_ignorable_lines(&mut self, ctx: &Context) {
        lazy_static! {
            static ref IGNORABLE: Regex =
                Regex::new(r"^((\s*///)|([\[\]\{\}\(\)\s;\?,/]*$))").unwrap();
        }
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
        let lines = ctx
            .file_contents
            .lines()
            .enumerate()
            .filter(|&(_, x)| IGNORABLE.is_match(&x))
            .map(|(i, _)| i + 1)
            .collect::<Vec<usize>>();
        analysis.add_to_ignore(&lines);

        let lines = ctx
            .file_contents
            .lines()
            .enumerate()
            .filter(|&(_, x)| {
                let mut x = x.to_string();
                x.retain(|c| !c.is_whitespace());
                x == "}else{"
            })
            .map(|(i, _)| i + 1)
            .collect::<Vec<usize>>();
        analysis.add_to_ignore(&lines);
    }

    pub(crate) fn visit_generics(&mut self, generics: &Generics, ctx: &Context) {
        if let Some(ref wh) = generics.where_clause {
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
            analysis.ignore_tokens(wh);
        }
    }

    /// Printout a debug summary of the results of source analysis if debug logging
    /// is enabled
    pub fn debug_printout(&self, config: &Config) {
        if config.debug {
            for (ref path, ref analysis) in &self.lines {
                trace!(
                    "Source analysis for {}",
                    config.strip_base_dir(path).display()
                );
                let mut lines = Vec::new();
                for l in &analysis.ignore {
                    match l {
                        Lines::All => {
                            lines.clear();
                            trace!("All lines are ignorable");
                            break;
                        }
                        Lines::Line(i) => {
                            lines.push(i);
                        }
                    }
                }
                if !lines.is_empty() {
                    lines.sort();
                    trace!("Ignorable lines: {:?}", lines);
                    lines.clear()
                }
                for c in &analysis.cover {
                    lines.push(c);
                }

                if !lines.is_empty() {
                    lines.sort();
                    trace!("Coverable lines: {:?}", lines);
                }
            }
            if config.branch_coverage {
                trace!("Branch analysis");
                trace!("{:?}", self.branches);
            }
        }
    }
}

/// lib.rs:1 can often show up as a coverable line when it's not. This ignores
/// that line as long as it's not a real source line. This can also affect
/// the main files for binaries in a project as well.
fn maybe_ignore_first_line(file: &Path, result: &mut HashMap<PathBuf, LineAnalysis>) {
    if let Ok(f) = File::open(file) {
        let read_file = BufReader::new(f);
        if let Some(Ok(first)) = read_file.lines().next() {
            if !(first.starts_with("pub") || first.starts_with("fn")) {
                let file = file.to_path_buf();
                let line_analysis = result.entry(file).or_default();
                line_analysis.add_to_ignore(&[1]);
            }
        }
    }
}

/// Provides context to the source analysis stage including the tarpaulin
/// config and the source code being analysed.
pub(crate) struct Context<'a> {
    /// Program config
    config: &'a Config,
    /// Contents of the source file
    file_contents: &'a str,
    /// path to the file being analysed
    file: &'a Path,
    /// Other parts of context are immutable like tarpaulin config and users
    /// source code. This is discovered during hence use of interior mutability
    ignore_mods: RefCell<HashSet<PathBuf>>,
}
<|endoftext|>


<commit_before>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

#![deny(
    non_camel_case_types,
    non_snake_case,
    path_statements,
    trivial_numeric_casts,
    unstable_features,
    unused_allocation,
    unused_import_braces,
    unused_imports,
    unused_must_use,
    unused_mut,
    unused_qualifications,
    while_true,
)]

extern crate clap;
#[macro_use] extern crate log;
#[macro_use] extern crate version;
extern crate toml;
extern crate toml_query;
extern crate kairos;
extern crate chrono;

extern crate libimaghabit;
extern crate libimagstore;
extern crate libimagrt;
extern crate libimagerror;
extern crate libimagutil;
extern crate libimagentrylist;

use std::process::exit;

use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagerror::trace::{MapErrTrace, trace_error};
use libimaghabit::store::HabitStore;
use libimaghabit::habit::builder::HabitBuilder;
use libimaghabit::habit::HabitTemplate;
use libimagstore::store::FileLockEntry;
use libimagentrylist::listers::table::TableLister;
use libimagentrylist::lister::Lister;

mod ui;

fn main() {
    let rt = generate_runtime_setup("imag-habit",
                                    &version!()[..],
                                    "Habit tracking tool",
                                    ui::build_ui);

    let _ = rt
        .cli()
        .subcommand_name()
        .map(|name| {
            debug!("Call {}", name);
            match name {
                "create" => create(&rt),
                "delete" => delete(&rt),
                "list"   => list(&rt),
                "today"  => today(&rt),
                "show"   => show(&rt),
                _        => {
                    debug!("Unknown command"); // More error handling
                    exit(1)
                },
            }
        })
        .unwrap_or_else(|| today(&rt));
}

fn create(rt: &Runtime) {
    let scmd = rt.cli().subcommand_matches("create").unwrap();                      // safe by call from main()
    let name = scmd.value_of("create-name").map(String::from).unwrap();             // safe by clap
    let recu = scmd.value_of("create-date-recurr-spec").map(String::from).unwrap(); // safe by clap
    let comm = scmd.value_of("create-comment").map(String::from).unwrap();          // safe by clap
    let date = scmd.value_of("create-date").unwrap();                               // safe by clap

    let date = match ::kairos::parser::parse(date).map_err_trace_exit_unwrap(1) {
        ::kairos::parser::Parsed::TimeType(tt) => match tt.get_moment() {
            Some(mom) => mom.date(),
            None => {
                error!("Error: 'date' parameter does not yield a point in time");
                exit(1);
            },
        },
        _ => {
            error!("Error: 'date' parameter does not yield a point in time");
            exit(1);
        },
    };

    let _ = HabitBuilder::default()
        .with_name(name)
        .with_basedate(date)
        .with_recurspec(recu)
        .with_comment(comm)
        .build(rt.store())
        .map_err_trace_exit_unwrap(1);

    info!("Ok");
}

fn delete(rt: &Runtime) {
    unimplemented!()
}

// Almost the same as `list()` but with other lister functions and an additional filter for only
// listing entries which are due today.
fn today(rt: &Runtime) {
    use libimaghabit::error::ResultExt;
    use libimaghabit::error::HabitErrorKind as HEK;

    fn lister_fn(h: &FileLockEntry) -> Vec<String> {
        debug!("Listing: {:?}", h);
        let name     = h.habit_name().map_err_trace_exit_unwrap(1);
        let basedate = h.habit_basedate().map_err_trace_exit_unwrap(1);
        let recur    = h.habit_recur_spec().map_err_trace_exit_unwrap(1);
        let comm     = h.habit_comment().map_err_trace_exit_unwrap(1);

        let v = vec![name, basedate, recur, comm];
        debug!(" -> {:?}", v);
        v
    }

    fn lister_header() -> Vec<String> {
        ["Name", "Basedate", "Recurr", "Comment"].iter().map(|x| String::from(*x)).collect()
    }

    let today = ::chrono::offset::Local::today().naive_local();

    let relevant : Vec<_> = { // scope, to have variable non-mutable in outer scope
        let mut relevant : Vec<_> = rt
            .store()
            .all_habit_templates()
            .map_err_trace_exit_unwrap(1)
            .filter_map(|id| match rt.store().get(id.clone()) {
                Ok(Some(h)) => Some(h),
                Ok(None) => {
                    error!("No habit found for {:?}", id);
                    None
                },
                Err(e) => {
                    trace_error(&e);
                    None
                },
            })
            .filter(|h| {
                let due = h.next_instance_date().map_err_trace_exit_unwrap(1);
                due == today || due > today // today or in future
            })
            .collect();

        relevant.sort_by_key(|h| h.next_instance_date().map_err_trace_exit_unwrap(1));
        relevant
    };

    let any_today_relevant = relevant
        .iter()
        .filter(|h| {
            let due = h.next_instance_date().map_err_trace_exit_unwrap(1);
            due == today // relevant today
        })
        .count() == 0;

    if any_today_relevant {
        let n = rt
            .cli()
            .subcommand_matches("today")
            .and_then(|am| {
                am.value_of("today-show-next-n")
                    .map(|x| {
                        x.parse::<usize>()
                            .chain_err(|| HEK::from(format!("Cannot parse String '{}' to integer", x)))
                            .map_err_trace_exit_unwrap(1)
                    })
            }).unwrap_or(5);

        info!("No Habits due today.");
        info!("Upcoming:");
        // list `n` which are relevant in the future.
        for element in relevant.iter().take(n) {
            let date = element.next_instance_date().map_err_trace_exit_unwrap(1);
            let name = element.habit_name().map_err_trace_exit_unwrap(1);

            info!(" * {date}: {name}", date = date, name = name);
        }
    } else {
        TableLister::new(lister_fn)
            .with_header(lister_header())
            .with_idx(true)
            .print_empty(false)
            .list(relevant.into_iter())
            .map_err_trace_exit_unwrap(1);
    }
}

fn list(rt: &Runtime) {
    fn lister_fn(h: &FileLockEntry) -> Vec<String> {
        debug!("Listing: {:?}", h);
        let name     = h.habit_name().map_err_trace_exit_unwrap(1);
        let basedate = h.habit_basedate().map_err_trace_exit_unwrap(1);
        let recur    = h.habit_recur_spec().map_err_trace_exit_unwrap(1);
        let comm     = h.habit_comment().map_err_trace_exit_unwrap(1);
        let due      = h.next_instance_date().map_err_trace_exit_unwrap(1);
        let due      = libimaghabit::util::date_to_string(&due);

        let v = vec![name, basedate, recur, comm, due];
        debug!(" -> {:?}", v);
        v
    }

    fn lister_header() -> Vec<String> {
        ["Name", "Basedate", "Recurr", "Comment", "Next Due"].iter().map(|x| String::from(*x)).collect()
    }

    let iter = rt
        .store()
        .all_habit_templates()
        .map_err_trace_exit_unwrap(1)
        .filter_map(|id| match rt.store().get(id.clone()) {
            Ok(Some(h)) => Some(h),
            Ok(None) => {
                error!("No habit found for {:?}", id);
                None
            },
            Err(e) => {
                trace_error(&e);
                None
            },
        });

    TableLister::new(lister_fn)
        .with_header(lister_header())
        .with_idx(true)
        .print_empty(false)
        .list(iter)
        .map_err_trace_exit_unwrap(1);
}

fn show(rt: &Runtime) {
    let scmd = rt.cli().subcommand_matches("show").unwrap();          // safe by call from main()
    let name = scmd
        .value_of("show-name")
        .map(String::from)
        .unwrap(); // safe by clap

    fn instance_lister_header() -> Vec<String> {
        ["Date", "Comment"].iter().map(|x| String::from(*x)).collect()
    }

    fn instance_lister_fn(i: &FileLockEntry) -> Vec<String> {
        use libimaghabit::util::date_to_string;
        use libimaghabit::instance::HabitInstance;

        let date = date_to_string(&i.get_date().map_err_trace_exit_unwrap(1));
        let comm = i.get_comment().map_err_trace_exit_unwrap(1);

        vec![date, comm]
    }

    let _ = rt
        .store()
        .all_habit_templates()
        .map_err_trace_exit_unwrap(1)
        .filter_map(|id| match rt.store().get(id.clone()) {
            Ok(Some(h)) => Some(h),
            Ok(None) => {
                error!("Cannot get habit for {:?} in 'show' subcommand", id);
                None
            },
            Err(e) => {
                trace_error(&e);
                None
            },
        })
        .filter(|h| h.habit_name().map(|n| name == n).map_err_trace_exit_unwrap(1))
        .enumerate()
        .map(|(i, habit)| {
            let name     = habit.habit_name().map_err_trace_exit_unwrap(1);
            let basedate = habit.habit_basedate().map_err_trace_exit_unwrap(1);
            let recur    = habit.habit_recur_spec().map_err_trace_exit_unwrap(1);
            let comm     = habit.habit_comment().map_err_trace_exit_unwrap(1);

            println!("{i} - {name}\nBase      : {b},\nRecurrence: {r}\nComment   : {c}\n",
                     i    = i,
                     name = name,
                     b    = basedate,
                     r    = recur,
                     c    = comm);

            let instances_iter = habit
                .linked_instances()
                .map_err_trace_exit_unwrap(1)
                .filter_map(|instance_id| {
                    debug!("Getting: {:?}", instance_id);
                    rt.store().get(instance_id).map_err_trace_exit_unwrap(1)
                });

            TableLister::new(instance_lister_fn)
                .with_header(instance_lister_header())
                .with_idx(true)
                .print_empty(false)
                .list(instances_iter)
                .map_err_trace_exit_unwrap(1);
        })
        .collect::<Vec<_>>();
}

<commit_msg>Impl delete()<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

#![deny(
    non_camel_case_types,
    non_snake_case,
    path_statements,
    trivial_numeric_casts,
    unstable_features,
    unused_allocation,
    unused_import_braces,
    unused_imports,
    unused_must_use,
    unused_mut,
    unused_qualifications,
    while_true,
)]

extern crate clap;
#[macro_use] extern crate log;
#[macro_use] extern crate version;
extern crate toml;
extern crate toml_query;
extern crate kairos;
extern crate chrono;

extern crate libimaghabit;
extern crate libimagstore;
extern crate libimagrt;
extern crate libimagerror;
extern crate libimagutil;
extern crate libimagentrylist;
extern crate libimaginteraction;

use std::process::exit;

use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagerror::trace::{MapErrTrace, trace_error};
use libimaghabit::store::HabitStore;
use libimaghabit::habit::builder::HabitBuilder;
use libimaghabit::habit::HabitTemplate;
use libimagstore::store::FileLockEntry;
use libimagentrylist::listers::table::TableLister;
use libimagentrylist::lister::Lister;
use libimaginteraction::ask::ask_bool;

mod ui;

fn main() {
    let rt = generate_runtime_setup("imag-habit",
                                    &version!()[..],
                                    "Habit tracking tool",
                                    ui::build_ui);

    let _ = rt
        .cli()
        .subcommand_name()
        .map(|name| {
            debug!("Call {}", name);
            match name {
                "create" => create(&rt),
                "delete" => delete(&rt),
                "list"   => list(&rt),
                "today"  => today(&rt),
                "show"   => show(&rt),
                _        => {
                    debug!("Unknown command"); // More error handling
                    exit(1)
                },
            }
        })
        .unwrap_or_else(|| today(&rt));
}

fn create(rt: &Runtime) {
    let scmd = rt.cli().subcommand_matches("create").unwrap();                      // safe by call from main()
    let name = scmd.value_of("create-name").map(String::from).unwrap();             // safe by clap
    let recu = scmd.value_of("create-date-recurr-spec").map(String::from).unwrap(); // safe by clap
    let comm = scmd.value_of("create-comment").map(String::from).unwrap();          // safe by clap
    let date = scmd.value_of("create-date").unwrap();                               // safe by clap

    let date = match ::kairos::parser::parse(date).map_err_trace_exit_unwrap(1) {
        ::kairos::parser::Parsed::TimeType(tt) => match tt.get_moment() {
            Some(mom) => mom.date(),
            None => {
                error!("Error: 'date' parameter does not yield a point in time");
                exit(1);
            },
        },
        _ => {
            error!("Error: 'date' parameter does not yield a point in time");
            exit(1);
        },
    };

    let _ = HabitBuilder::default()
        .with_name(name)
        .with_basedate(date)
        .with_recurspec(recu)
        .with_comment(comm)
        .build(rt.store())
        .map_err_trace_exit_unwrap(1);

    info!("Ok");
}

fn delete(rt: &Runtime) {
    use libimaghabit::instance::HabitInstance;

    let scmd = rt.cli().subcommand_matches("delete").unwrap();          // safe by call from main()
    let name = scmd.value_of("delete-name").map(String::from).unwrap(); // safe by clap
    let yes  = scmd.is_present("delete-yes");
    let delete_instances = scmd.is_present("delete-instances");

    let _ = rt
        .store()
        .all_habit_templates()
        .map_err_trace_exit_unwrap(1)
        .map(|sid| (sid.clone(), rt.store().get(sid).map_err_trace_exit_unwrap(1))) // get the FileLockEntry
        .filter(|&(_, ref habit)| match habit { // filter for name of habit == name we look for
            &Some(ref h) => h.habit_name().map_err_trace_exit_unwrap(1) == name,
            &None => false,
        })
        .filter_map(|(a, o)| o.map(|x| (a, x))) // map: (a, Option<b>) -> Option<(a, b)> -> (a, b)
        .map(|(sid, fle)| {
            if delete_instances {

                // if this does not succeed, we did something terribly wrong
                let t_name = fle.habit_name().map_err_trace_exit_unwrap(1);
                assert_eq!(t_name, name);

                let get_instance          =  |iid| rt.store().get(iid).map_err_trace_exit_unwrap(1);
                let has_template_name     =  |i: &FileLockEntry| t_name ==  i.get_template_name().map_err_trace_exit_unwrap(1);
                let instance_location     =  |i: FileLockEntry| i.get_location().clone();
                let delete_instance_by_id =  |id| {
                    let do_delete = |id| rt.store().delete(id).map_err_trace_exit_unwrap(1);
                    if !yes {
                        let q = format!("Really delete {}", id);
                        if ask_bool(&q, Some(false)) {
                            let _ = do_delete(id);
                        }
                    } else {
                        let _ = do_delete(id);
                    }
                };

                fle
                    .linked_instances()
                    .map_err_trace_exit_unwrap(1)
                    .filter_map(get_instance)
                    .filter(has_template_name)
                    .map(instance_location)
                    .map(delete_instance_by_id)
                    .collect::<Vec<_>>();
            }

            drop(fle);

            let do_delete_template = |sid| rt.store().delete(sid).map_err_trace_exit_unwrap(1);
            if !yes {
                let q = format!("Really delete template {}", sid);
                if ask_bool(&q, Some(false)) {
                    let _ = do_delete_template(sid);
                }
            } else {
                let _ = do_delete_template(sid);
            }
        })
        .collect::<Vec<_>>();

    info!("Done");
}

// Almost the same as `list()` but with other lister functions and an additional filter for only
// listing entries which are due today.
fn today(rt: &Runtime) {
    use libimaghabit::error::ResultExt;
    use libimaghabit::error::HabitErrorKind as HEK;

    fn lister_fn(h: &FileLockEntry) -> Vec<String> {
        debug!("Listing: {:?}", h);
        let name     = h.habit_name().map_err_trace_exit_unwrap(1);
        let basedate = h.habit_basedate().map_err_trace_exit_unwrap(1);
        let recur    = h.habit_recur_spec().map_err_trace_exit_unwrap(1);
        let comm     = h.habit_comment().map_err_trace_exit_unwrap(1);

        let v = vec![name, basedate, recur, comm];
        debug!(" -> {:?}", v);
        v
    }

    fn lister_header() -> Vec<String> {
        ["Name", "Basedate", "Recurr", "Comment"].iter().map(|x| String::from(*x)).collect()
    }

    let today = ::chrono::offset::Local::today().naive_local();

    let relevant : Vec<_> = { // scope, to have variable non-mutable in outer scope
        let mut relevant : Vec<_> = rt
            .store()
            .all_habit_templates()
            .map_err_trace_exit_unwrap(1)
            .filter_map(|id| match rt.store().get(id.clone()) {
                Ok(Some(h)) => Some(h),
                Ok(None) => {
                    error!("No habit found for {:?}", id);
                    None
                },
                Err(e) => {
                    trace_error(&e);
                    None
                },
            })
            .filter(|h| {
                let due = h.next_instance_date().map_err_trace_exit_unwrap(1);
                due == today || due > today // today or in future
            })
            .collect();

        relevant.sort_by_key(|h| h.next_instance_date().map_err_trace_exit_unwrap(1));
        relevant
    };

    let any_today_relevant = relevant
        .iter()
        .filter(|h| {
            let due = h.next_instance_date().map_err_trace_exit_unwrap(1);
            due == today // relevant today
        })
        .count() == 0;

    if any_today_relevant {
        let n = rt
            .cli()
            .subcommand_matches("today")
            .and_then(|am| {
                am.value_of("today-show-next-n")
                    .map(|x| {
                        x.parse::<usize>()
                            .chain_err(|| HEK::from(format!("Cannot parse String '{}' to integer", x)))
                            .map_err_trace_exit_unwrap(1)
                    })
            }).unwrap_or(5);

        info!("No Habits due today.");
        info!("Upcoming:");
        // list `n` which are relevant in the future.
        for element in relevant.iter().take(n) {
            let date = element.next_instance_date().map_err_trace_exit_unwrap(1);
            let name = element.habit_name().map_err_trace_exit_unwrap(1);

            info!(" * {date}: {name}", date = date, name = name);
        }
    } else {
        TableLister::new(lister_fn)
            .with_header(lister_header())
            .with_idx(true)
            .print_empty(false)
            .list(relevant.into_iter())
            .map_err_trace_exit_unwrap(1);
    }
}

fn list(rt: &Runtime) {
    fn lister_fn(h: &FileLockEntry) -> Vec<String> {
        debug!("Listing: {:?}", h);
        let name     = h.habit_name().map_err_trace_exit_unwrap(1);
        let basedate = h.habit_basedate().map_err_trace_exit_unwrap(1);
        let recur    = h.habit_recur_spec().map_err_trace_exit_unwrap(1);
        let comm     = h.habit_comment().map_err_trace_exit_unwrap(1);
        let due      = h.next_instance_date().map_err_trace_exit_unwrap(1);
        let due      = libimaghabit::util::date_to_string(&due);

        let v = vec![name, basedate, recur, comm, due];
        debug!(" -> {:?}", v);
        v
    }

    fn lister_header() -> Vec<String> {
        ["Name", "Basedate", "Recurr", "Comment", "Next Due"].iter().map(|x| String::from(*x)).collect()
    }

    let iter = rt
        .store()
        .all_habit_templates()
        .map_err_trace_exit_unwrap(1)
        .filter_map(|id| match rt.store().get(id.clone()) {
            Ok(Some(h)) => Some(h),
            Ok(None) => {
                error!("No habit found for {:?}", id);
                None
            },
            Err(e) => {
                trace_error(&e);
                None
            },
        });

    TableLister::new(lister_fn)
        .with_header(lister_header())
        .with_idx(true)
        .print_empty(false)
        .list(iter)
        .map_err_trace_exit_unwrap(1);
}

fn show(rt: &Runtime) {
    let scmd = rt.cli().subcommand_matches("show").unwrap();          // safe by call from main()
    let name = scmd
        .value_of("show-name")
        .map(String::from)
        .unwrap(); // safe by clap

    fn instance_lister_header() -> Vec<String> {
        ["Date", "Comment"].iter().map(|x| String::from(*x)).collect()
    }

    fn instance_lister_fn(i: &FileLockEntry) -> Vec<String> {
        use libimaghabit::util::date_to_string;
        use libimaghabit::instance::HabitInstance;

        let date = date_to_string(&i.get_date().map_err_trace_exit_unwrap(1));
        let comm = i.get_comment().map_err_trace_exit_unwrap(1);

        vec![date, comm]
    }

    let _ = rt
        .store()
        .all_habit_templates()
        .map_err_trace_exit_unwrap(1)
        .filter_map(|id| match rt.store().get(id.clone()) {
            Ok(Some(h)) => Some(h),
            Ok(None) => {
                error!("Cannot get habit for {:?} in 'show' subcommand", id);
                None
            },
            Err(e) => {
                trace_error(&e);
                None
            },
        })
        .filter(|h| h.habit_name().map(|n| name == n).map_err_trace_exit_unwrap(1))
        .enumerate()
        .map(|(i, habit)| {
            let name     = habit.habit_name().map_err_trace_exit_unwrap(1);
            let basedate = habit.habit_basedate().map_err_trace_exit_unwrap(1);
            let recur    = habit.habit_recur_spec().map_err_trace_exit_unwrap(1);
            let comm     = habit.habit_comment().map_err_trace_exit_unwrap(1);

            println!("{i} - {name}\nBase      : {b},\nRecurrence: {r}\nComment   : {c}\n",
                     i    = i,
                     name = name,
                     b    = basedate,
                     r    = recur,
                     c    = comm);

            let instances_iter = habit
                .linked_instances()
                .map_err_trace_exit_unwrap(1)
                .filter_map(|instance_id| {
                    debug!("Getting: {:?}", instance_id);
                    rt.store().get(instance_id).map_err_trace_exit_unwrap(1)
                });

            TableLister::new(instance_lister_fn)
                .with_header(instance_lister_header())
                .with_idx(true)
                .print_empty(false)
                .list(instances_iter)
                .map_err_trace_exit_unwrap(1);
        })
        .collect::<Vec<_>>();
}

<|endoftext|>