<commit_before>use std::collections::btree_map::Entry;
use std::collections::*;
use std::fmt::{Error as FmtError, Formatter};
use std::ops::Deref;
use std::vec::*;

use serde::de;
use serde::de::Error;
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
//use serde::de::Deserialize;
use super::link::HalLink;
use super::{HalError, HalResult};
use serde_json::{from_value, to_value, Map, Value as JsonValue};

/// A Simple wrapper around a vector to allow custom
/// serialization when only 1 element is contained.
///
/// # example
///
/// In the example below, the vector serializes to json as an object if it contains
/// only one value, but as an array if more than one.
///
/// ```rust
/// # extern crate serde_json;
/// # extern crate rustic_hal;
/// use rustic_hal::resource::OneOrMany;
/// use rustic_hal::HalLink;
///
/// use serde_json::to_string;
/// # fn main() {
/// let mut v = OneOrMany::new();
/// v.push(&HalLink::new("http://test.com"));
///
/// assert_eq!(to_string(&v).unwrap(), r#"{"href":"http://test.com"}"#);
///
/// v.push(&HalLink::new("http://test2.com"));
///
/// assert_eq!(to_string(&v).unwrap(), r#"[{"href":"http://test.com"},{"href":"http://test2.com"}]"#);
/// # }
/// ```
#[derive(Clone)]
pub struct OneOrMany<T> {
    force_many: bool,
    content: Vec<Box<T>>,
}

impl<T> OneOrMany<T>
where
    T: Clone,
{
    /// create a new empty object
    pub fn new() -> OneOrMany<T> {
        OneOrMany {
            content: Vec::new(),
            force_many: false,
        }
    }

    /// Force to be serialized as array, even if only one element
    pub fn force_many(&mut self) -> &mut Self {
        self.force_many = true;
        self
    }

    /// retrieve the length of the wrapped vector
    pub fn len(&self) -> usize {
        self.content.len()
    }

    pub fn is_empty(&self) -> bool {
        self.content.is_empty()
    }

    /// Retrieves a single element if possible.
    ///
    pub fn single(&self) -> Option<&T> {
        if self.is_empty() {
            None
        } else {
            Some(self.content[0].deref())
        }
    }

    /// Returns an immutable reference to the
    /// contained links
    pub fn many(&self) -> &Vec<Box<T>> {
        &self.content
    }

    /// Add an element to the wrapped vector.
    pub fn push(&mut self, newval: &T) {
        self.content.push(Box::new(newval.clone()));
    }
}

impl<T> Serialize for OneOrMany<T>
where
    T: Serialize + Clone,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        if self.is_empty() {
            ().serialize(serializer)
        } else if self.len() == 1 && !self.force_many {
            self.single().serialize(serializer)
        } else {
            self.content.serialize(serializer)
        }
    }
}

impl<'de, T> Deserialize<'de> for OneOrMany<T>
where
    for<'d> T: Deserialize<'d> + Clone,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value: JsonValue = try!(Deserialize::deserialize(deserializer));
        let v2 = value.clone();
        match v2 {
            JsonValue::Object(_) => {
                let obj: T = match from_value(value) {
                    Ok(v) => from_value(v).unwrap(),
                    Err(e) => return Err(D::Error::custom(format!("JSON Error: {:?}", e))),
                };
                let mut res = OneOrMany::new();
                res.push(&obj);
                Ok(res)
            }
            JsonValue::Array(_) => {
                let obj: Vec<Box<T>> = match from_value(value) {
                    Ok(v) => from_value(v).unwrap(),
                    Err(e) => return Err(D::Error::custom(format!("JSON Error: {:?}", e))),
                };
                let mut res = OneOrMany::new();
                res.content = obj;
                Ok(res)
            }
            _ => Ok(OneOrMany::new()),
        }
    }
}

/// The HAL Resource structure.
///
///
#[derive(Clone)]
pub struct HalResource {
    /// Map of links to related resources.
    links: BTreeMap<String, OneOrMany<HalLink>>,
    /// Map of set of embedded resources.
    embedded: BTreeMap<String, OneOrMany<HalResource>>,
    /// Documentations Curies
    curies: BTreeMap<String, HalLink>,
    /// The actual resource data
    data: JsonValue,
}

impl HalResource {
    pub fn new<T>(payload: T) -> HalResource
    where
        T: Serialize,
    {
        HalResource {
            links: BTreeMap::new(),
            embedded: BTreeMap::new(),
            curies: BTreeMap::new(),
            data: to_value(payload).unwrap(),
        }
    }

    pub fn with_link<S, L>(&mut self, name: S, link: L) -> &mut Self
    where
        S: Into<String>,
        L: Into<HalLink>,
    {
        let lk_name = name.into();
        match self.links.entry(lk_name.clone()) {
            Entry::Vacant(entry) => {
                let mut lk = OneOrMany::new();

                if lk_name == "curies" {
                    lk.force_many();
                }
                lk.push(&(link.into()));
                entry.insert(lk);
            }
            Entry::Occupied(mut entry) => {
                let mut content = entry.get_mut(); //&mut HalLinks
                content.push(&(link.into()));
            }
        }
        self
    }

    /// Retrieve one named link if found. Returns the first one if more than one.
    pub fn get_link(&self, name: &str) -> Option<&HalLink> {
        match self.links.get(name) {
            Some(link) => link.single(),
            None => None,
        }
    }

    /// Retrieve the self link
    pub fn get_self(&self) -> Option<&HalLink> {
        self.get_link("self")
    }

    /// Retrieve the list of links for a key
    pub fn get_links(&self, name: &str) -> Option<&Vec<Box<HalLink>>> {
        match self.links.get(name) {
            Some(link) => Some(link.many()),
            None => None,
        }
    }

    pub fn with_resource(&mut self, name: &str, resource: &HalResource) -> &mut Self {
        match self.embedded.entry(name.to_string()) {
            Entry::Vacant(entry) => {
                let mut resources = OneOrMany::new();
                resources.push(&resource.clone());
                entry.insert(resources);
            }
            Entry::Occupied(mut entry) => {
                let mut content = entry.get_mut(); //&mut HalLinks
                content.push(&resource.clone());
            }
        }
        self
    }

    pub fn with_curie(&mut self, name: &str, href: &str) -> &mut Self {
        self.with_link("curies", HalLink::new(href).templated(true).with_name(name));
        self
    }

    pub fn with_extra_data<V>(&mut self, name: &str, value: V) -> &mut Self
    where
        V: Serialize,
    {
        match self.data {
            JsonValue::Object(ref mut m) => {
                m.insert(name.to_string(), to_value(value).unwrap());
            }
            _ => {
                let mut data = Map::<String, JsonValue>::new();
                data.insert(name.to_string(), to_value(value).unwrap());
                self.data = JsonValue::Object(data);
            }
        };
        self
    }

    pub fn get_extra_data<V>(&self, name: &str) -> HalResult<V>
    where
        for<'de> V: Deserialize<'de>,
    {
        let data = match self.data {
            JsonValue::Object(ref m) => m,
            _ => return Err(HalError::Custom("Invalid payload".to_string())),
        };
        match data.get(name) {
            Some(v) => from_value::<V>(v.clone()).or_else(|e| Err(HalError::Json(e))),
            None => Err(HalError::Custom(format!("Key {} missing in payload", name))),
        }
    }

    pub fn get_data<V>(&self) -> HalResult<V>
    where
        for<'de> V: Deserialize<'de>,
    {
        from_value::<V>(self.data.clone()).or_else(|e| Err(HalError::Json(e)))
    }
}

impl Serialize for HalResource {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let map = match self.data {
            JsonValue::Object(ref m) => Some(m),
            _ => None,
        };
        let mut length = match map {
            Some(m) => m.len(),
            _ => 0,
        };
        length += self.links.len();
        length += self.embedded.len();

        let mut state = try!(serializer.serialize_map(Some(length)));
        if !self.links.is_empty() {
            try!(state.serialize_key("_links"));
            try!(state.serialize_value(&(self.links)));
        }
        if !self.embedded.is_empty() {
            try!(state.serialize_key("_embedded"));
            try!(state.serialize_value(&(self.embedded)));
        }
        if let Some(map) = map {
            for (k, v) in map {
                try!(state.serialize_key(k));
                try!(state.serialize_value(v));
            }
        };

        state.end()
    }
}

impl<'de> Deserialize<'de> for HalResource {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct ResourceVisitor {}

        impl ResourceVisitor {
            fn new() -> Self {
                ResourceVisitor {}
            }
        }

        /// A visitor for deserializing `HalResource` from map representation.
        ///
        /// Implementation deserialize the hal specific keys (_links, _embedded) to maps
        /// as usual, and stores everything else in  a `JsonValue` object.
        /// It then converts the `JsonValue` to the type T
        impl<'de> de::Visitor<'de> for ResourceVisitor {
            type Value = HalResource;
            fn expecting(&self, f: &mut Formatter) -> Result<(), FmtError> {
                Ok(())
            }

            fn visit_map<M>(self, mut visitor: M) -> Result<HalResource, M::Error>
            where
                M: de::MapAccess<'de>,
            {
                //transitory Value to read the data.
                let mut payload: Map<String, JsonValue> = Map::new();
                // Dummy resource to collect the links and embedded resources
                let mut resource: HalResource = HalResource::new(());
                while let Some(key) = try!(visitor.next_key::<String>()) {
                    match key.deref() {
                        "_links" => {
                            resource.links = try!(visitor.next_value());
                        }
                        "_embedded" => {
                            resource.embedded = try!(visitor.next_value());
                        }
                        _ => {
                            payload.insert(key, try!(visitor.next_value()));
                        }
                    }
                }
                //try!(visitor.end());
                resource.data = JsonValue::Object(payload);
                Ok(resource)
            }
        }

        deserializer.deserialize_map(ResourceVisitor::new())
    }
}

impl PartialEq for HalResource {
    fn eq(&self, other: &HalResource) -> bool {
        self.get_self() == other.get_self()
    }
}
<commit_msg>Add with_resources<commit_after>use std::collections::btree_map::Entry;
use std::collections::*;
use std::fmt::{Error as FmtError, Formatter};
use std::ops::Deref;
use std::vec::*;

use serde::de;
use serde::de::Error;
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
//use serde::de::Deserialize;
use super::link::HalLink;
use super::{HalError, HalResult};
use serde_json::{from_value, to_value, Map, Value as JsonValue};

