<commit_before>#[cfg(test)]
mod tests;
pub mod response;

pub use message::response::*;
use notification::Notification;
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::str;
use curl::http;
use rustc_serialize::json::{self, Json, ToJson};

#[derive(PartialEq, Debug, Clone)]
pub enum Priority {
  Normal, High
}

/// Represents a GCM message. Construct the GCM message
/// using various utility methods and finally send it.
/// # Examples:
/// ```rust
/// use gcm::Message;
///
/// let message = Message::new("<registration id>").dry_run(true);
/// ```
pub struct Message<'a> {
  to: &'a str,
  registration_ids: Option<Vec<String>>,
  collapse_key: Option<&'a str>,
  priority: Option<Priority>,
  content_available: Option<bool>,
  delay_while_idle: Option<bool>,
  time_to_live: Option<i32>,
  restricted_package_name: Option<&'a str>,
  dry_run: Option<bool>,
  data: Option<HashMap<String, String>>,
  notification: Option<Notification<'a>>,
}

impl <'a> ToJson for Message<'a> {
  fn to_json(&self) -> Json {
    let mut root = BTreeMap::new();

    root.insert("to".to_string(), self.to.to_json());

    if self.registration_ids.is_some() {
      root.insert("registration_ids".to_string(),
          self.registration_ids.clone().unwrap().to_json());
    }

    if self.collapse_key.is_some() {
      root.insert("collapse_key".to_string(), self.collapse_key.clone().unwrap().to_json());
    }

    if self.priority.is_some() {
      root.insert("priority".to_string(), match self.priority.clone().unwrap() {
        Priority::Normal => Json::String("normal".to_string()),
        Priority::High => Json::String("high".to_string()),
      });
    }

    if self.content_available.is_some() {
      root.insert("content_available".to_string(), self.content_available.unwrap().to_json());
    }

    if self.delay_while_idle.is_some() {
      root.insert("delay_while_idle".to_string(), self.delay_while_idle.unwrap().to_json());
    }

    if self.time_to_live.is_some() {
      root.insert("time_to_live".to_string(), self.time_to_live.unwrap().to_json());
    }

    if self.restricted_package_name.is_some() {
      root.insert("restricted_package_name".to_string(), self.restricted_package_name.clone().unwrap().to_json());
    }

    if self.dry_run.is_some() {
      root.insert("dry_run".to_string(), self.dry_run.unwrap().to_json());
    }

    if self.data.is_some() {
      root.insert("data".to_string(), self.data.clone().unwrap().to_json());
    }

    if self.notification.is_some() {
      root.insert("notification".to_string(), self.notification.clone().unwrap().to_json());
    }

    Json::Object(root)
  }
}

impl <'a> Message<'a> {
  /// Get a new instance of Message. You need to supply either
  /// a registration id, or a topic (/topic/...).
  pub fn new(to: &'a str) -> Message {
    Message {
      to: to,
      registration_ids: None,
      collapse_key: None,
      priority: None,
      content_available: None,
      delay_while_idle: None,
      time_to_live: None,
      restricted_package_name: None,
      dry_run: None,
      data: None,
      notification: None,
    }
  }

  /// Set various registration ids to which the message ought to be sent.
  pub fn registration_ids(mut self, ids: Vec<&'a str>) -> Message<'a> {
    self.registration_ids = Some(ids.iter().map(|s| s.to_string()).collect());
    self
  }

  /// Set this parameter to identify groups of messages that can be collapsed.
  pub fn collapse_key(mut self, collapse_key: &'a str) -> Message<'a> {
    self.collapse_key = Some(collapse_key);
    self
  }

  /// Set the priority of the message. You can set Normal or High priorities.
  /// # Examples:
  /// ```rust
  /// use gcm::{Message, Priority};
  ///
  /// let message = Message::new("<registration id>")
  ///     .priority(Priority::High);
  /// ```
  pub fn priority(mut self, priority: Priority) -> Message<'a> {
    self.priority = Some(priority);
    self
  }

  /// To set the `content-available` field on iOS
  pub fn content_available(mut self, content_available: bool) -> Message<'a> {
    self.content_available = Some(content_available);
    self
  }

  /// When set to `true`, sends the message only when the device is active.
  pub fn delay_while_idle(mut self, delay_while_idle: bool) -> Message<'a> {
    self.delay_while_idle = Some(delay_while_idle);
    self
  }

  /// How long (in seconds) to keep the message on GCM servers in case the device
  /// is offline. The maximum and default is 4 weeks.
  pub fn time_to_live(mut self, time_to_live: i32) -> Message<'a> {
    self.time_to_live = Some(time_to_live);
    self
  }

  /// Package name of the application where the registration tokens must match.
  pub fn restricted_package_name(mut self, restricted_package_name: &'a str) -> Message<'a> {
    self.restricted_package_name = Some(restricted_package_name);
    self
  }

  /// When set to `true`, allows you to test GCM without actually sending the message.
  pub fn dry_run(mut self, dry_run: bool) -> Message<'a> {
    self.dry_run = Some(dry_run);
    self
  }

  /// Use this to add custom key-value pairs to the message. This data
  /// must be handled appropriately on the client end.
  /// # Examples:
  /// ```rust
  /// use gcm::Message;
  /// use std::collections::HashMap;
  ///
  /// let mut map = HashMap::new();
  /// map.insert("message", "Howdy!");
  ///
  /// let message = Message::new("<registration id>").data(map);
  /// ```
  pub fn data(mut self, data: HashMap<&'a str, &'a str>) -> Message<'a> {
    let mut datamap: HashMap<String, String> = HashMap::new();
    for (key, val) in data.iter() {
      datamap.insert(key.to_string(), val.to_string());
    }

    self.data = Some(datamap);
    self
  }

  /// Use this to set a `Notification` for the message.
  /// # Examples:
  /// ```rust
  /// use gcm::{Message, NotificationBuilder};
  ///
  /// let notification = NotificationBuilder::new("Hey!")
  ///     .body("Do you want to catch up later?")
  ///     .finalize();
  ///
  /// let message = Message::new("<registration id>")
  ///     .notification(notification);
  /// ```
  pub fn notification(mut self, notification: Notification<'a>) -> Message<'a> {
    self.notification = Some(notification);
    self
  }

  /// Send the message using your GCM API Key.
  /// # Examples:
  /// ```no_run
  /// use gcm::Message;
  /// use std::collections::HashMap;
  ///
  /// let mut map = HashMap::new();
  /// map.insert("message", "Howdy!");
  ///
  /// let result = Message::new("<registration id>")
  ///     .data(map)
  ///     .send("<GCM API Key>");
  /// ```
  pub fn send(self, api_key: &'a str) -> Result<response::GcmResponse, response::GcmError> {
    let payload = self.to_json().to_string();
    let auth_header = "key=".to_string() + api_key;
    let res;
    let body;
    let code;

    let result = http::handle()
        .post("https://fcm.googleapis.com/fcm/send", &payload)
        .header("Authorization", &auth_header)
        .header("Content-Type", "application/json")
        .exec();

    match result {
      Ok(unwrapped) => {
        res = unwrapped;
        body = str::from_utf8(res.get_body()).unwrap();
        code = res.get_code();

        Message::parse_response(code, body)
      },
      Err(_) => {
        Message::parse_response(500, "Server Error")
      }
    }
  }

  fn parse_response(status: u32, body: &str) -> Result<response::GcmResponse, response::GcmError> {
    match status {
      200 => {
        Ok(json::decode(body).unwrap())
      },
      401 => Err(response::GcmError::Unauthorized),
      400 => Err(response::GcmError::InvalidMessage(body.to_string())),
      500...599 => Err(response::GcmError::ServerError),
      _ => Err(response::GcmError::InvalidMessage("Unknown Error".to_string())),
    }
  }
}
<commit_msg>Port from curl to hyper<commit_after>#[cfg(test)]
mod tests;
pub mod response;

pub use message::response::*;
use notification::Notification;
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::str;
use std::io::prelude::*;
use hyper::Client;
use hyper::header::{Authorization, ContentType};
use hyper::status::StatusCode;
use rustc_serialize::json::{self, Json, ToJson};

#[derive(PartialEq, Debug, Clone)]
pub enum Priority {
  Normal, High
}

/// Represents a GCM message. Construct the GCM message
/// using various utility methods and finally send it.
/// # Examples:
/// ```rust
/// use gcm::Message;
///
/// let message = Message::new("<registration id>").dry_run(true);
/// ```
pub struct Message<'a> {
  to: &'a str,
  registration_ids: Option<Vec<String>>,
  collapse_key: Option<&'a str>,
  priority: Option<Priority>,
  content_available: Option<bool>,
  delay_while_idle: Option<bool>,
  time_to_live: Option<i32>,
  restricted_package_name: Option<&'a str>,
  dry_run: Option<bool>,
  data: Option<HashMap<String, String>>,
  notification: Option<Notification<'a>>,
}

impl <'a> ToJson for Message<'a> {
  fn to_json(&self) -> Json {
    let mut root = BTreeMap::new();

    root.insert("to".to_string(), self.to.to_json());

    if self.registration_ids.is_some() {
      root.insert("registration_ids".to_string(),
          self.registration_ids.clone().unwrap().to_json());
    }

    if self.collapse_key.is_some() {
      root.insert("collapse_key".to_string(), self.collapse_key.clone().unwrap().to_json());
    }

    if self.priority.is_some() {
      root.insert("priority".to_string(), match self.priority.clone().unwrap() {
        Priority::Normal => Json::String("normal".to_string()),
        Priority::High => Json::String("high".to_string()),
      });
    }

    if self.content_available.is_some() {
      root.insert("content_available".to_string(), self.content_available.unwrap().to_json());
    }

    if self.delay_while_idle.is_some() {
      root.insert("delay_while_idle".to_string(), self.delay_while_idle.unwrap().to_json());
    }

    if self.time_to_live.is_some() {
      root.insert("time_to_live".to_string(), self.time_to_live.unwrap().to_json());
    }

    if self.restricted_package_name.is_some() {
      root.insert("restricted_package_name".to_string(), self.restricted_package_name.clone().unwrap().to_json());
    }

    if self.dry_run.is_some() {
      root.insert("dry_run".to_string(), self.dry_run.unwrap().to_json());
    }

    if self.data.is_some() {
      root.insert("data".to_string(), self.data.clone().unwrap().to_json());
    }

    if self.notification.is_some() {
      root.insert("notification".to_string(), self.notification.clone().unwrap().to_json());
    }

    Json::Object(root)
  }
}