/// A Simple wrapper around a vector to allow custom
/// serialization when only 1 element is contained.
///
/// # example
///
/// In the example below, the vector serializes to json as an object if it contains
/// only one value, but as an array if more than one.
///
/// ```rust
/// # extern crate serde_json;
/// # extern crate rustic_hal;
/// use rustic_hal::resource::OneOrMany;
/// use rustic_hal::HalLink;
///
/// use serde_json::to_string;
/// # fn main() {
/// let mut v = OneOrMany::new();
/// v.push(&HalLink::new("http://test.com"));
///
/// assert_eq!(to_string(&v).unwrap(), r#"{"href":"http://test.com"}"#);
///
/// v.push(&HalLink::new("http://test2.com"));
///
/// assert_eq!(to_string(&v).unwrap(), r#"[{"href":"http://test.com"},{"href":"http://test2.com"}]"#);
/// # }
/// ```
#[derive(Clone)]
pub struct OneOrMany<T> {
    force_many: bool,
    content: Vec<Box<T>>,
}

impl<T> OneOrMany<T>
where
    T: Clone,
{
    /// create a new empty object
    pub fn new() -> OneOrMany<T> {
        OneOrMany {
            content: Vec::new(),
            force_many: false,
        }
    }

    /// Force to be serialized as array, even if only one element
    pub fn force_many(mut self) -> Self {
        self.force_many = true;
        self
    }

    /// retrieve the length of the wrapped vector
    pub fn len(&self) -> usize {
        self.content.len()
    }

    pub fn is_empty(&self) -> bool {
        self.content.is_empty()
    }

    /// Retrieves a single element if possible.
    ///
    pub fn single(&self) -> Option<&T> {
        if self.is_empty() {
            None
        } else {
            Some(self.content[0].deref())
        }
    }

    /// Returns an immutable reference to the
    /// contained links
    pub fn many(&self) -> &Vec<Box<T>> {
        &self.content
    }

    /// Add an element to the wrapped vector.
    pub fn push(&mut self, newval: &T) {
        self.content.push(Box::new(newval.clone()));
    }
}

impl<T> Serialize for OneOrMany<T>
where
    T: Serialize + Clone,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        if self.is_empty() {
            ().serialize(serializer)
        } else if self.len() == 1 && !self.force_many {
            self.single().serialize(serializer)
        } else {
            self.content.serialize(serializer)
        }
    }
}

impl<'de, T> Deserialize<'de> for OneOrMany<T>
where
    for<'d> T: Deserialize<'d> + Clone,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value: JsonValue = try!(Deserialize::deserialize(deserializer));
        let v2 = value.clone();
        match v2 {
            JsonValue::Object(_) => {
                let obj: T = match from_value(value) {
                    Ok(v) => from_value(v).unwrap(),
                    Err(e) => return Err(D::Error::custom(format!("JSON Error: {:?}", e))),
                };
                let mut res = OneOrMany::new();
                res.push(&obj);
                Ok(res)
            }
            JsonValue::Array(_) => {
                let obj: Vec<Box<T>> = match from_value(value) {
                    Ok(v) => from_value(v).unwrap(),
                    Err(e) => return Err(D::Error::custom(format!("JSON Error: {:?}", e))),
                };
                let mut res = OneOrMany::new();
                res.content = obj;
                Ok(res)
            }
            _ => Ok(OneOrMany::new()),
        }
    }
}

/// The HAL Resource structure.
///
///
#[derive(Clone)]
pub struct HalResource {
    /// Map of links to related resources.
    links: BTreeMap<String, OneOrMany<HalLink>>,
    /// Map of set of embedded resources.
    embedded: BTreeMap<String, OneOrMany<HalResource>>,
    /// Documentations Curies
    curies: BTreeMap<String, HalLink>,
    /// The actual resource data
    data: JsonValue,
}

impl HalResource {
    pub fn new<T>(payload: T) -> HalResource
    where
        T: Serialize,
    {
        HalResource {
            links: BTreeMap::new(),
            embedded: BTreeMap::new(),
            curies: BTreeMap::new(),
            data: to_value(payload).unwrap(),
        }
    }

    pub fn with_link<S, L>(&mut self, name: S, link: L) -> &mut Self
    where
        S: Into<String>,
        L: Into<HalLink>,
    {
        let lk_name = name.into();
        match self.links.entry(lk_name.clone()) {
            Entry::Vacant(entry) => {
                let mut lk = OneOrMany::new();

                let mut lk = match lk_name.as_ref() {
                    "curies" => lk.force_many(),
                    _ => lk,
                };

                lk.push(&(link.into()));
                entry.insert(lk);
            }
            Entry::Occupied(mut entry) => {
                let mut content = entry.get_mut(); //&mut HalLinks
                content.push(&(link.into()));
            }
        }
        self
    }

    /// Retrieve one named link if found. Returns the first one if more than one.
    pub fn get_link(&self, name: &str) -> Option<&HalLink> {
        match self.links.get(name) {
            Some(link) => link.single(),
            None => None,
        }
    }

    /// Retrieve the self link
    pub fn get_self(&self) -> Option<&HalLink> {
        self.get_link("self")
    }

    /// Retrieve the list of links for a key
    pub fn get_links(&self, name: &str) -> Option<&Vec<Box<HalLink>>> {
        match self.links.get(name) {
            Some(link) => Some(link.many()),
            None => None,
        }
    }

    pub fn with_resource(&mut self, name: &str, resource: &HalResource) -> &mut Self {
        match self.embedded.entry(name.to_string()) {
            Entry::Vacant(entry) => {
                let mut resources = OneOrMany::new();
                resources.push(&resource.clone());
                entry.insert(resources);
            }
            Entry::Occupied(mut entry) => {
                let mut content = entry.get_mut(); //&mut HalLinks
                content.push(&resource.clone());
            }
        }
        self
    }

    pub fn with_resources(&mut self, name: &str, resources: Vec<&HalResource>) -> &mut Self {
        match self.embedded.entry(name.to_string()) {
            Entry::Vacant(entry) => {
                let mut _resources = OneOrMany::new().force_many();

                for resource in resources.iter() {
                    _resources.push(resource.clone())
                }
                entry.insert(_resources.clone());
            }
            Entry::Occupied(mut entry) => {
                let mut content = entry.get_mut(); //&mut HalLinks

                for resource in resources.iter() {
                    content.push(&resource.clone());
                }
            }
        }
        self
    }

    pub fn with_curie(&mut self, name: &str, href: &str) -> &mut Self {
        self.with_link("curies", HalLink::new(href).templated(true).with_name(name));
        self
    }

    pub fn with_extra_data<V>(&mut self, name: &str, value: V) -> &mut Self
    where
        V: Serialize,
    {
        match self.data {
            JsonValue::Object(ref mut m) => {
                m.insert(name.to_string(), to_value(value).unwrap());
            }
            _ => {
                let mut data = Map::<String, JsonValue>::new();
                data.insert(name.to_string(), to_value(value).unwrap());
                self.data = JsonValue::Object(data);
            }
        };
        self
    }

    pub fn get_extra_data<V>(&self, name: &str) -> HalResult<V>
    where
        for<'de> V: Deserialize<'de>,
    {
        let data = match self.data {
            JsonValue::Object(ref m) => m,
            _ => return Err(HalError::Custom("Invalid payload".to_string())),
        };
        match data.get(name) {
            Some(v) => from_value::<V>(v.clone()).or_else(|e| Err(HalError::Json(e))),
            None => Err(HalError::Custom(format!("Key {} missing in payload", name))),
        }
    }

    pub fn get_data<V>(&self) -> HalResult<V>
    where
        for<'de> V: Deserialize<'de>,
    {
        from_value::<V>(self.data.clone()).or_else(|e| Err(HalError::Json(e)))
    }
}

impl Serialize for HalResource {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let map = match self.data {
            JsonValue::Object(ref m) => Some(m),
            _ => None,
        };
        let mut length = match map {
            Some(m) => m.len(),
            _ => 0,
        };
        length += self.links.len();
        length += self.embedded.len();

        let mut state = try!(serializer.serialize_map(Some(length)));
        if !self.links.is_empty() {
            try!(state.serialize_key("_links"));
            try!(state.serialize_value(&(self.links)));
        }
        if !self.embedded.is_empty() {
            try!(state.serialize_key("_embedded"));
            try!(state.serialize_value(&(self.embedded)));
        }
        if let Some(map) = map {
            for (k, v) in map {
                try!(state.serialize_key(k));
                try!(state.serialize_value(v));
            }
        };

        state.end()
    }
}

impl<'de> Deserialize<'de> for HalResource {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct ResourceVisitor {}

        impl ResourceVisitor {
            fn new() -> Self {
                ResourceVisitor {}
            }
        }

        /// A visitor for deserializing `HalResource` from map representation.
        ///
        /// Implementation deserialize the hal specific keys (_links, _embedded) to maps
        /// as usual, and stores everything else in  a `JsonValue` object.
        /// It then converts the `JsonValue` to the type T
        impl<'de> de::Visitor<'de> for ResourceVisitor {
            type Value = HalResource;
            fn expecting(&self, f: &mut Formatter) -> Result<(), FmtError> {
                Ok(())
            }

            fn visit_map<M>(self, mut visitor: M) -> Result<HalResource, M::Error>
            where
                M: de::MapAccess<'de>,
            {
                //transitory Value to read the data.
                let mut payload: Map<String, JsonValue> = Map::new();
                // Dummy resource to collect the links and embedded resources
                let mut resource: HalResource = HalResource::new(());
                while let Some(key) = try!(visitor.next_key::<String>()) {
                    match key.deref() {
                        "_links" => {
                            resource.links = try!(visitor.next_value());
                        }
                        "_embedded" => {
                            resource.embedded = try!(visitor.next_value());
                        }
                        _ => {
                            payload.insert(key, try!(visitor.next_value()));
                        }
                    }
                }
                //try!(visitor.end());
                resource.data = JsonValue::Object(payload);
                Ok(resource)
            }
        }

        deserializer.deserialize_map(ResourceVisitor::new())
    }
}

impl PartialEq for HalResource {
    fn eq(&self, other: &HalResource) -> bool {
        self.get_self() == other.get_self()
    }
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate adapton_lab;

use std::rc::Rc;
use std::io::BufReader;
use std::io::BufWriter;
use std::io::Write;
use std::io::BufRead;
use std::fs::File;
use std::collections::HashMap;

use adapton_lab::labviz::{Div,WriteHTML};

// Temp: Make this better at some point?
type Graph = HashMap<Rc<String>, Vec<Rc<String>>>;
type NodeSet = HashMap<Rc<String>,()>;
type NodeCount = HashMap<Rc<String>,usize>;

#[derive(Debug)]
struct Options {
    tooltips: bool,
    tooltips_visited: bool,
}

impl Options {
    fn new() -> Self {
        Options {
            tooltips:true,
            tooltips_visited:false,
        }
    }
}

struct St {
    nodes: NodeSet,
    leaves:NodeSet,
    roots: NodeSet,
    graph: Graph,
    edges: Vec<(Rc<String>, Rc<String>)>,
    rev_graph:Graph,
}

impl St {
    fn new() -> Self {
        St {
            nodes: HashMap::new(),
            leaves:HashMap::new(),
            roots: HashMap::new(),
            graph: HashMap::new(),
            edges: Vec::new(),
            rev_graph:HashMap::new()
        }
    }
    fn process_nodes(self:&mut Self) {
        self.leaves=HashMap::new();
        self.roots=HashMap::new();
        for (node,_) in self.nodes.iter() {
            match self.graph.get(node) {
                None => { self.leaves.insert(node.clone(),()); },
                Some(_out) => {}
            };
            match self.rev_graph.get(node) {
                None => { self.roots.insert(node.clone(),()); },
                Some(_out) => {}
            };
        }
    }
    fn add_edge(self:&mut Self, src:Rc<String>, tgt:Rc<String>) {
        self.nodes.insert(src.clone(), ());
        self.nodes.insert(tgt.clone(), ());
        let edge = if false {
            //(Rc::new(v[0].to_string()), Rc::new(v[1].to_string()))
            (src, tgt)
        } else {
            //(Rc::new(v[1].to_string()), Rc::new(v[0].to_string()))
            (tgt, src)
        };
        self.edges.push(edge.clone());

        let inserted : bool = match self.graph.get_mut(&edge.0) {
            None => { false }
            Some(out) => {
                out.push(edge.1.clone());
                true
            },
        };
        if !inserted {
            self.graph.insert(edge.0.clone(), vec![ edge.1.clone() ]);
        };

        let inserted : bool = match self.rev_graph.get_mut(&edge.1) {
            None => { false }
            Some(out) => {
                out.push(edge.0.clone());
                true
            },
        };
        if !inserted {
            self.rev_graph.insert(edge.1, vec![ edge.0 ]);
        };
    }
}

fn main() {
    let mut options = Options::new();
    let args = clap::App::new("dep-viz")
        .version("0.1")
        .author("Matthew Hammer <matthew.hammer@colorado.edu>")
        .about("Consumes Rustc dependency information; Produces visualizations")
        .args_from_usage("\
                --no-tips            'do not show tips for nodes on hover'
                --tips-visited       'show tips even for visited nodes'
            -i, --infile=[infile]    'name for input file'
            -o, --outfile=[outfile]  'name for output file'")
    .get_matches();
    let infile_name = args.value_of("infile").unwrap_or("dep_graph.txt");
    let outfile_name = args.value_of("outfile").unwrap_or("dep_graph.html");

    options.tooltips = !(args.is_present("no-tips"));
    options.tooltips_visited = args.is_present("tips-visited");

    println!("{:?}", options);

    println!("Reading input file: {}", infile_name);
    let f = File::open(infile_name).unwrap();
    let file = BufReader::new(&f);
    let mut st = St::new();
    for (_num, line) in file.lines().enumerate() {
        let line = line.unwrap();
        let v: Vec<&str> = line.split(" -> ").map(|s|s.trim()).collect();
        assert_eq!(v.len(), 2);
        st.add_edge(Rc::new(v[0].to_string()),
                    Rc::new(v[1].to_string()));
    }
    println!("...Read input file: {}", infile_name);
    st.process_nodes();
    println!("  Found {} edges", st.edges.len());
    println!("  Found {} nodes", st.nodes.len());
    println!("    Found {} root nodes", st.roots.len());
    println!("    Found {} leaf nodes", st.leaves.len());

    println!("\nPerforming DFS on graph...");
    let mut stack = vec![];
    for (node,_) in st.roots.iter() {
        stack.push(node.clone());
        //break;
    }

    let (div, visited) = dfs(&options, &st.graph, stack);
    println!("...Performed DFS on graph.");
    println!("  Visited {} nodes, {:.1}% of total", visited.len(),
             100f32 * (visited.len() as f32) / (st.nodes.len() as f32));

    println!("\nFinding unvisited graph nodes...");
    let mut unvisited : NodeSet = HashMap::new();
    for (node, _) in st.nodes.iter() {
        if visited.get(node) == None {
            unvisited.insert(node.clone(), ());
            //println!("unvisited: {}", node);
        }
    }
    assert_eq!(visited.len() + unvisited.len(),
               st.nodes.len());
    //println!("...Found graph nodes.");
    println!("...Found {} unvisited graph nodes, {:.1}% of total.",
             unvisited.len(),
             100f32 * (unvisited.len() as f32) / (st.nodes.len() as f32));

    println!("Writing HTML output: {}", outfile_name);
    let outfile = File::create(outfile_name).unwrap();
    let mut buf_writer = BufWriter::new(outfile);
    let _ = buf_writer.write_all(style_string().as_bytes());
    div.write_html(&mut buf_writer);
    let _ = buf_writer.flush();
    println!("..Wrote HTML output: {}", outfile_name);
}

fn dfs (options:&Options, graph: &Graph, stack:Vec<Rc<String>>) -> (Div, NodeCount) {
    let mut visited:NodeCount = HashMap::new();
    let mut divs:Vec<Div> = Vec::new();
    for n in stack.iter() {
        divs.push(dfs_rec(options, graph, &mut visited, 0, n));
        //if visited.len() > 10000 { break } else { continue }
    }
    (Div{tag:"dfs".to_string(),
         classes:vec![],
         extent:Box::new(divs),
         text: None}, visited)
}

fn cons_of_node_name(node:&Rc<String>) -> String {
    let cons : Vec<&str> = node.trim().split("(").collect();
    assert!(cons.len() > 0 && cons[0] != "");
    cons[0].to_string()
}

fn append_classes_of_node(node:&Rc<String>, mut classes:Vec<String>) -> Vec<String> {
    classes.push(cons_of_node_name(node));
    classes
}

fn append_tooltip(options:&Options, node:&Rc<String>, mut extent:Vec<Div>, visited:bool, depth: usize) -> Vec<Div> {
    if options.tooltips && (!visited || options.tooltips_visited) {
        let show_full_details_depth = 2;
        extent.insert(0, Div{tag:"tooltip".to_string(),
                             classes:vec![],
                             extent:Box::new(vec![]),
                             text: Some(
                                 format!("{}: {}", depth,
                                         if !visited && depth < show_full_details_depth
                                         { (**node).clone() }
                                         else
                                         { cons_of_node_name(node) }))
        });
        extent
    }
    else {
        extent
    }
}

fn dfs_rec (options:&Options, graph: &Graph, visited: &mut NodeCount, depth:usize, node:&Rc<String>) -> Div {
    if visited.get(node) == None {
        visited.insert(node.clone(),1);
        match graph.get(node) {
            None => {
                /* Has no children */
                Div{tag:if false {(**node).clone()} else { "".to_string() },
                    classes:append_classes_of_node(node, vec![
                        "node".to_string(),
                        "no-extent".to_string()
                    ]),
                    extent:Box::new(append_tooltip(options, node, vec![], false, depth)),
                    text:None}
            }
            Some(out) => {
                /* Has children */
                let mut divs = vec![];
                for n in out {
                    divs.push(dfs_rec(options, graph, visited, depth+1, n))
                }
                Div{tag:if false {(**node).clone()} else { "".to_string() },
                    classes:append_classes_of_node(node, vec![
                        "node".to_string()
                    ]),
                    extent:Box::new(append_tooltip(options, node, divs, false, depth)),
                    text:None}
            }
        }
    }
    else {
        // increment counter
        let node_count = visited.get(node).unwrap() + 1;
        visited.insert(node.clone(), node_count);
        // Already visited
        Div{tag:if false {(**node).clone()} else { "".to_string() },
            classes:vec![
                "node".to_string(),
                "visited".to_string()
            ],
            extent:Box::new(append_tooltip(options, node, vec![], true, depth)),
            //extent:Box::new(vec![]),
            text:None}
    }
}

pub fn style_string() -> &'static str {
"
<html>
<head>
<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js\"></script>

<style>
div {
  display: inline
}
body {
  display: inline;
  color: #aa88cc;
  background: #313;
  font-family: sans-serif;
  text-decoration: none;
  padding: 0px;
  margin: 0px;
}
body:visited {
  color: #aa88cc;
}
a {
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}
hr {
  display: block;
  float: left;
  clear: both;
  width: 0px;
  border: none;
}

.node {
  color: black;
  display: inline-block;
  border-style: solid;
  border-color: #221133;
  border-width: 1px;
  background: #ffccff;
  font-size: 0px;
  padding: 0px;
  margin: 1px;
  border-radius: 5px;
}

.visited {
  border-width: 2px;
}

.no-extent {
  padding: 3px;
}
.tooltip {
  visibility: hidden;
  background-color: #324;
  border-style: solid;
  border-color: #eeeeee;
  border-width: 1px;
  color: #eae;
  text-align: left;
  padding: 5px 0;
  border-radius: 2px;
  font-size: 12px;
  position: absolute;
  z-index: 1;
}
.node:hover {
  border-color: green;
}
.node:hover > .tooltip {
  visibility: visible;
}

.WorkProduct {
  border-width: 2px;
  border-color: blue;
  background: #999;
}

.MetaData {
  border-color: #000055;
  background: #0000ff;
}

.Hir {
  border-color: #005555;
  background: #00aaff;
}

</style>
</head>
<body>
"
}
<commit_msg>node cons in visited nodes<commit_after>extern crate clap;
extern crate adapton_lab;

use std::rc::Rc;
use std::io::BufReader;
use std::io::BufWriter;
use std::io::Write;
use std::io::BufRead;
use std::fs::File;
use std::collections::HashMap;

use adapton_lab::labviz::{Div,WriteHTML};

// Temp: Make this better at some point?
type Graph = HashMap<Rc<String>, Vec<Rc<String>>>;
type NodeSet = HashMap<Rc<String>,()>;
type NodeCount = HashMap<Rc<String>,usize>;

#[derive(Debug)]
struct Options {
    tooltips: bool,
    tooltips_visited: bool,
}

impl Options {
    fn new() -> Self {
        Options {
            tooltips:true,
            tooltips_visited:false,
        }
    }
}

struct St {
    nodes: NodeSet,
    leaves:NodeSet,
    roots: NodeSet,
    graph: Graph,
    edges: Vec<(Rc<String>, Rc<String>)>,
    rev_graph:Graph,
}

impl St {
    fn new() -> Self {
        St {
            nodes: HashMap::new(),
            leaves:HashMap::new(),
            roots: HashMap::new(),
            graph: HashMap::new(),
            edges: Vec::new(),
            rev_graph:HashMap::new()
        }
    }
    fn process_nodes(self:&mut Self) {
        self.leaves=HashMap::new();
        self.roots=HashMap::new();
        for (node,_) in self.nodes.iter() {
            match self.graph.get(node) {
                None => { self.leaves.insert(node.clone(),()); },
                Some(_out) => {}
            };
            match self.rev_graph.get(node) {
                None => { self.roots.insert(node.clone(),()); },
                Some(_out) => {}
            };
        }
    }
    fn add_edge(self:&mut Self, src:Rc<String>, tgt:Rc<String>) {
        self.nodes.insert(src.clone(), ());
        self.nodes.insert(tgt.clone(), ());
        let edge = if false {
            //(Rc::new(v[0].to_string()), Rc::new(v[1].to_string()))
            (src, tgt)
        } else {
            //(Rc::new(v[1].to_string()), Rc::new(v[0].to_string()))
            (tgt, src)
        };
        self.edges.push(edge.clone());

        let inserted : bool = match self.graph.get_mut(&edge.0) {
            None => { false }
            Some(out) => {
                out.push(edge.1.clone());
                true
            },
        };
        if !inserted {
            self.graph.insert(edge.0.clone(), vec![ edge.1.clone() ]);
        };

        let inserted : bool = match self.rev_graph.get_mut(&edge.1) {
            None => { false }
            Some(out) => {
                out.push(edge.0.clone());
                true
            },
        };
        if !inserted {
            self.rev_graph.insert(edge.1, vec![ edge.0 ]);
        };
    }
}

fn main() {
    let mut options = Options::new();
    let args = clap::App::new("dep-viz")
        .version("0.1")
        .author("Matthew Hammer <matthew.hammer@colorado.edu>")
        .about("Consumes Rustc dependency information; Produces visualizations")
        .args_from_usage("\
                --no-tips            'do not show tips for nodes on hover'
                --tips-visited       'show tips even for visited nodes'
            -i, --infile=[infile]    'name for input file'
            -o, --outfile=[outfile]  'name for output file'")
    .get_matches();
    let infile_name = args.value_of("infile").unwrap_or("dep_graph.txt");
    let outfile_name = args.value_of("outfile").unwrap_or("dep_graph.html");

    options.tooltips = !(args.is_present("no-tips"));
    options.tooltips_visited = args.is_present("tips-visited");

    println!("{:?}", options);

    println!("Reading input file: {}", infile_name);
    let f = File::open(infile_name).unwrap();
    let file = BufReader::new(&f);
    let mut st = St::new();
    for (_num, line) in file.lines().enumerate() {
        let line = line.unwrap();
        let v: Vec<&str> = line.split(" -> ").map(|s|s.trim()).collect();
        assert_eq!(v.len(), 2);
        st.add_edge(Rc::new(v[0].to_string()),
                    Rc::new(v[1].to_string()));
    }
    println!("...Read input file: {}", infile_name);
    st.process_nodes();
    println!("  Found {} edges", st.edges.len());
    println!("  Found {} nodes", st.nodes.len());
    println!("    Found {} root nodes", st.roots.len());
    println!("    Found {} leaf nodes", st.leaves.len());

    println!("\nPerforming DFS on graph...");
    let mut stack = vec![];
    for (node,_) in st.roots.iter() {
        stack.push(node.clone());
        //break;
    }

    let (div, visited) = dfs(&options, &st.graph, stack);
    println!("...Performed DFS on graph.");
    println!("  Visited {} nodes, {:.1}% of total", visited.len(),
             100f32 * (visited.len() as f32) / (st.nodes.len() as f32));

    println!("\nFinding unvisited graph nodes...");
    let mut unvisited : NodeSet = HashMap::new();
    for (node, _) in st.nodes.iter() {
        if visited.get(node) == None {
            unvisited.insert(node.clone(), ());
            //println!("unvisited: {}", node);
        }
    }
    assert_eq!(visited.len() + unvisited.len(),
               st.nodes.len());
    //println!("...Found graph nodes.");
    println!("...Found {} unvisited graph nodes, {:.1}% of total.",
             unvisited.len(),
             100f32 * (unvisited.len() as f32) / (st.nodes.len() as f32));

    println!("Writing HTML output: {}", outfile_name);
    let outfile = File::create(outfile_name).unwrap();
    let mut buf_writer = BufWriter::new(outfile);
    let _ = buf_writer.write_all(style_string().as_bytes());
    div.write_html(&mut buf_writer);
    let _ = buf_writer.flush();
    println!("..Wrote HTML output: {}", outfile_name);
}

fn dfs (options:&Options, graph: &Graph, stack:Vec<Rc<String>>) -> (Div, NodeCount) {
    let mut visited:NodeCount = HashMap::new();
    let mut divs:Vec<Div> = Vec::new();
    for n in stack.iter() {
        divs.push(dfs_rec(options, graph, &mut visited, 0, n));
        //if visited.len() > 10000 { break } else { continue }
    }
    (Div{tag:"dfs".to_string(),
         classes:vec![],
         extent:Box::new(divs),
         text: None}, visited)
}

fn cons_of_node_name(node:&Rc<String>) -> String {
    let cons : Vec<&str> = node.trim().split("(").collect();
    assert!(cons.len() > 0 && cons[0] != "");
    cons[0].to_string()
}

fn append_classes_of_node(node:&Rc<String>, mut classes:Vec<String>) -> Vec<String> {
    classes.push(cons_of_node_name(node));
    classes
}

fn append_tooltip(options:&Options, node:&Rc<String>, mut extent:Vec<Div>, visited:bool, depth: usize) -> Vec<Div> {
    if options.tooltips && (!visited || options.tooltips_visited) {
        let show_full_details_depth = 2;
        extent.insert(0, Div{tag:"tooltip".to_string(),
                             classes:vec![],
                             extent:Box::new(vec![]),
                             text: Some(
                                 format!("{}: {}", depth,
                                         if !visited && depth < show_full_details_depth
                                         { (**node).clone() }
                                         else
                                         { cons_of_node_name(node) }))
        });
        extent
    }
    else {
        extent
    }
}

fn dfs_rec (options:&Options, graph: &Graph, visited: &mut NodeCount, depth:usize, node:&Rc<String>) -> Div {
    if visited.get(node) == None {
        visited.insert(node.clone(),1);
        match graph.get(node) {
            None => {
                /* Has no children */
                Div{tag:if false {(**node).clone()} else { "".to_string() },
                    classes:append_classes_of_node(node, vec![
                        "node".to_string(),
                        "no-extent".to_string()
                    ]),
                    extent:Box::new(append_tooltip(options, node, vec![], false, depth)),
                    text:None}
            }
            Some(out) => {
                /* Has children */
                let mut divs = vec![];
                for n in out {
                    divs.push(dfs_rec(options, graph, visited, depth+1, n))
                }
                Div{tag:if false {(**node).clone()} else { "".to_string() },
                    classes:append_classes_of_node(node, vec![
                        "node".to_string()
                    ]),
                    extent:Box::new(append_tooltip(options, node, divs, false, depth)),
                    text:None}
            }
        }
    }
    else {
        // increment counter
        let node_count = visited.get(node).unwrap() + 1;
        visited.insert(node.clone(), node_count);
        // Already visited
        Div{tag:if false {(**node).clone()} else { "".to_string() },
            classes:append_classes_of_node(node, vec![
                "node".to_string(),
                "visited".to_string()
            ]),
            extent:Box::new(append_tooltip(options, node, vec![], true, depth)),
            //extent:Box::new(vec![]),
            text:None}
    }
}

pub fn style_string() -> &'static str {
"
<html>
<head>
<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js\"></script>

<style>
div {
  display: inline
}
body {
  display: inline;
  color: #aa88cc;
  background: #313;
  font-family: sans-serif;
  text-decoration: none;
  padding: 0px;
  margin: 0px;
}
body:visited {
  color: #aa88cc;
}
a {
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}
hr {
  display: block;
  float: left;
  clear: both;
  width: 0px;
  border: none;
}

.node {
  color: black;
  display: inline-block;
  border-style: solid;
  border-color: #221133;
  border-width: 1px;
  background: #ffccff;
  font-size: 0px;
  padding: 0px;
  margin: 1px;
  border-radius: 5px;
}

.visited {
  border-width: 2px;
}

.no-extent {
  padding: 3px;
}
.tooltip {
  visibility: hidden;
  background-color: #324;
  border-style: solid;
  border-color: #eeeeee;
  border-width: 1px;
  color: #eae;
  text-align: left;
  padding: 5px 0;
  border-radius: 2px;
  font-size: 12px;
  position: absolute;
  z-index: 1;
}
.node:hover {
  border-color: green;
}
.node:hover > .tooltip {
  visibility: visible;
}

.WorkProduct {
  border-width: 2px;
  border-color: blue;
  background: #999;
}

.MetaData {
  border-color: #000055;
  background: #0000ff;
}

.Hir {
  border-color: #005555;
  background: #00aaff;
}

</style>
</head>
<body>
"
}
<|endoftext|>


<commit_before>// Copyright 2018 Syn Developers
//
// 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 super::*;
use punctuated::Punctuated;

use std::iter;

use proc_macro2::{Delimiter, Spacing, TokenStream, TokenTree};

#[cfg(feature = "extra-traits")]
use std::hash::{Hash, Hasher};
#[cfg(feature = "extra-traits")]
use tt::TokenStreamHelper;

ast_struct! {
    /// An attribute like `#[repr(transparent)]`.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    ///
    /// # Syntax
    ///
    /// Rust has six types of attributes.
    ///
    /// - Outer attributes like `#[repr(transparent)]`. These appear outside or
    ///   in front of the item they describe.
    /// - Inner attributes like `#![feature(proc_macro)]`. These appear inside
    ///   of the item they describe, usually a module.
    /// - Outer doc comments like `/// # Example`.
    /// - Inner doc comments like `//! Please file an issue`.
    /// - Outer block comments `/** # Example */`.
    /// - Inner block comments `/*! Please file an issue */`.
    ///
    /// The `style` field of type `AttrStyle` distinguishes whether an attribute
    /// is outer or inner. Doc comments and block comments are promoted to
    /// attributes, as this is how they are processed by the compiler and by
    /// `macro_rules!` macros.
    ///
    /// The `path` field gives the possibly colon-delimited path against which
    /// the attribute is resolved. It is equal to `"doc"` for desugared doc
    /// comments. The `tts` field contains the rest of the attribute body as
    /// tokens.
    ///
    /// ```text
    /// #[derive(Copy)]      #[crate::precondition x < 5]
    ///   ^^^^^^~~~~~~         ^^^^^^^^^^^^^^^^^^^ ~~~~~
    ///    path  tts                   path         tts
    /// ```
    ///
    /// Use the [`interpret_meta`] method to try parsing the tokens of an
    /// attribute into the structured representation that is used by convention
    /// across most Rust libraries.
    ///
    /// [`interpret_meta`]: #method.interpret_meta
    pub struct Attribute #manual_extra_traits {
        pub pound_token: Token![#],
        pub style: AttrStyle,
        pub bracket_token: token::Bracket,
        pub path: Path,
        pub tts: TokenStream,
    }
}

#[cfg(feature = "extra-traits")]
impl Eq for Attribute {}

#[cfg(feature = "extra-traits")]
impl PartialEq for Attribute {
    fn eq(&self, other: &Self) -> bool {
        self.style == other.style
            && self.pound_token == other.pound_token
            && self.bracket_token == other.bracket_token
            && self.path == other.path
            && TokenStreamHelper(&self.tts) == TokenStreamHelper(&other.tts)
    }
}

#[cfg(feature = "extra-traits")]
impl Hash for Attribute {
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        self.style.hash(state);
        self.pound_token.hash(state);
        self.bracket_token.hash(state);
        self.path.hash(state);
        TokenStreamHelper(&self.tts).hash(state);
    }
}