impl <'a> Message<'a> {
  /// Get a new instance of Message. You need to supply either
  /// a registration id, or a topic (/topic/...).
  pub fn new(to: &'a str) -> Message {
    Message {
      to: to,
      registration_ids: None,
      collapse_key: None,
      priority: None,
      content_available: None,
      delay_while_idle: None,
      time_to_live: None,
      restricted_package_name: None,
      dry_run: None,
      data: None,
      notification: None,
    }
  }

  /// Set various registration ids to which the message ought to be sent.
  pub fn registration_ids(mut self, ids: Vec<&'a str>) -> Message<'a> {
    self.registration_ids = Some(ids.iter().map(|s| s.to_string()).collect());
    self
  }

  /// Set this parameter to identify groups of messages that can be collapsed.
  pub fn collapse_key(mut self, collapse_key: &'a str) -> Message<'a> {
    self.collapse_key = Some(collapse_key);
    self
  }

  /// Set the priority of the message. You can set Normal or High priorities.
  /// # Examples:
  /// ```rust
  /// use gcm::{Message, Priority};
  ///
  /// let message = Message::new("<registration id>")
  ///     .priority(Priority::High);
  /// ```
  pub fn priority(mut self, priority: Priority) -> Message<'a> {
    self.priority = Some(priority);
    self
  }

  /// To set the `content-available` field on iOS
  pub fn content_available(mut self, content_available: bool) -> Message<'a> {
    self.content_available = Some(content_available);
    self
  }

  /// When set to `true`, sends the message only when the device is active.
  pub fn delay_while_idle(mut self, delay_while_idle: bool) -> Message<'a> {
    self.delay_while_idle = Some(delay_while_idle);
    self
  }

  /// How long (in seconds) to keep the message on GCM servers in case the device
  /// is offline. The maximum and default is 4 weeks.
  pub fn time_to_live(mut self, time_to_live: i32) -> Message<'a> {
    self.time_to_live = Some(time_to_live);
    self
  }

  /// Package name of the application where the registration tokens must match.
  pub fn restricted_package_name(mut self, restricted_package_name: &'a str) -> Message<'a> {
    self.restricted_package_name = Some(restricted_package_name);
    self
  }

  /// When set to `true`, allows you to test GCM without actually sending the message.
  pub fn dry_run(mut self, dry_run: bool) -> Message<'a> {
    self.dry_run = Some(dry_run);
    self
  }

  /// Use this to add custom key-value pairs to the message. This data
  /// must be handled appropriately on the client end.
  /// # Examples:
  /// ```rust
  /// use gcm::Message;
  /// use std::collections::HashMap;
  ///
  /// let mut map = HashMap::new();
  /// map.insert("message", "Howdy!");
  ///
  /// let message = Message::new("<registration id>").data(map);
  /// ```
  pub fn data(mut self, data: HashMap<&'a str, &'a str>) -> Message<'a> {
    let mut datamap: HashMap<String, String> = HashMap::new();
    for (key, val) in data.iter() {
      datamap.insert(key.to_string(), val.to_string());
    }

    self.data = Some(datamap);
    self
  }

  /// Use this to set a `Notification` for the message.
  /// # Examples:
  /// ```rust
  /// use gcm::{Message, NotificationBuilder};
  ///
  /// let notification = NotificationBuilder::new("Hey!")
  ///     .body("Do you want to catch up later?")
  ///     .finalize();
  ///
  /// let message = Message::new("<registration id>")
  ///     .notification(notification);
  /// ```
  pub fn notification(mut self, notification: Notification<'a>) -> Message<'a> {
    self.notification = Some(notification);
    self
  }

  /// Send the message using your GCM API Key.
  /// # Examples:
  /// ```no_run
  /// use gcm::Message;
  /// use std::collections::HashMap;
  ///
  /// let mut map = HashMap::new();
  /// map.insert("message", "Howdy!");
  ///
  /// let result = Message::new("<registration id>")
  ///     .data(map)
  ///     .send("<GCM API Key>");
  /// ```
  pub fn send(self, api_key: &'a str) -> Result<response::GcmResponse, response::GcmError> {
    let payload = self.to_json().to_string();
    let auth_header = "key=".to_string() + api_key;
    let client = Client::new();

    let response = client
        .post("https://fcm.googleapis.com/fcm/send")
        .body(&payload)
        .header(Authorization(auth_header))
        .header(ContentType::json())
        .send();

    match response {
      Ok(mut response) => {
        let mut body = String::new();
        response.read_to_string(&mut body).unwrap();

        Message::parse_response(response.status, &body)
      },
      Err(_) => {
        Message::parse_response(StatusCode::InternalServerError, "Server Error")
      }
    }
  }

  fn parse_response(status: StatusCode, body: &str) -> Result<response::GcmResponse, response::GcmError> {
    use hyper::status::StatusCode::*;
    use hyper::status::StatusClass::ServerError;
    match status {
      Ok => Result::Ok(json::decode(body).unwrap()),
      Unauthorized => Err(response::GcmError::Unauthorized),
      BadRequest => Err(response::GcmError::InvalidMessage(body.to_string())),
      _ => match status.class() {
        ServerError => Err(response::GcmError::ServerError),
        _ => Err(response::GcmError::InvalidMessage("Unknown Error".to_string())),
      }
    }
  }
}
<|endoftext|>


<commit_before>use std::fmt::{ Display, Formatter };
use std::fmt::Error as FormatError;
use std::num::ParseFloatError;
use read_token::ParseStringError;
use std::rc::Rc;

use {
    Type,
    DebugId
};

/// Errors reporting expected values.
#[derive(Debug, PartialEq)]
pub enum ParseError {
    /// Not supported.
    NotSupported,
    /// Whitespace is required.
    ExpectedWhitespace(DebugId),
    /// Something is required.
    ExpectedSomething(DebugId),
    /// Expected number.
    ExpectedNumber(DebugId),
    /// Error when parsing float.
    ParseFloatError(ParseFloatError, DebugId),
    /// Expected text.
    ExpectedText(DebugId),
    /// Empty text not allowed.
    EmptyTextNotAllowed(DebugId),
    /// Invalid string format.
    ParseStringError(ParseStringError, DebugId),
    /// Expected token.
    ExpectedToken(Rc<String>, DebugId),
    /// An invalid rule.
    InvalidRule(&'static str, DebugId),
    /// Expected nodes with other names.
    ExpectedNode(Vec<String>),
    /// Expected another propert type.
    ExpectedPropertyType(Type),
    /// Reaching end of node, but expected more properties.
    ExpectedMoreProperties(Vec<String>),
}

impl Display for ParseError {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), FormatError> {
        match self {
            &ParseError::NotSupported =>
                try!(fmt.write_str("This feature is not supported")),
            &ParseError::ExpectedWhitespace(debug_id) =>
                try!(write!(fmt, "#{}, Expected whitespace",
                    debug_id)),
            &ParseError::ExpectedSomething(debug_id) =>
                try!(write!(fmt, "#{}, Expected something",
                    debug_id)),
            &ParseError::ExpectedNumber(debug_id) =>
                try!(write!(fmt, "#{}, Expected number", debug_id)),
            &ParseError::ParseFloatError(ref err, debug_id) =>
                try!(write!(fmt, "#{}, Invalid number format: {}",
                    debug_id, err)),
            &ParseError::ExpectedToken(ref token, debug_id) =>
                try!(write!(fmt, "#{}, Expected: `{}`", debug_id, token)),
            &ParseError::ExpectedText(debug_id) =>
                try!(write!(fmt, "#{}, Expected text", debug_id)),
            &ParseError::EmptyTextNotAllowed(debug_id) =>
                try!(write!(fmt, "#{}, Empty text not allowed", debug_id)),
            &ParseError::ParseStringError(err, debug_id) =>
                try!(write!(fmt, "#{}, Invalid string format: {}",
                    debug_id, err)),
            &ParseError::ExpectedNode(ref nodes) => {
                try!(fmt.write_str("Expected nodes: "));
                let mut tail = false;
                for node in nodes {
                    if tail {
                        try!(fmt.write_str(", "));
                    } else {
                        tail = true;
                    }
                    try!(fmt.write_str(&node));
                }
            }
            &ParseError::ExpectedPropertyType(ref ty) =>
                try!(write!(fmt, "Expected property type: {}", ty)),
            &ParseError::ExpectedMoreProperties(ref props) => {
                try!(fmt.write_str("Expected more properties: "));
                let mut tail = false;
                for prop in props {
                    if tail {
                        try!(fmt.write_str(", "));
                    } else {
                        tail = true;
                    }
                    try!(fmt.write_str(prop));
                }
            }
            &ParseError::InvalidRule(msg, debug_id) =>
                try!(write!(fmt, "#{}, Invalid rule: {}", debug_id, msg)),
        }
        Ok(())
    }
}
<commit_msg>Deleted meta reader errors on `ParseError`<commit_after>use std::fmt::{ Display, Formatter };
use std::fmt::Error as FormatError;
use std::num::ParseFloatError;
use read_token::ParseStringError;
use std::rc::Rc;

use DebugId;