impl Attribute {
    /// Parses the tokens after the path as a [`Meta`](enum.Meta.html) if
    /// possible.
    pub fn interpret_meta(&self) -> Option<Meta> {
        let name = if self.path.segments.len() == 1 {
            &self.path.segments.first().unwrap().value().ident
        } else {
            return None;
        };

        if self.tts.is_empty() {
            return Some(Meta::Word(name.clone()));
        }

        let tts = self.tts.clone().into_iter().collect::<Vec<_>>();

        if tts.len() == 1 {
            if let Some(meta) = Attribute::extract_meta_list(name.clone(), &tts[0]) {
                return Some(meta);
            }
        }

        if tts.len() == 2 {
            if let Some(meta) = Attribute::extract_name_value(name.clone(), &tts[0], &tts[1]) {
                return Some(meta);
            }
        }

        None
    }

    fn extract_meta_list(ident: Ident, tt: &TokenTree) -> Option<Meta> {
        let g = match *tt {
            TokenTree::Group(ref g) => g,
            _ => return None,
        };
        if g.delimiter() != Delimiter::Parenthesis {
            return None;
        }
        let tokens = g.stream().clone().into_iter().collect::<Vec<_>>();
        let nested = match list_of_nested_meta_items_from_tokens(&tokens) {
            Some(n) => n,
            None => return None,
        };
        Some(Meta::List(MetaList {
            paren_token: token::Paren(g.span()),
            ident: ident,
            nested: nested,
        }))
    }

    fn extract_name_value(ident: Ident, a: &TokenTree, b: &TokenTree) -> Option<Meta> {
        let a = match *a {
            TokenTree::Punct(ref o) => o,
            _ => return None,
        };
        if a.spacing() != Spacing::Alone {
            return None;
        }
        if a.as_char() != '=' {
            return None;
        }

        match *b {
            TokenTree::Literal(ref l) if !l.to_string().starts_with('/') => {
                Some(Meta::NameValue(MetaNameValue {
                    ident: ident,
                    eq_token: Token![=]([a.span()]),
                    lit: Lit::new(l.clone()),
                }))
            }
            TokenTree::Ident(ref v) => match &v.to_string()[..] {
                v @ "true" | v @ "false" => Some(Meta::NameValue(MetaNameValue {
                    ident: ident,
                    eq_token: Token![=]([a.span()]),
                    lit: Lit::Bool(LitBool {
                        value: v == "true",
                        span: b.span(),
                    }),
                })),
                _ => None,
            },
            _ => None,
        }
    }
}

fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMeta, &[TokenTree])> {
    assert!(!tts.is_empty());

    match tts[0] {
        TokenTree::Literal(ref lit) => {
            if lit.to_string().starts_with('/') {
                None
            } else {
                let lit = Lit::new(lit.clone());
                Some((NestedMeta::Literal(lit), &tts[1..]))
            }
        }

        TokenTree::Ident(ref ident) => {
            if tts.len() >= 3 {
                if let Some(meta) = Attribute::extract_name_value(ident.clone(), &tts[1], &tts[2]) {
                    return Some((NestedMeta::Meta(meta), &tts[3..]));
                }
            }

            if tts.len() >= 2 {
                if let Some(meta) = Attribute::extract_meta_list(ident.clone(), &tts[1]) {
                    return Some((NestedMeta::Meta(meta), &tts[2..]));
                }
            }

            Some((Meta::Word(ident.clone()).into(), &tts[1..]))
        }

        _ => None,
    }
}

fn list_of_nested_meta_items_from_tokens(
    mut tts: &[TokenTree],
) -> Option<Punctuated<NestedMeta, Token![,]>> {
    let mut nested_meta_items = Punctuated::new();
    let mut first = true;

    while !tts.is_empty() {
        let prev_comma = if first {
            first = false;
            None
        } else if let TokenTree::Punct(ref op) = tts[0] {
            if op.spacing() != Spacing::Alone {
                return None;
            }
            if op.as_char() != ',' {
                return None;
            }
            let tok = Token![,]([op.span()]);
            tts = &tts[1..];
            if tts.is_empty() {
                break;
            }
            Some(tok)
        } else {
            return None;
        };
        let (nested, rest) = match nested_meta_item_from_tokens(tts) {
            Some(pair) => pair,
            None => return None,
        };
        if let Some(comma) = prev_comma {
            nested_meta_items.push_punct(comma);
        }
        nested_meta_items.push_value(nested);
        tts = rest;
    }

    Some(nested_meta_items)
}

ast_enum! {
    /// Distinguishes between attributes that decorate an item and attributes
    /// that are contained within an item.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    ///
    /// # Outer attributes
    ///
    /// - `#[repr(transparent)]`
    /// - `/// # Example`
    /// - `/** Please file an issue */`
    ///
    /// # Inner attributes
    ///
    /// - `#![feature(proc_macro)]`
    /// - `//! # Example`
    /// - `/*! Please file an issue */`
    #[cfg_attr(feature = "clone-impls", derive(Copy))]
    pub enum AttrStyle {
        Outer,
        Inner(Token![!]),
    }
}

ast_enum_of_structs! {
    /// Content of a compile-time structured attribute.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    ///
    /// ## Word
    ///
    /// A meta word is like the `test` in `#[test]`.
    ///
    /// ## List
    ///
    /// A meta list is like the `derive(Copy)` in `#[derive(Copy)]`.
    ///
    /// ## NameValue
    ///
    /// A name-value meta is like the `path = "..."` in `#[path =
    /// "sys/windows.rs"]`.
    ///
    /// # Syntax tree enum
    ///
    /// This type is a [syntax tree enum].
    ///
    /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums
    pub enum Meta {
        pub Word(Ident),
        /// A structured list within an attribute, like `derive(Copy, Clone)`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub List(MetaList {
            pub ident: Ident,
            pub paren_token: token::Paren,
            pub nested: Punctuated<NestedMeta, Token![,]>,
        }),
        /// A name-value pair within an attribute, like `feature = "nightly"`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub NameValue(MetaNameValue {
            pub ident: Ident,
            pub eq_token: Token![=],
            pub lit: Lit,
        }),
    }
}

impl Meta {
    /// Returns the identifier that begins this structured meta item.
    ///
    /// For example this would return the `test` in `#[test]`, the `derive` in
    /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`.
    pub fn name(&self) -> Ident {
        match *self {
            Meta::Word(ref meta) => meta.clone(),
            Meta::List(ref meta) => meta.ident.clone(),
            Meta::NameValue(ref meta) => meta.ident.clone(),
        }
    }
}

ast_enum_of_structs! {
    /// Element of a compile-time attribute list.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    pub enum NestedMeta {
        /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which
        /// would be a nested `Meta::Word`.
        pub Meta(Meta),

        /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`.
        pub Literal(Lit),
    }
}

pub trait FilterAttrs<'a> {
    type Ret: Iterator<Item = &'a Attribute>;

    fn outer(self) -> Self::Ret;
    fn inner(self) -> Self::Ret;
}

impl<'a, T> FilterAttrs<'a> for T
where
    T: IntoIterator<Item = &'a Attribute>,
{
    type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;

    fn outer(self) -> Self::Ret {
        fn is_outer(attr: &&Attribute) -> bool {
            match attr.style {
                AttrStyle::Outer => true,
                _ => false,
            }
        }
        self.into_iter().filter(is_outer)
    }

    fn inner(self) -> Self::Ret {
        fn is_inner(attr: &&Attribute) -> bool {
            match attr.style {
                AttrStyle::Inner(_) => true,
                _ => false,
            }
        }
        self.into_iter().filter(is_inner)
    }
}

#[cfg(feature = "parsing")]
pub mod parsing {
    use super::*;
    use buffer::Cursor;
    use parse::{ParseStream, Result};
    use parse_error;
    use proc_macro2::{Literal, Punct, Spacing, Span, TokenTree};
    use synom::PResult;

    fn eq(span: Span) -> TokenTree {
        let mut op = Punct::new('=', Spacing::Alone);
        op.set_span(span);
        op.into()
    }