/// Errors reporting expected values.
#[derive(Debug, PartialEq)]
pub enum ParseError {
    /// Not supported.
    NotSupported,
    /// Whitespace is required.
    ExpectedWhitespace(DebugId),
    /// Something is required.
    ExpectedSomething(DebugId),
    /// Expected number.
    ExpectedNumber(DebugId),
    /// Error when parsing float.
    ParseFloatError(ParseFloatError, DebugId),
    /// Expected text.
    ExpectedText(DebugId),
    /// Empty text not allowed.
    EmptyTextNotAllowed(DebugId),
    /// Invalid string format.
    ParseStringError(ParseStringError, DebugId),
    /// Expected token.
    ExpectedToken(Rc<String>, DebugId),
    /// An invalid rule.
    InvalidRule(&'static str, DebugId),
}

impl Display for ParseError {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), FormatError> {
        match self {
            &ParseError::NotSupported =>
                try!(fmt.write_str("This feature is not supported")),
            &ParseError::ExpectedWhitespace(debug_id) =>
                try!(write!(fmt, "#{}, Expected whitespace",
                    debug_id)),
            &ParseError::ExpectedSomething(debug_id) =>
                try!(write!(fmt, "#{}, Expected something",
                    debug_id)),
            &ParseError::ExpectedNumber(debug_id) =>
                try!(write!(fmt, "#{}, Expected number", debug_id)),
            &ParseError::ParseFloatError(ref err, debug_id) =>
                try!(write!(fmt, "#{}, Invalid number format: {}",
                    debug_id, err)),
            &ParseError::ExpectedToken(ref token, debug_id) =>
                try!(write!(fmt, "#{}, Expected: `{}`", debug_id, token)),
            &ParseError::ExpectedText(debug_id) =>
                try!(write!(fmt, "#{}, Expected text", debug_id)),
            &ParseError::EmptyTextNotAllowed(debug_id) =>
                try!(write!(fmt, "#{}, Empty text not allowed", debug_id)),
            &ParseError::ParseStringError(err, debug_id) =>
                try!(write!(fmt, "#{}, Invalid string format: {}",
                    debug_id, err)),
            &ParseError::InvalidRule(msg, debug_id) =>
                try!(write!(fmt, "#{}, Invalid rule: {}", debug_id, msg)),
        }
        Ok(())
    }
}
<|endoftext|>


<commit_before>// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Convert number to enum.
//!
//! This crate provides a derive macro to generate a function for converting a
//! primitive integer into the corresponding variant of an enum.
//!
//! The generated function is named `n` and has the following signature:
//!
//! ```rust
//! # const IGNORE: &str = stringify! {
//! impl YourEnum {
//!     pub fn n(value: Repr) -> Option<Self>;
//! }
//! # };
//! ```
//!
//! where `Repr` is an integer type of the right size as described in more
//! detail below.
//!
//! # Example
//!
//! ```rust
//! use enumn::N;
//!
//! #[derive(PartialEq, Debug, N)]
//! enum Status {
//!     LegendaryTriumph,
//!     QualifiedSuccess,
//!     FortuitousRevival,
//!     IndeterminateStalemate,
//!     RecoverableSetback,
//!     DireMisadventure,
//!     AbjectFailure,
//! }
//!
//! fn main() {
//!     let s = Status::n(1);
//!     assert_eq!(s, Some(Status::QualifiedSuccess));
//!
//!     let s = Status::n(9);
//!     assert_eq!(s, None);
//! }
//! ```
//!
//! # Signature
//!
//! The generated signature depends on whether the enum has a `#[repr(..)]`
//! attribute. If a `repr` is specified, the input to `n` will be required to be
//! of that type.
//!
//! ```rust
//! use enumn::N;
//!
//! #[derive(N)]
//! #[repr(u8)]
//! enum E {
//!     /* ... */
//!     # IGNORE
//! }
//!
//! // expands to:
//! impl E {
//!     pub fn n(value: u8) -> Option<Self> {
//!         /* ... */
//!         # unimplemented!()
//!     }
//! }
//! ```
//!
//! On the other hand if no `repr` is specified then we get a signature that is
//! generic over a variety of possible types.
//!
//! ```rust
//! # enum E {}
//! #
//! impl E {
//!     pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> {
//!         /* ... */
//!         # unimplemented!()
//!     }
//! }
//! ```
//!
//! # Discriminants
//!
//! The conversion respects explictly specified enum discriminants. Consider
//! this enum:
//!
//! ```rust
//! use enumn::N;
//!
//! #[derive(N)]
//! enum Letter {
//!     A = 65,
//!     B = 66,
//! }
//! ```
//!
//! Here `Letter::n(65)` would return `Some(Letter::A)`.

#![recursion_limit = "128"]

extern crate proc_macro;

#[cfg(test)]
mod tests;

use proc_macro::TokenStream;
use quote::quote;
use syn::parse::Error;
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Meta, NestedMeta};

fn testable_derive(input: DeriveInput) -> proc_macro2::TokenStream {
    let variants = match input.data {
        Data::Enum(data) => data.variants,
        Data::Struct(_) | Data::Union(_) => panic!("input must be an enum"),
    };

    for variant in &variants {
        match variant.fields {
            Fields::Unit => {}
            Fields::Named(_) | Fields::Unnamed(_) => {
                let span = variant.ident.span();
                let err = Error::new(span, "enumn: variant with data is not supported");
                return err.to_compile_error();
            }
        }
    }

    // Parse repr attribute like #[repr(u16)].
    let mut repr = None;
    for attr in input.attrs {
        if let Ok(Meta::List(list)) = attr.parse_meta() {
            if list.ident == "repr" {
                if let Some(NestedMeta::Meta(Meta::Word(word))) = list.nested.into_iter().next() {
                    match word.to_string().as_str() {
                        "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32"
                        | "i64" | "i128" | "isize" => {
                            repr = Some(word);
                        }
                        _ => {}
                    }
                }
            }
        }
    }

    let signature;
    let value;
    match &repr {
        Some(repr) => {
            signature = quote! {
                fn n(value: #repr)
            };
            value = quote!(value);
        }
        None => {
            repr = Some(parse_quote!(i64));
            signature = quote! {
                fn n<REPR: Into<i64>>(value: REPR)
            };
            value = quote! {
                <REPR as Into<i64>>::into(value)
            };
        }
    }

    let ident = input.ident;
    let declare_discriminants = variants.iter().map(|variant| {
        let variant = &variant.ident;
        quote! {
            const #variant: #repr = #ident::#variant as #repr;
        }
    });
    let match_discriminants = variants.iter().map(|variant| {
        let variant = &variant.ident;
        quote! {
            discriminant::#variant => Some(#ident::#variant),
        }
    });

    quote! {
        #[allow(non_upper_case_globals)]
        impl #ident {
            pub #signature -> Option<Self> {
                struct discriminant;
                impl discriminant {
                    #(#declare_discriminants)*
                }
                match #value {
                    #(#match_discriminants)*
                    _ => None,
                }
            }
        }
    }
}

#[proc_macro_derive(N)]
pub fn derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let expanded = testable_derive(input);
    TokenStream::from(expanded)
}
<commit_msg>enumn: fix duplicate fn in doc tests<commit_after>// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Convert number to enum.
//!
//! This crate provides a derive macro to generate a function for converting a
//! primitive integer into the corresponding variant of an enum.
//!
//! The generated function is named `n` and has the following signature:
//!
//! ```rust
//! # const IGNORE: &str = stringify! {
//! impl YourEnum {
//!     pub fn n(value: Repr) -> Option<Self>;
//! }
//! # };
//! ```
//!
//! where `Repr` is an integer type of the right size as described in more
//! detail below.
//!
//! # Example
//!
//! ```rust
//! use enumn::N;
//!
//! #[derive(PartialEq, Debug, N)]
//! enum Status {
//!     LegendaryTriumph,
//!     QualifiedSuccess,
//!     FortuitousRevival,
//!     IndeterminateStalemate,
//!     RecoverableSetback,
//!     DireMisadventure,
//!     AbjectFailure,
//! }
//!
//! fn main() {
//!     let s = Status::n(1);
//!     assert_eq!(s, Some(Status::QualifiedSuccess));
//!
//!     let s = Status::n(9);
//!     assert_eq!(s, None);
//! }
//! ```
//!
//! # Signature
//!
//! The generated signature depends on whether the enum has a `#[repr(..)]`
//! attribute. If a `repr` is specified, the input to `n` will be required to be
//! of that type.
//!
//! ```ignore
//! use enumn::N;
//!
//! #[derive(N)]
//! #[repr(u8)]
//! enum E {
//!     /* ... */
//!     # IGNORE
//! }
//!
//! // expands to:
//! impl E {
//!     pub fn n(value: u8) -> Option<Self> {
//!         /* ... */
//!         # unimplemented!()
//!     }
//! }
//! ```
//!
//! On the other hand if no `repr` is specified then we get a signature that is
//! generic over a variety of possible types.
//!
//! ```ignore
//! # enum E {}
//! #
//! impl E {
//!     pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> {
//!         /* ... */
//!         # unimplemented!()
//!     }
//! }
//! ```
//!
//! # Discriminants
//!
//! The conversion respects explictly specified enum discriminants. Consider
//! this enum:
//!
//! ```rust
//! use enumn::N;
//!
//! #[derive(N)]
//! enum Letter {
//!     A = 65,
//!     B = 66,
//! }
//! ```
//!
//! Here `Letter::n(65)` would return `Some(Letter::A)`.

#![recursion_limit = "128"]

extern crate proc_macro;

#[cfg(test)]
mod tests;

use proc_macro::TokenStream;
use quote::quote;
use syn::parse::Error;
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Meta, NestedMeta};