    impl Attribute {
        pub fn parse_outer(input: ParseStream) -> Result<Vec<Self>> {
            let mut attrs = Vec::new();
            while input.peek(Token![#]) {
                attrs.push(input.parse_synom(Attribute::old_parse_outer)?);
            }
            Ok(attrs)
        }

        pub fn parse_inner(input: ParseStream) -> Result<Vec<Self>> {
            let mut attrs = Vec::new();
            while input.peek(Token![#]) && input.peek2(Token![!]) {
                attrs.push(input.parse_synom(Attribute::old_parse_inner)?);
            }
            Ok(attrs)
        }

        named!(pub old_parse_inner -> Self, alt!(
            do_parse!(
                pound: punct!(#) >>
                bang: punct!(!) >>
                path_and_tts: brackets!(tuple!(
                    call!(Path::old_parse_mod_style),
                    syn!(TokenStream),
                )) >>
                ({
                    let (bracket, (path, tts)) = path_and_tts;

                    Attribute {
                        style: AttrStyle::Inner(bang),
                        path: path,
                        tts: tts,
                        pound_token: pound,
                        bracket_token: bracket,
                    }
                })
            )
            |
            map!(
                call!(lit_doc_comment, Comment::Inner),
                |lit| {
                    let span = lit.span();
                    Attribute {
                        style: AttrStyle::Inner(Token![!](span)),
                        path: Ident::new("doc", span).into(),
                        tts: vec![
                            eq(span),
                            lit,
                        ].into_iter().collect(),
                        pound_token: Token![#](span),
                        bracket_token: token::Bracket(span),
                    }
                }
            )
        ));

        named!(pub old_parse_outer -> Self, alt!(
            do_parse!(
                pound: punct!(#) >>
                path_and_tts: brackets!(tuple!(
                    call!(Path::old_parse_mod_style),
                    syn!(TokenStream),
                )) >>
                ({
                    let (bracket, (path, tts)) = path_and_tts;

                    Attribute {
                        style: AttrStyle::Outer,
                        path: path,
                        tts: tts,
                        pound_token: pound,
                        bracket_token: bracket,
                    }
                })
            )
            |
            map!(
                call!(lit_doc_comment, Comment::Outer),
                |lit| {
                    let span = lit.span();
                    Attribute {
                        style: AttrStyle::Outer,
                        path: Ident::new("doc", span).into(),
                        tts: vec![
                            eq(span),
                            lit,
                        ].into_iter().collect(),
                        pound_token: Token![#](span),
                        bracket_token: token::Bracket(span),
                    }
                }
            )
        ));
    }

    enum Comment {
        Inner,
        Outer,
    }

    fn lit_doc_comment(input: Cursor, style: Comment) -> PResult<TokenTree> {
        match input.literal() {
            Some((lit, rest)) => {
                let string = lit.to_string();
                let ok = match style {
                    Comment::Inner => string.starts_with("//!") || string.starts_with("/*!"),
                    Comment::Outer => string.starts_with("///") || string.starts_with("/**"),
                };
                if ok {
                    let mut new = Literal::string(&string);
                    new.set_span(lit.span());
                    Ok((new.into(), rest))
                } else {
                    parse_error()
                }
            }
            _ => parse_error(),
        }
    }
}

#[cfg(feature = "printing")]
mod printing {
    use super::*;
    use proc_macro2::TokenStream;
    use quote::ToTokens;

    impl ToTokens for Attribute {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.pound_token.to_tokens(tokens);
            if let AttrStyle::Inner(ref b) = self.style {
                b.to_tokens(tokens);
            }
            self.bracket_token.surround(tokens, |tokens| {
                self.path.to_tokens(tokens);
                self.tts.to_tokens(tokens);
            });
        }
    }

    impl ToTokens for MetaList {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.ident.to_tokens(tokens);
            self.paren_token.surround(tokens, |tokens| {
                self.nested.to_tokens(tokens);
            })
        }
    }

    impl ToTokens for MetaNameValue {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.ident.to_tokens(tokens);
            self.eq_token.to_tokens(tokens);
            self.lit.to_tokens(tokens);
        }
    }
}
<commit_msg>Switch attribute parsing to Parse<commit_after>// Copyright 2018 Syn Developers
//
// 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 super::*;
use punctuated::Punctuated;

use std::iter;

use proc_macro2::{Delimiter, Spacing, TokenStream, TokenTree};

#[cfg(feature = "extra-traits")]
use std::hash::{Hash, Hasher};
#[cfg(feature = "extra-traits")]
use tt::TokenStreamHelper;

ast_struct! {
    /// An attribute like `#[repr(transparent)]`.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    ///
    /// # Syntax
    ///
    /// Rust has six types of attributes.
    ///
    /// - Outer attributes like `#[repr(transparent)]`. These appear outside or
    ///   in front of the item they describe.
    /// - Inner attributes like `#![feature(proc_macro)]`. These appear inside
    ///   of the item they describe, usually a module.
    /// - Outer doc comments like `/// # Example`.
    /// - Inner doc comments like `//! Please file an issue`.
    /// - Outer block comments `/** # Example */`.
    /// - Inner block comments `/*! Please file an issue */`.
    ///
    /// The `style` field of type `AttrStyle` distinguishes whether an attribute
    /// is outer or inner. Doc comments and block comments are promoted to
    /// attributes, as this is how they are processed by the compiler and by
    /// `macro_rules!` macros.
    ///
    /// The `path` field gives the possibly colon-delimited path against which
    /// the attribute is resolved. It is equal to `"doc"` for desugared doc
    /// comments. The `tts` field contains the rest of the attribute body as
    /// tokens.
    ///
    /// ```text
    /// #[derive(Copy)]      #[crate::precondition x < 5]
    ///   ^^^^^^~~~~~~         ^^^^^^^^^^^^^^^^^^^ ~~~~~
    ///    path  tts                   path         tts
    /// ```
    ///
    /// Use the [`interpret_meta`] method to try parsing the tokens of an
    /// attribute into the structured representation that is used by convention
    /// across most Rust libraries.
    ///
    /// [`interpret_meta`]: #method.interpret_meta
    pub struct Attribute #manual_extra_traits {
        pub pound_token: Token![#],
        pub style: AttrStyle,
        pub bracket_token: token::Bracket,
        pub path: Path,
        pub tts: TokenStream,
    }
}

#[cfg(feature = "extra-traits")]
impl Eq for Attribute {}

#[cfg(feature = "extra-traits")]
impl PartialEq for Attribute {
    fn eq(&self, other: &Self) -> bool {
        self.style == other.style
            && self.pound_token == other.pound_token
            && self.bracket_token == other.bracket_token
            && self.path == other.path
            && TokenStreamHelper(&self.tts) == TokenStreamHelper(&other.tts)
    }
}

#[cfg(feature = "extra-traits")]
impl Hash for Attribute {
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        self.style.hash(state);
        self.pound_token.hash(state);
        self.bracket_token.hash(state);
        self.path.hash(state);
        TokenStreamHelper(&self.tts).hash(state);
    }
}

impl Attribute {
    /// Parses the tokens after the path as a [`Meta`](enum.Meta.html) if
    /// possible.
    pub fn interpret_meta(&self) -> Option<Meta> {
        let name = if self.path.segments.len() == 1 {
            &self.path.segments.first().unwrap().value().ident
        } else {
            return None;
        };

        if self.tts.is_empty() {
            return Some(Meta::Word(name.clone()));
        }

        let tts = self.tts.clone().into_iter().collect::<Vec<_>>();

        if tts.len() == 1 {
            if let Some(meta) = Attribute::extract_meta_list(name.clone(), &tts[0]) {
                return Some(meta);
            }
        }

        if tts.len() == 2 {
            if let Some(meta) = Attribute::extract_name_value(name.clone(), &tts[0], &tts[1]) {
                return Some(meta);
            }
        }

        None
    }

    fn extract_meta_list(ident: Ident, tt: &TokenTree) -> Option<Meta> {
        let g = match *tt {
            TokenTree::Group(ref g) => g,
            _ => return None,
        };
        if g.delimiter() != Delimiter::Parenthesis {
            return None;
        }
        let tokens = g.stream().clone().into_iter().collect::<Vec<_>>();
        let nested = match list_of_nested_meta_items_from_tokens(&tokens) {
            Some(n) => n,
            None => return None,
        };
        Some(Meta::List(MetaList {
            paren_token: token::Paren(g.span()),
            ident: ident,
            nested: nested,
        }))
    }

    fn extract_name_value(ident: Ident, a: &TokenTree, b: &TokenTree) -> Option<Meta> {
        let a = match *a {
            TokenTree::Punct(ref o) => o,
            _ => return None,
        };
        if a.spacing() != Spacing::Alone {
            return None;
        }
        if a.as_char() != '=' {
            return None;
        }

        match *b {
            TokenTree::Literal(ref l) if !l.to_string().starts_with('/') => {
                Some(Meta::NameValue(MetaNameValue {
                    ident: ident,
                    eq_token: Token![=]([a.span()]),
                    lit: Lit::new(l.clone()),
                }))
            }
            TokenTree::Ident(ref v) => match &v.to_string()[..] {
                v @ "true" | v @ "false" => Some(Meta::NameValue(MetaNameValue {
                    ident: ident,
                    eq_token: Token![=]([a.span()]),
                    lit: Lit::Bool(LitBool {
                        value: v == "true",
                        span: b.span(),
                    }),
                })),
                _ => None,
            },
            _ => None,
        }
    }
}

fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMeta, &[TokenTree])> {
    assert!(!tts.is_empty());

    match tts[0] {
        TokenTree::Literal(ref lit) => {
            if lit.to_string().starts_with('/') {
                None
            } else {
                let lit = Lit::new(lit.clone());
                Some((NestedMeta::Literal(lit), &tts[1..]))
            }
        }

        TokenTree::Ident(ref ident) => {
            if tts.len() >= 3 {
                if let Some(meta) = Attribute::extract_name_value(ident.clone(), &tts[1], &tts[2]) {
                    return Some((NestedMeta::Meta(meta), &tts[3..]));
                }
            }

            if tts.len() >= 2 {
                if let Some(meta) = Attribute::extract_meta_list(ident.clone(), &tts[1]) {
                    return Some((NestedMeta::Meta(meta), &tts[2..]));
                }
            }

            Some((Meta::Word(ident.clone()).into(), &tts[1..]))
        }

        _ => None,
    }
}

fn list_of_nested_meta_items_from_tokens(
    mut tts: &[TokenTree],
) -> Option<Punctuated<NestedMeta, Token![,]>> {
    let mut nested_meta_items = Punctuated::new();
    let mut first = true;

    while !tts.is_empty() {
        let prev_comma = if first {
            first = false;
            None
        } else if let TokenTree::Punct(ref op) = tts[0] {
            if op.spacing() != Spacing::Alone {
                return None;
            }
            if op.as_char() != ',' {
                return None;
            }
            let tok = Token![,]([op.span()]);
            tts = &tts[1..];
            if tts.is_empty() {
                break;
            }
            Some(tok)
        } else {
            return None;
        };
        let (nested, rest) = match nested_meta_item_from_tokens(tts) {
            Some(pair) => pair,
            None => return None,
        };
        if let Some(comma) = prev_comma {
            nested_meta_items.push_punct(comma);
        }
        nested_meta_items.push_value(nested);
        tts = rest;
    }

    Some(nested_meta_items)
}

ast_enum! {
    /// Distinguishes between attributes that decorate an item and attributes
    /// that are contained within an item.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    ///
    /// # Outer attributes
    ///
    /// - `#[repr(transparent)]`
    /// - `/// # Example`
    /// - `/** Please file an issue */`
    ///
    /// # Inner attributes
    ///
    /// - `#![feature(proc_macro)]`
    /// - `//! # Example`
    /// - `/*! Please file an issue */`
    #[cfg_attr(feature = "clone-impls", derive(Copy))]
    pub enum AttrStyle {
        Outer,
        Inner(Token![!]),
    }
}

ast_enum_of_structs! {
    /// Content of a compile-time structured attribute.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    ///
    /// ## Word
    ///
    /// A meta word is like the `test` in `#[test]`.
    ///
    /// ## List
    ///
    /// A meta list is like the `derive(Copy)` in `#[derive(Copy)]`.
    ///
    /// ## NameValue
    ///
    /// A name-value meta is like the `path = "..."` in `#[path =
    /// "sys/windows.rs"]`.
    ///
    /// # Syntax tree enum
    ///
    /// This type is a [syntax tree enum].
    ///
    /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums
    pub enum Meta {
        pub Word(Ident),
        /// A structured list within an attribute, like `derive(Copy, Clone)`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub List(MetaList {
            pub ident: Ident,
            pub paren_token: token::Paren,
            pub nested: Punctuated<NestedMeta, Token![,]>,
        }),
        /// A name-value pair within an attribute, like `feature = "nightly"`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub NameValue(MetaNameValue {
            pub ident: Ident,
            pub eq_token: Token![=],
            pub lit: Lit,
        }),
    }
}

impl Meta {
    /// Returns the identifier that begins this structured meta item.
    ///
    /// For example this would return the `test` in `#[test]`, the `derive` in
    /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`.
    pub fn name(&self) -> Ident {
        match *self {
            Meta::Word(ref meta) => meta.clone(),
            Meta::List(ref meta) => meta.ident.clone(),
            Meta::NameValue(ref meta) => meta.ident.clone(),
        }
    }
}

ast_enum_of_structs! {
    /// Element of a compile-time attribute list.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    pub enum NestedMeta {
        /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which
        /// would be a nested `Meta::Word`.
        pub Meta(Meta),

        /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`.
        pub Literal(Lit),
    }
}

pub trait FilterAttrs<'a> {
    type Ret: Iterator<Item = &'a Attribute>;

    fn outer(self) -> Self::Ret;
    fn inner(self) -> Self::Ret;
}

impl<'a, T> FilterAttrs<'a> for T
where
    T: IntoIterator<Item = &'a Attribute>,
{
    type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;

    fn outer(self) -> Self::Ret {
        fn is_outer(attr: &&Attribute) -> bool {
            match attr.style {
                AttrStyle::Outer => true,
                _ => false,
            }
        }
        self.into_iter().filter(is_outer)
    }

    fn inner(self) -> Self::Ret {
        fn is_inner(attr: &&Attribute) -> bool {
            match attr.style {
                AttrStyle::Inner(_) => true,
                _ => false,
            }
        }
        self.into_iter().filter(is_inner)
    }
}

#[cfg(feature = "parsing")]
pub mod parsing {
    use super::*;
    use parse::{ParseStream, Result};

    impl Attribute {
        pub fn parse_outer(input: ParseStream) -> Result<Vec<Self>> {
            let mut attrs = Vec::new();
            while input.peek(Token![#]) {
                attrs.push(input.call(single_parse_outer)?);
            }
            Ok(attrs)
        }

        pub fn parse_inner(input: ParseStream) -> Result<Vec<Self>> {
            let mut attrs = Vec::new();
            while input.peek(Token![#]) && input.peek2(Token![!]) {
                attrs.push(input.call(single_parse_inner)?);
            }
            Ok(attrs)
        }
    }

    pub fn single_parse_inner(input: ParseStream) -> Result<Attribute> {
        let content;
        Ok(Attribute {
            pound_token: input.parse()?,
            style: AttrStyle::Inner(input.parse()?),
            bracket_token: bracketed!(content in input),
            path: content.call(Path::parse_mod_style)?,
            tts: content.parse()?,
        })
    }

    pub fn single_parse_outer(input: ParseStream) -> Result<Attribute> {
        let content;
        Ok(Attribute {
            pound_token: input.parse()?,
            style: AttrStyle::Outer,
            bracket_token: bracketed!(content in input),
            path: content.call(Path::parse_mod_style)?,
            tts: content.parse()?,
        })
    }
}

#[cfg(feature = "printing")]
mod printing {
    use super::*;
    use proc_macro2::TokenStream;
    use quote::ToTokens;

    impl ToTokens for Attribute {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.pound_token.to_tokens(tokens);
            if let AttrStyle::Inner(ref b) = self.style {
                b.to_tokens(tokens);
            }
            self.bracket_token.surround(tokens, |tokens| {
                self.path.to_tokens(tokens);
                self.tts.to_tokens(tokens);
            });
        }
    }

    impl ToTokens for MetaList {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.ident.to_tokens(tokens);
            self.paren_token.surround(tokens, |tokens| {
                self.nested.to_tokens(tokens);
            })
        }
    }

    impl ToTokens for MetaNameValue {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.ident.to_tokens(tokens);
            self.eq_token.to_tokens(tokens);
            self.lit.to_tokens(tokens);
        }
    }
}
<|endoftext|>


<commit_before>use std::ffi::OsString;
use std::io;
use std::io::Stderr;
use std::io::Stdin;
use std::io::Stdout;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use structopt::StructOpt;
use super::Error;
use super::Result;

enum Context {
    /// The path is absolute (has root).
    Absolute,
    /// The `interactive` option is present.
    Interactive,
}

#[derive(Debug, StructOpt)]
#[structopt(about = "Recursively change the mode of files or directories.")]
struct Options {
    /// The mode to apply to files.
    #[structopt(short = "f", long = "file", name = "file-mode")]
    file: Option<String>,

    /// The mode to apply to directories.
    #[structopt(short = "d", long = "dir", name = "dir-mode")]
    dir: Option<String>,

    /// Do not overwrite any files (verbose).
    #[structopt(short = "D", long = "dry-run")]
    dry_run: bool,

    /// Prompt before overwriting each file.
    #[structopt(short = "i", long = "interactive")]
    interactive: bool,

    /// Suppress all interaction.
    #[structopt(short = "s", long = "suppress")]
    suppress: bool,

    /// Explain what's being done.
    #[structopt(short = "V", long = "verbose")]
    verbose: bool,

    /// Show this message.
    #[structopt(short = "h", long = "help")]
    help: bool,

    /// Show the version.
    #[structopt(short = "v", long = "version")]
    version: bool,

    /// The paths to be modified by this tool.
    #[structopt(name = "PATHS", parse(from_str))]
    paths: Vec<PathBuf>,
}

pub struct Rchmod {
    options: Options,
    stderr: Stderr,
    stdout: Stdout,
    stdin: Stdin,
}

impl Rchmod {
    /// Constructs this program from an iterable of arguments.
    pub fn from_iter<I>(iter: I) -> Result<Self>
    where
        Self: Sized,
        I: IntoIterator,
        I::Item: Into<OsString> + Clone,
    {
        return Ok(
            Self {
                options: Options::from_iter_safe(iter)?,
                stderr: io::stderr(),
                stdout: io::stdout(),
                stdin: io::stdin(),
            }
        );
    }

    /// Replaces the standard error stream for this program.
    pub fn stderr(&mut self, stderr: Stderr) -> &mut Self {
        self.stderr = stderr;
        return self;
    }

    /// Replaces the standard output stream for this program.
    pub fn stdout(&mut self, stdout: Stdout) -> &mut Self {
        self.stdout = stdout;
        return self;
    }

    /// Replaces the standard input stream for this program.
    pub fn stdin(&mut self, stdin: Stdin) -> &mut Self {
        self.stdin = stdin;
        return self;
    }

    /// Runs this program and writes all errors.
    pub fn run(&mut self) -> Result<()> {
        let mut run = || -> Result<()> {
            // Write the help or version message
            if self.options.help {
                return self.help();
            }
            if self.options.version {
                return self.version();
            }

            // Handle the paths
            return self.validate()?.change();
        };

        match run() {
            Ok(val) => {
                return Ok(val);
            },
            Err(err) => {
                writeln!(self.stderr, "Error: {}", err)?;
                return Err(err);
            },
        }
    }

    /// Validates the options.
    fn validate(&mut self) -> Result<&mut Self> {
        return if {
            self.options.interactive && self.options.suppress
        } {
            Err(Error::Conflict)
        } else if {
            !self.has_file() && !self.has_dir()
        } {
            Err(Error::Missing)
        } else {
            Ok(self)
        };
    }

    /// Writes the help message to the standard error stream.
    fn help(&mut self) -> Result<()> {
        Options::clap().write_help(&mut self.stderr)?;
        writeln!(self.stderr, "")?;
        return Ok(());
    }

    /// Writes the version message to the standard error stream.
    fn version(&mut self) -> Result<()> {
        Options::clap().write_version(&mut self.stderr)?;
        writeln!(self.stderr, "")?;
        return Ok(());
    }