fn testable_derive(input: DeriveInput) -> proc_macro2::TokenStream {
    let variants = match input.data {
        Data::Enum(data) => data.variants,
        Data::Struct(_) | Data::Union(_) => panic!("input must be an enum"),
    };

    for variant in &variants {
        match variant.fields {
            Fields::Unit => {}
            Fields::Named(_) | Fields::Unnamed(_) => {
                let span = variant.ident.span();
                let err = Error::new(span, "enumn: variant with data is not supported");
                return err.to_compile_error();
            }
        }
    }

    // Parse repr attribute like #[repr(u16)].
    let mut repr = None;
    for attr in input.attrs {
        if let Ok(Meta::List(list)) = attr.parse_meta() {
            if list.ident == "repr" {
                if let Some(NestedMeta::Meta(Meta::Word(word))) = list.nested.into_iter().next() {
                    match word.to_string().as_str() {
                        "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32"
                        | "i64" | "i128" | "isize" => {
                            repr = Some(word);
                        }
                        _ => {}
                    }
                }
            }
        }
    }

    let signature;
    let value;
    match &repr {
        Some(repr) => {
            signature = quote! {
                fn n(value: #repr)
            };
            value = quote!(value);
        }
        None => {
            repr = Some(parse_quote!(i64));
            signature = quote! {
                fn n<REPR: Into<i64>>(value: REPR)
            };
            value = quote! {
                <REPR as Into<i64>>::into(value)
            };
        }
    }

    let ident = input.ident;
    let declare_discriminants = variants.iter().map(|variant| {
        let variant = &variant.ident;
        quote! {
            const #variant: #repr = #ident::#variant as #repr;
        }
    });
    let match_discriminants = variants.iter().map(|variant| {
        let variant = &variant.ident;
        quote! {
            discriminant::#variant => Some(#ident::#variant),
        }
    });

    quote! {
        #[allow(non_upper_case_globals)]
        impl #ident {
            pub #signature -> Option<Self> {
                struct discriminant;
                impl discriminant {
                    #(#declare_discriminants)*
                }
                match #value {
                    #(#match_discriminants)*
                    _ => None,
                }
            }
        }
    }
}

#[proc_macro_derive(N)]
pub fn derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let expanded = testable_derive(input);
    TokenStream::from(expanded)
}
<|endoftext|>


<commit_before>#[doc = "Generate markdown from a document tree"];

import std::io;
import std::io::writer_util;

export mk_pass;

fn mk_pass(
    writer: fn~() -> io::writer
) -> pass {
    ret fn~(
        _srv: astsrv::srv,
        doc: doc::cratedoc
    ) -> doc::cratedoc {
        write_markdown(doc, writer());
        doc
    };
}

type ctxt = {
    w: io::writer
};

fn write_markdown(
    doc: doc::cratedoc,
    writer: io::writer
) {
    let ctxt = {
        w: writer
    };

    write_crate(ctxt, doc);
}

enum hlvl {
    h1 = 1,
    h2 = 2,
    h3 = 3
}