    /// Authorizes directory and file access by prompting the user and reading
    /// from the standard input stream.
    fn authorize(&mut self, path: &PathBuf, context: Context) -> Result<bool> {
        // Determine the appropriate prompt
        let prompt = match context {
            Context::Absolute => "is absolute",
            Context::Interactive => "mode will be changed",
        };

        let mut input = String::new();
        loop {
            // Prompt the user and normalize the input
            write!(self.stderr, r#""{}" {} - continue? [y/n] "#, path.display(), prompt)?;
            self.stdin.read_line(&mut input)?;

            // The response must be `y` or `n`
            match input.trim().to_lowercase().as_str() {
                "n" => {
                    if self.options.verbose {
                        writeln!(self.stderr, "Skipped.")?;
                    }
                    return Ok(false);
                },
                "y" => {
                    return Ok(true);
                },
                _ => {
                    input.clear();
                },
            }
        }
    }

    /// Changes all paths provided by the user. Authorization may be requested
    /// if the `suppress` option is not present.
    fn change(&mut self) -> Result<()> {
        for path in self.options.paths.to_owned() {
            if !self.options.suppress && path.has_root() {
                // Authorize absolute paths (optional)
                if let Ok(false) = self.authorize(&path, Context::Absolute) {
                    continue;
                }
            }

            // Dig through directories
            if path.is_file() {
                self.change_one(&path)?;
            } else if path.is_dir() {
                self.change_many(&path)?;
            }
        }
        return Ok(());
    }

    /// Changes all entries under the given directory and writes all errors.
    fn change_many(&mut self, path: &PathBuf) -> Result<()> {
        let mut change_many = || -> Result<()> {
            // Change the children first
            for entry in path.read_dir()? {
                let path = entry?.path();

                // Dig through directories
                if path.is_file() {
                    self.change_one(&path)?;
                } else if path.is_dir() {
                    self.change_many(&path)?;
                }
            }

            // Change the parent last
            self.change_one(path)?;

            return Ok(());
        };

        return if let Err(err) = change_many() {
            self.write_error("Cannot access", path, &err)
        } else {
            Ok(())
        };
    }

    /// Changes the mode of the given path and writes all errors. Authorization
    /// may be requested and additional information may be written if the
    /// `interactive` and `verbose` options are present. The path will not be
    /// changed during a `dry-run`.
    fn change_one(&mut self, path: &PathBuf) -> Result<()> {
        let mut change_one = || -> Result<()> {
            if self.options.interactive && !self.options.suppress {
                // Authorize every path (optional)
                if let Ok(false) = self.authorize(path, Context::Interactive) {
                    return Ok(());
                }
            }

            if !self.options.dry_run {
                // Change the mode of the path
                let mode = if path.is_file() && self.has_file() {
                    self.options.file.as_ref().unwrap().to_owned()
                } else if path.is_dir() && self.has_dir() {
                    self.options.dir.as_ref().unwrap().to_owned()
                } else {
                    "".to_owned()
                };

                if mode.len() > 0 {
                    Command::new("chmod")
                        .args(&[mode, path.display().to_string()])
                        .status()
                        .unwrap();

                    if self.options.verbose {
                        // Write the results (optional)
                        self.write_result("changed", path)?;
                    }
                }
            } else {
                // Perform a dry run (optional)
                self.write_result("will be changed", path)?;
            }
            return Ok(());
        };

        return if let Err(err) = change_one() {
            self.write_error("Cannot change permissions", path, &err)
        } else {
            Ok(())
        };
    }

    /// Determines if the file option is present.
    fn has_file(&self) -> bool {
        return self.options.file.is_some();
    }

    /// Determines if the directory option is present.
    fn has_dir(&self) -> bool {
        return self.options.dir.is_some();
    }

    /// Writes a path related error to the standard error stream.
    fn write_error(&mut self, msg: &str, path: &PathBuf, err: &Error) -> Result<()> {
        writeln!(self.stderr, r#"Error: {} "{}": {}"#, msg, path.display(), err)?;
        return Ok(());
    }

    /// Writes the result of an operation to the standard output stream.
    fn write_result(&mut self, msg: &str, path: &PathBuf) -> Result<()> {
        writeln!(self.stdout, r#""{}": mode {}."#, path.display(), msg)?;
        return Ok(());
    }
}
<commit_msg>Reordered methods<commit_after>use std::ffi::OsString;
use std::io;
use std::io::Stderr;
use std::io::Stdin;
use std::io::Stdout;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use structopt::StructOpt;
use super::Error;
use super::Result;

enum Context {
    /// The path is absolute (has root).
    Absolute,
    /// The `interactive` option is present.
    Interactive,
}

#[derive(Debug, StructOpt)]
#[structopt(about = "Recursively change the mode of files or directories.")]
struct Options {
    /// The mode to apply to files.
    #[structopt(short = "f", long = "file", name = "file-mode")]
    file: Option<String>,

    /// The mode to apply to directories.
    #[structopt(short = "d", long = "dir", name = "dir-mode")]
    dir: Option<String>,

    /// Do not overwrite any files (verbose).
    #[structopt(short = "D", long = "dry-run")]
    dry_run: bool,

    /// Prompt before overwriting each file.
    #[structopt(short = "i", long = "interactive")]
    interactive: bool,

    /// Suppress all interaction.
    #[structopt(short = "s", long = "suppress")]
    suppress: bool,

    /// Explain what's being done.
    #[structopt(short = "V", long = "verbose")]
    verbose: bool,

    /// Show this message.
    #[structopt(short = "h", long = "help")]
    help: bool,

    /// Show the version.
    #[structopt(short = "v", long = "version")]
    version: bool,

    /// The paths to be modified by this tool.
    #[structopt(name = "PATHS", parse(from_str))]
    paths: Vec<PathBuf>,
}

pub struct Rchmod {
    options: Options,
    stderr: Stderr,
    stdout: Stdout,
    stdin: Stdin,
}

impl Rchmod {
    /// Constructs this program from an iterable of arguments.
    pub fn from_iter<I>(iter: I) -> Result<Self>
    where
        Self: Sized,
        I: IntoIterator,
        I::Item: Into<OsString> + Clone,
    {
        return Ok(
            Self {
                options: Options::from_iter_safe(iter)?,
                stderr: io::stderr(),
                stdout: io::stdout(),
                stdin: io::stdin(),
            }
        );
    }

    /// Replaces the standard error stream for this program.
    pub fn stderr(&mut self, stderr: Stderr) -> &mut Self {
        self.stderr = stderr;
        return self;
    }

    /// Replaces the standard output stream for this program.
    pub fn stdout(&mut self, stdout: Stdout) -> &mut Self {
        self.stdout = stdout;
        return self;
    }

    /// Replaces the standard input stream for this program.
    pub fn stdin(&mut self, stdin: Stdin) -> &mut Self {
        self.stdin = stdin;
        return self;
    }

    /// Runs this program and writes all errors.
    pub fn run(&mut self) -> Result<()> {
        let mut run = || -> Result<()> {
            // Write the help or version message
            if self.options.help {
                return self.help();
            }
            if self.options.version {
                return self.version();
            }

            // Validate the options
            self.validate()?;

            // Handle the paths
            return self.change();
        };

        match run() {
            Ok(val) => {
                return Ok(val);
            },
            Err(err) => {
                writeln!(self.stderr, "Error: {}", err)?;
                return Err(err);
            },
        }
    }

    /// Writes the help message to the standard error stream.
    fn help(&mut self) -> Result<()> {
        Options::clap().write_help(&mut self.stderr)?;
        writeln!(self.stderr, "")?;
        return Ok(());
    }

    /// Writes the version message to the standard error stream.
    fn version(&mut self) -> Result<()> {
        Options::clap().write_version(&mut self.stderr)?;
        writeln!(self.stderr, "")?;
        return Ok(());
    }

    /// Validates the options.
    fn validate(&mut self) -> Result<()> {
        return if {
            self.options.interactive && self.options.suppress
        } {
            Err(Error::Conflict)
        } else if {
            !self.has_file() && !self.has_dir()
        } {
            Err(Error::Missing)
        } else {
            Ok(())
        };
    }

    /// Changes all paths provided by the user. Authorization may be requested
    /// if the `suppress` option is not present.
    fn change(&mut self) -> Result<()> {
        for path in self.options.paths.to_owned() {
            if !self.options.suppress && path.has_root() {
                // Authorize absolute paths (optional)
                if let Ok(false) = self.authorize(&path, Context::Absolute) {
                    continue;
                }
            }

            // Dig through directories
            if path.is_file() {
                self.change_one(&path)?;
            } else if path.is_dir() {
                self.change_many(&path)?;
            }
        }
        return Ok(());
    }

    /// Changes all entries under the given directory and writes all errors.
    fn change_many(&mut self, path: &PathBuf) -> Result<()> {
        let mut change_many = || -> Result<()> {
            // Change the children first
            for entry in path.read_dir()? {
                let path = entry?.path();

                // Dig through directories
                if path.is_file() {
                    self.change_one(&path)?;
                } else if path.is_dir() {
                    self.change_many(&path)?;
                }
            }

            // Change the parent last
            self.change_one(path)?;

            return Ok(());
        };

        return if let Err(err) = change_many() {
            self.write_error("Cannot access", path, &err)
        } else {
            Ok(())
        };
    }

    /// Changes the mode of the given path and writes all errors. Authorization
    /// may be requested and additional information may be written if the
    /// `interactive` and `verbose` options are present. The path will not be
    /// changed during a `dry-run`.
    fn change_one(&mut self, path: &PathBuf) -> Result<()> {
        let mut change_one = || -> Result<()> {
            if self.options.interactive && !self.options.suppress {
                // Authorize every path (optional)
                if let Ok(false) = self.authorize(path, Context::Interactive) {
                    return Ok(());
                }
            }

            if !self.options.dry_run {
                // Change the mode of the path
                let mode = if path.is_file() && self.has_file() {
                    self.options.file.as_ref().unwrap().to_owned()
                } else if path.is_dir() && self.has_dir() {
                    self.options.dir.as_ref().unwrap().to_owned()
                } else {
                    "".to_owned()
                };

                if mode.len() > 0 {
                    Command::new("chmod")
                        .args(&[mode, path.display().to_string()])
                        .status()
                        .unwrap();

                    if self.options.verbose {
                        // Write the results (optional)
                        self.write_result("changed", path)?;
                    }
                }
            } else {
                // Perform a dry run (optional)
                self.write_result("will be changed", path)?;
            }
            return Ok(());
        };

        return if let Err(err) = change_one() {
            self.write_error("Cannot change permissions", path, &err)
        } else {
            Ok(())
        };
    }

    /// Authorizes directory and file access by prompting the user and reading
    /// from the standard input stream.
    fn authorize(&mut self, path: &PathBuf, context: Context) -> Result<bool> {
        // Determine the appropriate prompt
        let prompt = match context {
            Context::Absolute => "is absolute",
            Context::Interactive => "mode will be changed",
        };

        let mut input = String::new();
        loop {
            // Prompt the user and normalize the input
            write!(self.stderr, r#""{}" {} - continue? [y/n] "#, path.display(), prompt)?;
            self.stdin.read_line(&mut input)?;

            // The response must be `y` or `n`
            match input.trim().to_lowercase().as_str() {
                "n" => {
                    if self.options.verbose {
                        writeln!(self.stderr, "Skipped.")?;
                    }
                    return Ok(false);
                },
                "y" => {
                    return Ok(true);
                },
                _ => {
                    input.clear();
                },
            }
        }
    }

    /// Determines if the file option is present.
    fn has_file(&self) -> bool {
        return self.options.file.is_some();
    }

    /// Determines if the directory option is present.
    fn has_dir(&self) -> bool {
        return self.options.dir.is_some();
    }

    /// Writes a path related error to the standard error stream.
    fn write_error(&mut self, msg: &str, path: &PathBuf, err: &Error) -> Result<()> {
        writeln!(self.stderr, r#"Error: {} "{}": {}"#, msg, path.display(), err)?;
        return Ok(());
    }

    /// Writes the result of an operation to the standard output stream.
    fn write_result(&mut self, msg: &str, path: &PathBuf) -> Result<()> {
        writeln!(self.stdout, r#""{}": mode {}."#, path.display(), msg)?;
        return Ok(());
    }
}
<|endoftext|>