fn write_header(ctxt: ctxt, lvl: hlvl, title: str) {
    let hashes = str::from_chars(vec::init_elt(lvl as uint, '#'));
    ctxt.w.write_line(#fmt("%s %s", hashes, title));
    ctxt.w.write_line("");
}

fn write_crate(
    ctxt: ctxt,
    doc: doc::cratedoc
) {
    write_header(ctxt, h1, #fmt("Crate %s", doc.topmod.name));
    write_top_module(ctxt, doc.topmod);
}

fn write_top_module(
    ctxt: ctxt,
    moddoc: doc::moddoc
) {
    write_mod_contents(ctxt, moddoc);
}

fn write_mod(
    ctxt: ctxt,
    moddoc: doc::moddoc
) {
    let fullpath = str::connect(moddoc.path + [moddoc.name], "::");
    write_header(ctxt, h2, #fmt("Module `%s`", fullpath));
    write_mod_contents(ctxt, moddoc);
}

#[test]
fn should_write_full_path_to_mod() {
    let markdown = test::render("mod a { mod b { mod c { } } }");
    assert str::contains(markdown, "## Module `a::b::c`");
}

fn write_mod_contents(
    ctxt: ctxt,
    doc: doc::moddoc
) {
    write_brief(ctxt, doc.brief);
    write_desc(ctxt, doc.desc);

    for fndoc in *doc.fns {
        write_fn(ctxt, fndoc);
    }

    for moddoc in *doc.mods {
        write_mod(ctxt, moddoc);
    }
}

#[test]
fn should_write_crate_brief_description() {
    let markdown = test::render("#[doc(brief = \"this is the crate\")];");
    assert str::contains(markdown, "this is the crate");
}

#[test]
fn should_write_crate_description() {
    let markdown = test::render("#[doc = \"this is the crate\"];");
    assert str::contains(markdown, "this is the crate");
}

fn write_fn(
    ctxt: ctxt,
    doc: doc::fndoc
) {
    write_header(ctxt, h3, #fmt("Function `%s`", doc.name));
    write_sig(ctxt, doc.sig);
    write_brief(ctxt, doc.brief);
    write_desc(ctxt, doc.desc);
    write_args(ctxt, doc.args);
    write_return(ctxt, doc.return);
}

fn write_sig(ctxt: ctxt, sig: option<str>) {
    alt sig {
      some(sig) {
        ctxt.w.write_line(code_block_indent(sig));
        ctxt.w.write_line("");
      }
      none { fail "unimplemented" }
    }
}

fn code_block_indent(s: str) -> str {
    let lines = str::lines_any(s);
    let indented = vec::map(lines, { |line| #fmt("    %s", line) });
    str::connect(indented, "\n")
}

#[test]
fn should_write_the_function_signature() {
    let markdown = test::render("#[doc = \"f\"] fn a() { }");
    assert str::contains(markdown, "\n    fn a()\n");
}

#[test]
fn should_insert_blank_line_after_fn_signature() {
    let markdown = test::render("#[doc = \"f\"] fn a() { }");
    assert str::contains(markdown, "fn a()\n\n");
}

#[test]
fn should_correctly_indent_fn_signature() {
    let doc = test::create_doc("fn a() { }");
    let doc = ~{
        topmod: ~{
            fns: doc::fnlist([~{
                sig: some("line 1\nline 2")
                with *doc.topmod.fns[0]
            }])
            with *doc.topmod
        }
        with *doc
    };
    let markdown = test::write_markdown_str(doc);
    assert str::contains(markdown, "    line 1\n    line 2");
}

fn write_brief(
    ctxt: ctxt,
    brief: option<str>
) {
    alt brief {
      some(brief) {
        ctxt.w.write_line(brief);
        ctxt.w.write_line("");
      }
      none { }
    }
}

fn write_desc(
    ctxt: ctxt,
    desc: option<str>
) {
    alt desc {
        some(desc) {
            ctxt.w.write_line(desc);
            ctxt.w.write_line("");
        }
        none { }
    }
}

fn write_args(
    ctxt: ctxt,
    args: [doc::argdoc]
) {
    if vec::is_not_empty(args) {
        ctxt.w.write_line("Arguments:");
        ctxt.w.write_line("");
        vec::iter(args) {|arg| write_arg(ctxt, arg) };
        ctxt.w.write_line("");
    }
}

fn write_arg(ctxt: ctxt, arg: doc::argdoc) {
    assert option::is_some(arg.ty);
    ctxt.w.write_str(#fmt(
        "* `%s`: `%s`",
        arg.name,
        option::get(arg.ty)
    ));
    alt arg.desc {
      some(desc) {
        ctxt.w.write_str(#fmt(" - %s", desc));
      }
      none { }
    }
    ctxt.w.write_line("");
}

#[test]
fn should_write_argument_list() {
    let source = "fn a(b: int, c: int) { }";
    let markdown = test::render(source);
    assert str::contains(
        markdown,
        "Arguments:\n\
         \n\
         * `b`: `int`\n\
         * `c`: `int`\n\
         \n"
    );
}

#[test]
fn should_not_write_arguments_if_none() {
    let source = "fn a() { } fn b() { }";
    let markdown = test::render(source);
    assert !str::contains(markdown, "Arguments");
}

#[test]
fn should_write_argument_description() {
    let source = "#[doc(args(a = \"milk\"))] fn f(a: bool) { }";
    let markdown = test::render(source);
    assert str::contains(markdown, "`a`: `bool` - milk");
}

fn write_return(
    ctxt: ctxt,
    doc: doc::retdoc
) {
    alt doc.ty {
      some(ty) {
        ctxt.w.write_str(#fmt("Returns `%s`", ty));
        alt doc.desc {
          some(d) {
            ctxt.w.write_line(#fmt(" - %s", d));
            ctxt.w.write_line("");
          }
          none {
            ctxt.w.write_line("");
            ctxt.w.write_line("");
          }
        }
      }
      none { }
    }
}

#[test]
fn should_write_return_type_on_new_line() {
    let markdown = test::render("fn a() -> int { }");
    assert str::contains(markdown, "\nReturns `int`");
}

#[test]
fn should_write_blank_line_between_return_type_and_next_header() {
    let markdown = test::render(
        "fn a() -> int { } \
         fn b() -> int { }"
    );
    assert str::contains(markdown, "Returns `int`\n\n##");
}

#[test]
fn should_not_write_return_type_when_there_is_none() {
    let markdown = test::render("fn a() { }");
    assert !str::contains(markdown, "Returns");
}

#[test]
fn should_write_blank_line_after_return_description() {
    let markdown = test::render(
        "#[doc(return = \"blorp\")] fn a() -> int { }"
    );
    assert str::contains(markdown, "blorp\n\n");
}

#[test]
fn should_write_return_description_on_same_line_as_type() {
    let markdown = test::render(
        "#[doc(return = \"blorp\")] fn a() -> int { }"
    );
    assert str::contains(markdown, "Returns `int` - blorp");
}

#[cfg(test)]
mod test {
    fn render(source: str) -> str {
        let doc = create_doc(source);
        let markdown = write_markdown_str(doc);
        #debug("markdown: %s", markdown);
        markdown
    }

    fn create_doc(source: str) -> doc::cratedoc {
        let srv = astsrv::mk_srv_from_str(source);
        let doc = extract::from_srv(srv, "");
        #debug("doc (extract): %?", doc);
        let doc = tystr_pass::mk_pass()(srv, doc);
        #debug("doc (tystr): %?", doc);
        let doc = path_pass::mk_pass()(srv, doc);
        #debug("doc (path): %?", doc);
        let doc = attr_pass::mk_pass()(srv, doc);
        #debug("doc (attr): %?", doc);
        doc
    }

    fn write_markdown_str(
        doc: doc::cratedoc
    ) -> str {
        let buffer = io::mk_mem_buffer();
        let writer = io::mem_buffer_writer(buffer);
        write_markdown(doc, writer);
        ret io::mem_buffer_str(buffer);
    }

    #[test]
    fn write_markdown_should_write_crate_header() {
        let srv = astsrv::mk_srv_from_str("");
        let doc = extract::from_srv(srv, "belch");
        let doc = attr_pass::mk_pass()(srv, doc);
        let markdown = write_markdown_str(doc);
        assert str::contains(markdown, "# Crate belch");
    }

    #[test]
    fn write_markdown_should_write_function_header() {
        let markdown = render("fn func() { }");
        assert str::contains(markdown, "## Function `func`");
    }

    #[test]
    fn write_markdown_should_write_mod_headers() {
        let markdown = render("mod moo { }");
        assert str::contains(markdown, "## Module `moo`");
    }

    #[test]
    fn should_leave_blank_line_after_header() {
        let markdown = render("mod morp { }");
        assert str::contains(markdown, "Module `morp`\n\n");
    }

    #[test]
    fn should_leave_blank_line_between_fn_header_and_sig() {
        let markdown = render("#[doc(brief = \"brief\")] fn a() { }");
        assert str::contains(markdown, "Function `a`\n\n    fn a()");
    }

    #[test]
    fn should_leave_blank_line_after_brief() {
        let markdown = render("#[doc(brief = \"brief\")] fn a() { }");
        assert str::contains(markdown, "brief\n\n");
    }

    #[test]
    fn should_leave_blank_line_between_brief_and_desc() {
        let markdown = render(
            "#[doc(brief = \"brief\", desc = \"desc\")] fn a() { }"
        );
        assert str::contains(markdown, "brief\n\ndesc");
    }

}<commit_msg>rustdoc: Write markdown for consts<commit_after>#[doc = "Generate markdown from a document tree"];

import std::io;
import std::io::writer_util;

export mk_pass;

fn mk_pass(
    writer: fn~() -> io::writer
) -> pass {
    ret fn~(
        _srv: astsrv::srv,
        doc: doc::cratedoc
    ) -> doc::cratedoc {
        write_markdown(doc, writer());
        doc
    };
}

type ctxt = {
    w: io::writer
};

fn write_markdown(
    doc: doc::cratedoc,
    writer: io::writer
) {
    let ctxt = {
        w: writer
    };

    write_crate(ctxt, doc);
}

enum hlvl {
    h1 = 1,
    h2 = 2,
    h3 = 3
}

fn write_header(ctxt: ctxt, lvl: hlvl, title: str) {
    let hashes = str::from_chars(vec::init_elt(lvl as uint, '#'));
    ctxt.w.write_line(#fmt("%s %s", hashes, title));
    ctxt.w.write_line("");
}

fn write_crate(
    ctxt: ctxt,
    doc: doc::cratedoc
) {
    write_header(ctxt, h1, #fmt("Crate %s", doc.topmod.name));
    write_top_module(ctxt, doc.topmod);
}

fn write_top_module(
    ctxt: ctxt,
    moddoc: doc::moddoc
) {
    write_mod_contents(ctxt, moddoc);
}

fn write_mod(
    ctxt: ctxt,
    moddoc: doc::moddoc
) {
    let fullpath = str::connect(moddoc.path + [moddoc.name], "::");
    write_header(ctxt, h2, #fmt("Module `%s`", fullpath));
    write_mod_contents(ctxt, moddoc);
}

#[test]
fn should_write_full_path_to_mod() {
    let markdown = test::render("mod a { mod b { mod c { } } }");
    assert str::contains(markdown, "## Module `a::b::c`");
}

fn write_mod_contents(
    ctxt: ctxt,
    doc: doc::moddoc
) {
    write_brief(ctxt, doc.brief);
    write_desc(ctxt, doc.desc);

    for constdoc in *doc.consts {
        write_const(ctxt, constdoc);
    }

    for fndoc in *doc.fns {
        write_fn(ctxt, fndoc);
    }

    for moddoc in *doc.mods {
        write_mod(ctxt, moddoc);
    }
}

#[test]
fn should_write_crate_brief_description() {
    let markdown = test::render("#[doc(brief = \"this is the crate\")];");
    assert str::contains(markdown, "this is the crate");
}

#[test]
fn should_write_crate_description() {
    let markdown = test::render("#[doc = \"this is the crate\"];");
    assert str::contains(markdown, "this is the crate");
}

fn write_fn(
    ctxt: ctxt,
    doc: doc::fndoc
) {
    write_header(ctxt, h3, #fmt("Function `%s`", doc.name));
    write_sig(ctxt, doc.sig);
    write_brief(ctxt, doc.brief);
    write_desc(ctxt, doc.desc);
    write_args(ctxt, doc.args);
    write_return(ctxt, doc.return);
}

fn write_sig(ctxt: ctxt, sig: option<str>) {
    alt sig {
      some(sig) {
        ctxt.w.write_line(code_block_indent(sig));
        ctxt.w.write_line("");
      }
      none { fail "unimplemented" }
    }
}

fn code_block_indent(s: str) -> str {
    let lines = str::lines_any(s);
    let indented = vec::map(lines, { |line| #fmt("    %s", line) });
    str::connect(indented, "\n")
}

#[test]
fn should_write_the_function_signature() {
    let markdown = test::render("#[doc = \"f\"] fn a() { }");
    assert str::contains(markdown, "\n    fn a()\n");
}

#[test]
fn should_insert_blank_line_after_fn_signature() {
    let markdown = test::render("#[doc = \"f\"] fn a() { }");
    assert str::contains(markdown, "fn a()\n\n");
}

#[test]
fn should_correctly_indent_fn_signature() {
    let doc = test::create_doc("fn a() { }");
    let doc = ~{
        topmod: ~{
            fns: doc::fnlist([~{
                sig: some("line 1\nline 2")
                with *doc.topmod.fns[0]
            }])
            with *doc.topmod
        }
        with *doc
    };
    let markdown = test::write_markdown_str(doc);
    assert str::contains(markdown, "    line 1\n    line 2");
}

fn write_brief(
    ctxt: ctxt,
    brief: option<str>
) {
    alt brief {
      some(brief) {
        ctxt.w.write_line(brief);
        ctxt.w.write_line("");
      }
      none { }
    }
}

fn write_desc(
    ctxt: ctxt,
    desc: option<str>
) {
    alt desc {
        some(desc) {
            ctxt.w.write_line(desc);
            ctxt.w.write_line("");
        }
        none { }
    }
}

fn write_args(
    ctxt: ctxt,
    args: [doc::argdoc]
) {
    if vec::is_not_empty(args) {
        ctxt.w.write_line("Arguments:");
        ctxt.w.write_line("");
        vec::iter(args) {|arg| write_arg(ctxt, arg) };
        ctxt.w.write_line("");
    }
}

fn write_arg(ctxt: ctxt, arg: doc::argdoc) {
    assert option::is_some(arg.ty);
    ctxt.w.write_str(#fmt(
        "* `%s`: `%s`",
        arg.name,
        option::get(arg.ty)
    ));
    alt arg.desc {
      some(desc) {
        ctxt.w.write_str(#fmt(" - %s", desc));
      }
      none { }
    }
    ctxt.w.write_line("");
}

#[test]
fn should_write_argument_list() {
    let source = "fn a(b: int, c: int) { }";
    let markdown = test::render(source);
    assert str::contains(
        markdown,
        "Arguments:\n\
         \n\
         * `b`: `int`\n\
         * `c`: `int`\n\
         \n"
    );
}

#[test]
fn should_not_write_arguments_if_none() {
    let source = "fn a() { } fn b() { }";
    let markdown = test::render(source);
    assert !str::contains(markdown, "Arguments");
}

#[test]
fn should_write_argument_description() {
    let source = "#[doc(args(a = \"milk\"))] fn f(a: bool) { }";
    let markdown = test::render(source);
    assert str::contains(markdown, "`a`: `bool` - milk");
}

fn write_return(
    ctxt: ctxt,
    doc: doc::retdoc
) {
    alt doc.ty {
      some(ty) {
        ctxt.w.write_str(#fmt("Returns `%s`", ty));
        alt doc.desc {
          some(d) {
            ctxt.w.write_line(#fmt(" - %s", d));
            ctxt.w.write_line("");
          }
          none {
            ctxt.w.write_line("");
            ctxt.w.write_line("");
          }
        }
      }
      none { }
    }
}

#[test]
fn should_write_return_type_on_new_line() {
    let markdown = test::render("fn a() -> int { }");
    assert str::contains(markdown, "\nReturns `int`");
}

#[test]
fn should_write_blank_line_between_return_type_and_next_header() {
    let markdown = test::render(
        "fn a() -> int { } \
         fn b() -> int { }"
    );
    assert str::contains(markdown, "Returns `int`\n\n##");
}

#[test]
fn should_not_write_return_type_when_there_is_none() {
    let markdown = test::render("fn a() { }");
    assert !str::contains(markdown, "Returns");
}

#[test]
fn should_write_blank_line_after_return_description() {
    let markdown = test::render(
        "#[doc(return = \"blorp\")] fn a() -> int { }"
    );
    assert str::contains(markdown, "blorp\n\n");
}

#[test]
fn should_write_return_description_on_same_line_as_type() {
    let markdown = test::render(
        "#[doc(return = \"blorp\")] fn a() -> int { }"
    );
    assert str::contains(markdown, "Returns `int` - blorp");
}

fn write_const(
    ctxt: ctxt,
    doc: doc::constdoc
) {
    write_header(ctxt, h3, #fmt("Const `%s`", doc.name));
    write_sig(ctxt, doc.ty);
    write_brief(ctxt, doc.brief);
    write_desc(ctxt, doc.desc);
}

#[test]
fn should_write_const_header() {
    let markdown = test::render("const a: bool = true;");
    assert str::contains(markdown, "### Const `a`\n\n");
}

#[test]
fn should_write_const_description() {
    let markdown = test::render(
        "#[doc(brief = \"a\", desc = \"b\")]\
         const a: bool = true;");
    assert str::contains(markdown, "\n\na\n\nb\n\n");
}

#[cfg(test)]
mod test {
    fn render(source: str) -> str {
        let doc = create_doc(source);
        let markdown = write_markdown_str(doc);
        #debug("markdown: %s", markdown);
        markdown
    }

    fn create_doc(source: str) -> doc::cratedoc {
        let srv = astsrv::mk_srv_from_str(source);
        let doc = extract::from_srv(srv, "");
        #debug("doc (extract): %?", doc);
        let doc = tystr_pass::mk_pass()(srv, doc);
        #debug("doc (tystr): %?", doc);
        let doc = path_pass::mk_pass()(srv, doc);
        #debug("doc (path): %?", doc);
        let doc = attr_pass::mk_pass()(srv, doc);
        #debug("doc (attr): %?", doc);
        doc
    }

    fn write_markdown_str(
        doc: doc::cratedoc
    ) -> str {
        let buffer = io::mk_mem_buffer();
        let writer = io::mem_buffer_writer(buffer);
        write_markdown(doc, writer);
        ret io::mem_buffer_str(buffer);
    }

    #[test]
    fn write_markdown_should_write_crate_header() {
        let srv = astsrv::mk_srv_from_str("");
        let doc = extract::from_srv(srv, "belch");
        let doc = attr_pass::mk_pass()(srv, doc);
        let markdown = write_markdown_str(doc);
        assert str::contains(markdown, "# Crate belch");
    }

    #[test]
    fn write_markdown_should_write_function_header() {
        let markdown = render("fn func() { }");
        assert str::contains(markdown, "## Function `func`");
    }

    #[test]
    fn write_markdown_should_write_mod_headers() {
        let markdown = render("mod moo { }");
        assert str::contains(markdown, "## Module `moo`");
    }

    #[test]
    fn should_leave_blank_line_after_header() {
        let markdown = render("mod morp { }");
        assert str::contains(markdown, "Module `morp`\n\n");
    }

    #[test]
    fn should_leave_blank_line_between_fn_header_and_sig() {
        let markdown = render("#[doc(brief = \"brief\")] fn a() { }");
        assert str::contains(markdown, "Function `a`\n\n    fn a()");
    }

    #[test]
    fn should_leave_blank_line_after_brief() {
        let markdown = render("#[doc(brief = \"brief\")] fn a() { }");
        assert str::contains(markdown, "brief\n\n");
    }

    #[test]
    fn should_leave_blank_line_between_brief_and_desc() {
        let markdown = render(
            "#[doc(brief = \"brief\", desc = \"desc\")] fn a() { }"
        );
        assert str::contains(markdown, "brief\n\ndesc");
    }

}<|endoftext|>


<commit_before>extern crate rustbox;
extern crate chrono;

use rustbox::*;
use chrono::*;

use screen::common::*;
use utility::string::*;
use model::ShowItem;
use model::ShowReplyItem;
use reply_model::*;

pub struct Show<'a> {
    rustbox: &'a rustbox::RustBox,
    scrollY: usize,
    y: usize
}

impl<'a> Show<'a> {
    pub fn new(rustbox: &'a rustbox::RustBox) -> Self {
        Show {
            rustbox: &rustbox,
            scrollY: 0,
            y: 0
        }
    }
    pub fn print(&mut self, title: &str, item: &ShowItem) {

        self.y = 2;

        self.print_header(&format!("{} - {} [{}/{}]",
                                   item.title,
                                   title,
                                   item.page,
                                   item.max_page));
        self.print_body(&item);
    }

    fn print_separator_top(&mut self, reply: &ShowReplyItem) {
        if self.y > self.scrollY + 1 {
            let (replier_name, time) = self.build_separator_content(&reply);
            self.rustbox.print(0,
                               self.y - self.scrollY,
                               rustbox::RB_NORMAL,
                               Color::Green,
                               Color::Black,
                               &self.build_separator_top(&replier_name, &time));
        }
    }

    fn print_separator_bottom(&mut self) {
        if self.y > self.scrollY + 1 {
            self.rustbox.print(0,
                               self.y - self.scrollY,
                               rustbox::RB_NORMAL,
                               Color::Green,
                               Color::Black,
                               &self.build_separator_bottom());
        }
    }

    fn print_header(&mut self, text: &str) {
        let title_len = jks_len(text);
        let padding = (if self.rustbox.width() >= title_len {
            self.rustbox.width() - title_len
        } else {
            0
        }) / 2;

        let header_bottom = seq_str_gen(0, self.rustbox.width(), "─", "");

        clearline(&self.rustbox, self.rustbox.width(), 0, 0);
        self.rustbox.print(padding,
                           0,
                           rustbox::RB_BOLD,
                           Color::White,
                           Color::Black,
                           text);
        self.rustbox.print(0,
                           1,
                           rustbox::RB_BOLD,
                           Color::Yellow,
                           Color::Black,
                           &header_bottom);
    }

    pub fn print_body(&mut self, item: &ShowItem) {
        let width = self.body_width();
        let rows = self.body_height();
        let rustbox = self.rustbox;
        let scrollY = self.scrollY;

        for (i, reply) in item.replies.iter().take(rows).enumerate() {

            self.y += self.print_reply(&reply.body, 0);

            self.print_separator_top(&reply);
            self.y += 1;

            self.print_separator_bottom();
            self.y += 1;
        }
    }

    fn print_reply(&mut self, vec: &Vec<NodeType>,
                   depth: usize)
                   -> usize {

       let rustbox = self.rustbox;
       let scrollY = self.scrollY;

        let padding = seq_str_gen(0, depth, "├─", "");
        let mut m = 0;
        let mut recursive_offset = 0;
        let mut total_y = 0;
        let mut line = String::new();
        let mut is_first = true;

        let vec_clean = clean_reply_body(vec);
        for (j, node) in vec_clean.iter().enumerate() {
            total_y = self.y + m + recursive_offset;
            if total_y > scrollY + 1 {
                let node2 = node.clone();
                match node2 {
                    NodeType::Text(n) => {
                        if n.data != "" {
                            line = format!("{}{}", line, n.data);
                        }
                    }
                    NodeType::Image(n) => {
                        if n.data != "" {
                            line = format!("{}[img {}]", line, n.data);
                        }
                    }
                    NodeType::BlockQuote(n) => {
                        recursive_offset += self.print_reply(&n.data, depth + 1); // consider we pass y as total_y here previously
                        is_first = false;
                    }
                    NodeType::Br(n) => {
                        if !line.is_empty() {
                            print_default(rustbox,
                                          0,
                                          total_y - scrollY,
                                          format!(" {}{}", padding, line));
                            line = String::new();
                            is_first = false;
                        }

                        // prevent first line empty
                        if !is_first {
                            m += 1;
                        }

                    }
                }
            }
        }

        if !line.is_empty() {
            total_y = self.y + m + recursive_offset;
            print_default(rustbox,
                          0,
                          total_y - scrollY,
                          format!(" {}{}  ", padding, line));
            line = String::new();
            m += 1;
        }

        m + recursive_offset
    }

    fn build_separator_content(&mut self, reply: &ShowReplyItem) -> (String, String) {
        let now = Local::now();

        let replier_name = reply.username.clone();

        let published_at = reply.published_at.clone();

        let published_at_dt = match Local.datetime_from_str(&published_at, "%d/%m/%Y %H:%M") {
            Ok(v) => v,
            Err(e) => now,
        };
        let time = published_at_format(&(now - published_at_dt));
        (replier_name, time)
    }

    fn build_separator_arguments(&mut self) -> (usize, usize, String) {
        let width = self.body_width();
        let rustbox = self.rustbox;

        let separator_width = if rustbox.width() >= 2 {
            rustbox.width() - 2
        } else {
            0
        };
        let separator_padding_width = if rustbox.width() > separator_width {
            rustbox.width() - separator_width
        } else {
            0
        } / 2;

        let separator_padding = seq_str_gen(0, separator_padding_width, " ", "");

        (separator_width, separator_padding_width, separator_padding)
    }

    fn build_separator_top(&mut self, replier_name: &str, time: &str) -> String {
        let replier_max_width = 14;
        let time_max_width = 5;
        let (separator_width, separator_padding_width, separator_padding) =
            self.build_separator_arguments();
        make_separator_top(separator_width,
                           &separator_padding,
                           replier_max_width,
                           &replier_name,
                           time_max_width,
                           &time)
    }

    fn build_separator_bottom(&mut self) -> String {
        let (separator_width, separator_padding_width, separator_padding) =
            self.build_separator_arguments();
        make_separator_bottom(separator_width, &separator_padding)
    }

    pub fn resetY(&mut self) {
        self.scrollY = 0;
    }

    pub fn scrollUp(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp > value {
            self.scrollY = tmp - value;
            true
        } else if tmp != 0 {
            self.scrollY = 0;
            true
        } else {
            false
        }
    }

    pub fn scrollDown(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp < 10000 {
            self.scrollY = tmp + value;
            return true;
        }
        false
    }

    pub fn body_height(&self) -> usize {
        if self.rustbox.height() >= 3 {
            self.rustbox.height() - 3
        } else {
            0
        }
    }

    pub fn body_width(&self) -> usize {
        if self.rustbox.width() >= 2 {
            self.rustbox.width() - 2
        } else {
            0
        }
    }

}

fn print_default(rustbox: &rustbox::RustBox, x: usize, y: usize, s: String) {
    rustbox.print(0, y, rustbox::RB_NORMAL, Color::White, Color::Black, &s);
}

fn clean_reply_body(vec: &Vec<NodeType>) -> Vec<NodeType>{
    // clean up lines (end)
    let vec2 = {
        let vec_length = vec.len();
        let vec_check_cleanup = vec.clone();

        // check if last 4 elements match the EMPTY PATTERN
        let is_last4_empty = vec_check_cleanup.iter()
                                              .rev()
                                              .take(4)
                                              .enumerate()
                                              .all(|(j, node)| match node.clone() {
                                                  NodeType::Br(n) => j == 1 || j == 2 || j == 3,
                                                  NodeType::Text(n) => j == 0 && n.data.is_empty(),
                                                  _ => false,
                                              });

        let vec_short_length = if vec_length > 4 && is_last4_empty {
            vec_length - 4
        } else {
            vec_length
        };

        vec.iter().take(vec_short_length)
    };

    // clean up lines (start)
    let vec3 = {
        let vec2_cloned = vec2.clone();
        let mut result: Vec<NodeType> = Vec::new();
        for (j, node) in vec2_cloned.enumerate() {
            let node2 = node.clone();
            let node3 = node.clone();
            match node2 {
                NodeType::Br(n) => {
                    if !result.is_empty() {
                        result.push(node3);
                    }
                }
                _ => result.push(node3),
            }
        }
        result.clone()
    };

    vec3
}

fn make_separator_replier_name(separator_width: usize,
                               separator_padding: &str,
                               replier_max_width: usize,
                               replier_name: &str)
                               -> String {
    let replier_name_len = jks_len(&replier_name);
    let replier_name_spacing_width = replier_max_width - replier_name_len;
    let is_replier_name_spacing_width_odd = replier_name_spacing_width & 1 == 1;
    let replier_name_right_spacing_width = replier_name_spacing_width / 2;
    let replier_name_left_spacing_width = if is_replier_name_spacing_width_odd {
        replier_name_right_spacing_width + 1
    } else {
        replier_name_right_spacing_width
    };

    let replier_name_left_spacing = seq_str_gen(0, replier_name_left_spacing_width, "─", "");
    let replier_name_right_spacing = seq_str_gen(0, replier_name_right_spacing_width, "─", "");

    let separator_replier = format!("{}{}{}{}{}",
                                    "╭",
                                    replier_name_left_spacing,
                                    replier_name,
                                    replier_name_right_spacing,
                                    "");

    return separator_replier;
}

fn make_separator_time(separator_width: usize,
                       separator_padding: &str,
                       time_max_width: usize,
                       time: &str)
                       -> String {
    let time_len = jks_len(&time);
    let time_spacing_width = if time_max_width > time_len {
        time_max_width - time_len
    } else {
        0
    };

    let is_time_spacing_width_odd = time_spacing_width & 1 == 1;
    let time_right_spacing_width = time_spacing_width / 2;
    let time_left_spacing_width = if is_time_spacing_width_odd {
        time_right_spacing_width + 1
    } else {
        time_right_spacing_width
    };

    let time_left_spacing = seq_str_gen(0, time_left_spacing_width, "─", "");
    let time_right_spacing = seq_str_gen(0, time_right_spacing_width, "─", "");

    let separator_time = format!("{}{}{}{}{}",
                                 "",
                                 time_left_spacing,
                                 time,
                                 time_right_spacing,
                                 "╮");

    return separator_time;
}

fn make_separator_top(separator_width: usize,
                      separator_padding: &str,
                      replier_max_width: usize,
                      replier_name: &str,
                      time_max_width: usize,
                      time: &str)
                      -> String {

    let separator_replier = make_separator_replier_name(separator_width,
                                                        &separator_padding,
                                                        replier_max_width,
                                                        &replier_name);

    let separator_replier_width = jks_len(&separator_replier);

    let separator_time = make_separator_time(separator_width,
                                             &separator_padding,
                                             time_max_width,
                                             &time);

    let separator_time_width = jks_len(&separator_time);

    let separator_top_middle_width = if separator_width >=
                                        (separator_replier_width + separator_time_width) {
        separator_width - separator_replier_width - separator_time_width
    } else {
        0
    };

    let separator_top_middle = seq_str_gen(0, separator_top_middle_width, " ", "");
    let separator_top = format!("{}{}{}{}{}",
                                separator_padding,
                                separator_top_middle,
                                separator_replier,
                                separator_time,
                                separator_padding);
    return separator_top;
}

fn make_separator_bottom(separator_width: usize, separator_padding: &str) -> String {
    let style_box_width = 1;
    let separator_bottom_middle_width = if separator_width > style_box_width {
        separator_width - style_box_width
    } else {
        0
    };
    let separator_bottom_middle = seq_str_gen(0, separator_bottom_middle_width, "─", "");

    let separator_bottom = format!("{}{}{}{}",
                                   separator_padding,
                                   separator_bottom_middle,
                                   "╯",
                                   separator_padding);
    return separator_bottom;
}

fn published_at_format(duration: &Duration) -> String {
    let weeks = duration.num_weeks();
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();

    if weeks > 0 {
        format!("{}w", weeks)
    } else if days > 0 {
        format!("{}d", days)
    } else if hours > 0 {
        format!("{}h", hours)
    } else if minutes > 0 {
        format!("{}m", minutes)
    } else {
        String::from("1m")
    }
}

fn seq_str_gen(start: usize, end: usize, sym: &str, join_sym: &str) -> String {
    (start..end).map(|_| sym.clone()).collect::<Vec<_>>().join(&join_sym)
}
<commit_msg>show - make_separator_content<commit_after>extern crate rustbox;
extern crate chrono;

use rustbox::*;
use chrono::*;

use screen::common::*;
use utility::string::*;
use model::ShowItem;
use model::ShowReplyItem;
use reply_model::*;

pub struct Show<'a> {
    rustbox: &'a rustbox::RustBox,
    scrollY: usize,
    y: usize
}

impl<'a> Show<'a> {
    pub fn new(rustbox: &'a rustbox::RustBox) -> Self {
        Show {
            rustbox: &rustbox,
            scrollY: 0,
            y: 0
        }
    }
    pub fn print(&mut self, title: &str, item: &ShowItem) {

        self.y = 2;

        self.print_header(&format!("{} - {} [{}/{}]",
                                   item.title,
                                   title,
                                   item.page,
                                   item.max_page));
        self.print_body(&item);
    }

    fn print_separator_top(&mut self, reply: &ShowReplyItem) {
        if self.y > self.scrollY + 1 {
            let (replier_name, time) = make_separator_content(&reply);
            self.rustbox.print(0,
                               self.y - self.scrollY,
                               rustbox::RB_NORMAL,
                               Color::Green,
                               Color::Black,
                               &self.build_separator_top(&replier_name, &time));
        }
    }

    fn print_separator_bottom(&mut self) {
        if self.y > self.scrollY + 1 {
            self.rustbox.print(0,
                               self.y - self.scrollY,
                               rustbox::RB_NORMAL,
                               Color::Green,
                               Color::Black,
                               &self.build_separator_bottom());
        }
    }

    fn print_header(&mut self, text: &str) {
        let title_len = jks_len(text);
        let padding = (if self.rustbox.width() >= title_len {
            self.rustbox.width() - title_len
        } else {
            0
        }) / 2;

        let header_bottom = seq_str_gen(0, self.rustbox.width(), "─", "");

        clearline(&self.rustbox, self.rustbox.width(), 0, 0);
        self.rustbox.print(padding,
                           0,
                           rustbox::RB_BOLD,
                           Color::White,
                           Color::Black,
                           text);
        self.rustbox.print(0,
                           1,
                           rustbox::RB_BOLD,
                           Color::Yellow,
                           Color::Black,
                           &header_bottom);
    }

    pub fn print_body(&mut self, item: &ShowItem) {
        let width = self.body_width();
        let rows = self.body_height();
        let rustbox = self.rustbox;
        let scrollY = self.scrollY;

        for (i, reply) in item.replies.iter().take(rows).enumerate() {

            self.y += self.print_reply(&reply.body, 0);

            self.print_separator_top(&reply);
            self.y += 1;

            self.print_separator_bottom();
            self.y += 1;
        }
    }

    fn print_reply(&mut self, vec: &Vec<NodeType>,
                   depth: usize)
                   -> usize {

       let rustbox = self.rustbox;
       let scrollY = self.scrollY;

        let padding = seq_str_gen(0, depth, "├─", "");
        let mut m = 0;
        let mut recursive_offset = 0;
        let mut total_y = 0;
        let mut line = String::new();
        let mut is_first = true;

        let vec_clean = clean_reply_body(vec);
        for (j, node) in vec_clean.iter().enumerate() {
            total_y = self.y + m + recursive_offset;
            if total_y > scrollY + 1 {
                let node2 = node.clone();
                match node2 {
                    NodeType::Text(n) => {
                        if n.data != "" {
                            line = format!("{}{}", line, n.data);
                        }
                    }
                    NodeType::Image(n) => {
                        if n.data != "" {
                            line = format!("{}[img {}]", line, n.data);
                        }
                    }
                    NodeType::BlockQuote(n) => {
                        recursive_offset += self.print_reply(&n.data, depth + 1); // consider we pass y as total_y here previously
                        is_first = false;
                    }
                    NodeType::Br(n) => {
                        if !line.is_empty() {
                            print_default(rustbox,
                                          0,
                                          total_y - scrollY,
                                          format!(" {}{}", padding, line));
                            line = String::new();
                            is_first = false;
                        }

                        // prevent first line empty
                        if !is_first {
                            m += 1;
                        }

                    }
                }
            }
        }

        if !line.is_empty() {
            total_y = self.y + m + recursive_offset;
            print_default(rustbox,
                          0,
                          total_y - scrollY,
                          format!(" {}{}  ", padding, line));
            line = String::new();
            m += 1;
        }

        m + recursive_offset
    }

    fn build_separator_arguments(&mut self) -> (usize, usize, String) {
        let width = self.body_width();
        let rustbox = self.rustbox;

        let separator_width = if rustbox.width() >= 2 {
            rustbox.width() - 2
        } else {
            0
        };
        let separator_padding_width = if rustbox.width() > separator_width {
            rustbox.width() - separator_width
        } else {
            0
        } / 2;

        let separator_padding = seq_str_gen(0, separator_padding_width, " ", "");

        (separator_width, separator_padding_width, separator_padding)
    }

    fn build_separator_top(&mut self, replier_name: &str, time: &str) -> String {
        let replier_max_width = 14;
        let time_max_width = 5;
        let (separator_width, separator_padding_width, separator_padding) =
            self.build_separator_arguments();
        make_separator_top(separator_width,
                           &separator_padding,
                           replier_max_width,
                           &replier_name,
                           time_max_width,
                           &time)
    }

    fn build_separator_bottom(&mut self) -> String {
        let (separator_width, separator_padding_width, separator_padding) =
            self.build_separator_arguments();
        make_separator_bottom(separator_width, &separator_padding)
    }

    pub fn resetY(&mut self) {
        self.scrollY = 0;
    }

    pub fn scrollUp(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp > value {
            self.scrollY = tmp - value;
            true
        } else if tmp != 0 {
            self.scrollY = 0;
            true
        } else {
            false
        }
    }

    pub fn scrollDown(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp < 10000 {
            self.scrollY = tmp + value;
            return true;
        }
        false
    }

    pub fn body_height(&self) -> usize {
        if self.rustbox.height() >= 3 {
            self.rustbox.height() - 3
        } else {
            0
        }
    }

    pub fn body_width(&self) -> usize {
        if self.rustbox.width() >= 2 {
            self.rustbox.width() - 2
        } else {
            0
        }
    }
}

fn print_default(rustbox: &rustbox::RustBox, x: usize, y: usize, s: String) {
    rustbox.print(0, y, rustbox::RB_NORMAL, Color::White, Color::Black, &s);
}

fn make_separator_content(reply: &ShowReplyItem) -> (String, String) {
    let now = Local::now();

    let replier_name = reply.username.clone();

    let published_at = reply.published_at.clone();

    let published_at_dt = match Local.datetime_from_str(&published_at, "%d/%m/%Y %H:%M") {
        Ok(v) => v,
        Err(e) => now,
    };
    let time = published_at_format(&(now - published_at_dt));
    (replier_name, time)
}

fn clean_reply_body(vec: &Vec<NodeType>) -> Vec<NodeType>{
    // clean up lines (end)
    let vec2 = {
        let vec_length = vec.len();
        let vec_check_cleanup = vec.clone();

        // check if last 4 elements match the EMPTY PATTERN
        let is_last4_empty = vec_check_cleanup.iter()
                                              .rev()
                                              .take(4)
                                              .enumerate()
                                              .all(|(j, node)| match node.clone() {
                                                  NodeType::Br(n) => j == 1 || j == 2 || j == 3,
                                                  NodeType::Text(n) => j == 0 && n.data.is_empty(),
                                                  _ => false,
                                              });

        let vec_short_length = if vec_length > 4 && is_last4_empty {
            vec_length - 4
        } else {
            vec_length
        };

        vec.iter().take(vec_short_length)
    };

    // clean up lines (start)
    let vec3 = {
        let vec2_cloned = vec2.clone();
        let mut result: Vec<NodeType> = Vec::new();
        for (j, node) in vec2_cloned.enumerate() {
            let node2 = node.clone();
            let node3 = node.clone();
            match node2 {
                NodeType::Br(n) => {
                    if !result.is_empty() {
                        result.push(node3);
                    }
                }
                _ => result.push(node3),
            }
        }
        result.clone()
    };

    vec3
}

fn make_separator_replier_name(separator_width: usize,
                               separator_padding: &str,
                               replier_max_width: usize,
                               replier_name: &str)
                               -> String {
    let replier_name_len = jks_len(&replier_name);
    let replier_name_spacing_width = replier_max_width - replier_name_len;
    let is_replier_name_spacing_width_odd = replier_name_spacing_width & 1 == 1;
    let replier_name_right_spacing_width = replier_name_spacing_width / 2;
    let replier_name_left_spacing_width = if is_replier_name_spacing_width_odd {
        replier_name_right_spacing_width + 1
    } else {
        replier_name_right_spacing_width
    };

    let replier_name_left_spacing = seq_str_gen(0, replier_name_left_spacing_width, "─", "");
    let replier_name_right_spacing = seq_str_gen(0, replier_name_right_spacing_width, "─", "");

    let separator_replier = format!("{}{}{}{}{}",
                                    "╭",
                                    replier_name_left_spacing,
                                    replier_name,
                                    replier_name_right_spacing,
                                    "");

    return separator_replier;
}

fn make_separator_time(separator_width: usize,
                       separator_padding: &str,
                       time_max_width: usize,
                       time: &str)
                       -> String {
    let time_len = jks_len(&time);
    let time_spacing_width = if time_max_width > time_len {
        time_max_width - time_len
    } else {
        0
    };

    let is_time_spacing_width_odd = time_spacing_width & 1 == 1;
    let time_right_spacing_width = time_spacing_width / 2;
    let time_left_spacing_width = if is_time_spacing_width_odd {
        time_right_spacing_width + 1
    } else {
        time_right_spacing_width
    };

    let time_left_spacing = seq_str_gen(0, time_left_spacing_width, "─", "");
    let time_right_spacing = seq_str_gen(0, time_right_spacing_width, "─", "");

    let separator_time = format!("{}{}{}{}{}",
                                 "",
                                 time_left_spacing,
                                 time,
                                 time_right_spacing,
                                 "╮");

    return separator_time;
}

fn make_separator_top(separator_width: usize,
                      separator_padding: &str,
                      replier_max_width: usize,
                      replier_name: &str,
                      time_max_width: usize,
                      time: &str)
                      -> String {

    let separator_replier = make_separator_replier_name(separator_width,
                                                        &separator_padding,
                                                        replier_max_width,
                                                        &replier_name);

    let separator_replier_width = jks_len(&separator_replier);

    let separator_time = make_separator_time(separator_width,
                                             &separator_padding,
                                             time_max_width,
                                             &time);

    let separator_time_width = jks_len(&separator_time);

    let separator_top_middle_width = if separator_width >=
                                        (separator_replier_width + separator_time_width) {
        separator_width - separator_replier_width - separator_time_width
    } else {
        0
    };

    let separator_top_middle = seq_str_gen(0, separator_top_middle_width, " ", "");
    let separator_top = format!("{}{}{}{}{}",
                                separator_padding,
                                separator_top_middle,
                                separator_replier,
                                separator_time,
                                separator_padding);
    return separator_top;
}

fn make_separator_bottom(separator_width: usize, separator_padding: &str) -> String {
    let style_box_width = 1;
    let separator_bottom_middle_width = if separator_width > style_box_width {
        separator_width - style_box_width
    } else {
        0
    };
    let separator_bottom_middle = seq_str_gen(0, separator_bottom_middle_width, "─", "");

    let separator_bottom = format!("{}{}{}{}",
                                   separator_padding,
                                   separator_bottom_middle,
                                   "╯",
                                   separator_padding);
    return separator_bottom;
}

fn published_at_format(duration: &Duration) -> String {
    let weeks = duration.num_weeks();
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();

    if weeks > 0 {
        format!("{}w", weeks)
    } else if days > 0 {
        format!("{}d", days)
    } else if hours > 0 {
        format!("{}h", hours)
    } else if minutes > 0 {
        format!("{}m", minutes)
    } else {
        String::from("1m")
    }
}

fn seq_str_gen(start: usize, end: usize, sym: &str, join_sym: &str) -> String {
    (start..end).map(|_| sym.clone()).collect::<Vec<_>>().join(&join_sym)
}
<|endoftext|>


<commit_before>use std::fmt;
use std::error::Error;
use super::{
    Ix,
};

/// An error that can be produced by `.into_shape()`
#[derive(Clone, Debug)]
pub enum ShapeError {
    /// incompatible shapes in reshape, (from, to)
    IncompatibleShapes(Box<[Ix]>, Box<[Ix]>),
    /// incompatible layout: not contiguous
    IncompatibleLayout,
    /// Dimension too large (shape)
    DimensionTooLarge(Box<[Ix]>),
}

impl Error for ShapeError {
    fn description(&self) -> &str {
        match *self {
            ShapeError::IncompatibleShapes(..) =>
                "incompatible shapes in reshape",
            ShapeError::IncompatibleLayout =>
                "incompatible layout: not not contiguous",
            ShapeError::DimensionTooLarge(..) =>
                "dimension too large",
        }
    }
}

impl fmt::Display for ShapeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ShapeError::IncompatibleShapes(ref a, ref b) => {
                write!(f, "incompatible shapes, attempted from: {:?}, to: {:?}",
                       a, b)
            }
            ShapeError::IncompatibleLayout => {
                write!(f, "{}", self.description())
            }
            ShapeError::DimensionTooLarge(ref a) => {
                write!(f, "dimension too large in shape {:?}", a)
            }
        }
    }
}
<commit_msg>Fix typo and wording in ShapeError::description<commit_after>use std::fmt;
use std::error::Error;
use super::{
    Ix,
};

/// An error that can be produced by `.into_shape()`
#[derive(Clone, Debug)]
pub enum ShapeError {
    /// incompatible shapes in reshape, (from, to)
    IncompatibleShapes(Box<[Ix]>, Box<[Ix]>),
    /// incompatible layout: not contiguous
    IncompatibleLayout,
    /// Dimension too large (shape)
    DimensionTooLarge(Box<[Ix]>),
}

impl Error for ShapeError {
    fn description(&self) -> &str {
        match *self {
            ShapeError::IncompatibleShapes(..) =>
                "incompatible shapes",
            ShapeError::IncompatibleLayout =>
                "incompatible layout (not contiguous)",
            ShapeError::DimensionTooLarge(..) =>
                "dimension too large",
        }
    }
}

impl fmt::Display for ShapeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ShapeError::IncompatibleShapes(ref a, ref b) => {
                write!(f, "incompatible shapes, attempted from: {:?}, to: {:?}",
                       a, b)
            }
            ShapeError::IncompatibleLayout => {
                write!(f, "{}", self.description())
            }
            ShapeError::DimensionTooLarge(ref a) => {
                write!(f, "dimension too large in shape {:?}", a)
            }
        }
    }
}
<|endoftext|>