<commit_before>use std::error::Error;
use std::fmt;

use self::FIXMessageParseError::{ProtocolVersionNotFound, UnsupportedProtocolVersion, InvalidChecksum};

#[derive(PartialEq, Debug)]
pub enum FIXMessageParseError {
  ProtocolVersionNotFound,
  UnsupportedProtocolVersion(String),
  InvalidChecksum,
}

impl fmt::Display for FIXMessageParseError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match *self {
      UnsupportedProtocolVersion(ref version) => write!(f, "{}: {}", self.description(), version),
      _ => write!(f, "{}", self.description()),
    }
  }
}

impl Error for FIXMessageParseError {
  fn description(&self) -> &str {
    match *self {
      ProtocolVersionNotFound => "FIX message protocol version not found",
      UnsupportedProtocolVersion(..) => "Unsupported FIX protocol version",
      InvalidChecksum => "Invalid FIX message checksum.",
    }
  }
}

pub struct FIXMessage {
  version: String,
  header: FIXMessageHeader,
  body: FIXMessageBody,
  footer: FIXMessageFooter,
}

pub struct FIXMessageHeader {
  version: String,
  body: String,
}

pub struct FIXMessageBody {
  version: String,
  body: String,
}

pub struct FIXMessageFooter {
  version: String,
  body: String,
}

fn get_protocol_version(inbound_message: &str) -> Result<String, FIXMessageParseError> {
  Ok(String::from("test"))
}

pub fn parse(inbound_message: &str) -> Result<FIXMessage, FIXMessageParseError> {
  let fix_version = "4.2";
  let fix_message_header = FIXMessageHeader {version: String::from(fix_version), body: "fix_header".to_string()};
  let fix_message_body = FIXMessageBody {version: String::from(fix_version), body: "fix_body".to_string()};
  let fix_message_footer = FIXMessageFooter {version: String::from(fix_version), body: "fix_footer".to_string()};
  let fix_message = FIXMessage {
    version: String::from(fix_version),
    header: fix_message_header,
    body: fix_message_body,
    footer: fix_message_footer
  };

  Ok(fix_message)
}

pub fn generate(outbound_messge: FIXMessage) -> String {
  return "hello_world".to_string();
}

#[cfg(test)]
mod tests {
  use super::{parse, generate};

  #[test]
  fn it_should_parse_fix_message() {
    assert_eq!(true, true);
  }

  #[test]
  fn it_should_generate_fix_message() {
    assert_eq!(true, true);
  }
}
<commit_msg>FIX message API defined<commit_after>//! The `fix_message` crate provides functions that parse, validate and generate
//! FIX messages. These functions are agnostic to FIX protocol version.
//!
//! Here validated only those message fields which common for all versions of
//! FIX protocol.
//! This crate should be used as a middleware for parsing and initial validation
//! of messages. And as middleware for the final validation, checksum
//! calculation and FIX message generation.
//!
//! Message body or additional fields must be validated in version specific
//! crates.

use std::error::Error;
use std::fmt;
use std::result;

use self::FIXMessageError::{ProtocolVersionNotFound, InvalidChecksum};

const FIX_MESSAGE_DELIMITER: char = '\x01';
const FIX_MESSAGE_FIELD_DELIMITER: char = '\x3D';

#[derive(PartialEq, Debug)]
pub enum FIXMessageError {
  ProtocolVersionNotFound,
  InvalidChecksum,
}

impl fmt::Display for FIXMessageError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match *self {
      _ => write!(f, "{}", self.description()),
    }
  }
}

impl Error for FIXMessageError {
  fn description(&self) -> &str {
    match *self {
      ProtocolVersionNotFound => "FIX message protocol version not found",
      InvalidChecksum => "Invalid FIX message checksum.",
    }
  }
}

/// This structure represents field/value pair of FIX message
pub struct FIXMessageField {
  field: String,
  value: String
}

/// This structure represents the whole FIX message
///
/// ### Parsing FIX message
/// When parsing FIX messaged then field/value pairs stored in order they
/// were in the message.
///
/// ### Generating FIX messages
/// When generating FIX message then field/value pairs will be concatenated
/// in the order they stored in vector.
/// First two fileds (8, 9) should not be provided in data (will be ignored if
/// found). Field `8` - `BeginString` will be automatically added to the message
/// header and have `FIXMessage.version` value. Field `9` - `BodyLength` will be
/// automatically calculated and added to the message header.
pub struct FIXMessage {
  version: String,
  data: Vec<FIXMessageField>
}

/// This function validates and parses FIX message
///
/// # Examples
pub fn parse(inbound_message: &str) -> FIXMessageResult<FIXMessage> {
  unimplemented!();
}

/// This function validates and generates FIX message
///
/// # Examples
pub fn generate(outbound_messge: FIXMessage) -> FIXMessageResult<String> {
  unimplemented!();
}

pub type FIXMessageResult<T> = result::Result<T, FIXMessageError>;

#[cfg(test)]
mod tests {
  use super::{parse, generate};

  #[test]
  fn it_should_complain_when_checksum_not_found() {
    // checksum must always be the last
    unimplemented!();
  }

  #[test]
  fn it_should_complain_when_checksum_format_is_not_valid() {
    unimplemented!();
  }

  #[test]
  fn it_should_complain_when_checksum_is_incorrect() {
    unimplemented!();
  }

  #[test]
  fn it_should_complain_when_not_all_required_header_fields_presented() {
    // should split message by delimiter
    unimplemented!();
  }

  #[test]
  fn it_should_complain_when_first_three_fields_in_wrong_order() {
    // should split message by delimiter
    unimplemented!();
  }

  #[test]
  fn it_should_parse_fix_message() {
    // should split message by delimiter
    unimplemented!();
  }

  #[test]
  fn it_should_generate_fix_message() {
    // concatenate message
    unimplemented!();
  }
}
<|endoftext|>


<commit_before>#![feature(panic_implementation)] // required for defining the panic handler
#![no_std] // don't link the Rust standard library
#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points
#![cfg_attr(test, allow(dead_code, unused_macros))] // allow unused code in test mode

extern crate spin;
extern crate volatile;
#[macro_use]
extern crate lazy_static;

#[cfg(test)]
extern crate array_init;
#[cfg(test)]
extern crate std;

use core::panic::PanicInfo;

#[macro_use]
mod vga_buffer;

/// This function is the entry point, since the linker looks for a function
/// named `_start_` by default.
#[cfg(not(test))]
#[no_mangle] // don't mangle the name of this function
pub extern "C" fn _start() -> ! {
    println!("Hello World{}", "!");

    loop {}
}

/// This function is called on panic.
#[cfg(not(test))]
#[panic_implementation]
#[no_mangle]
pub fn panic(info: &PanicInfo) -> ! {
    println!("{}", info);
    loop {}
}
<commit_msg>Import PanicInfo only in non(test) environment<commit_after>#![feature(panic_implementation)] // required for defining the panic handler
#![no_std] // don't link the Rust standard library
#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points
#![cfg_attr(test, allow(dead_code, unused_macros))] // allow unused code in test mode

extern crate spin;
extern crate volatile;
#[macro_use]
extern crate lazy_static;

#[cfg(test)]
extern crate array_init;
#[cfg(test)]
extern crate std;

#[cfg(not(test))]
use core::panic::PanicInfo;

#[macro_use]
mod vga_buffer;

/// This function is the entry point, since the linker looks for a function
/// named `_start_` by default.
#[cfg(not(test))]
#[no_mangle] // don't mangle the name of this function
pub extern "C" fn _start() -> ! {
    println!("Hello World{}", "!");

    loop {}
}

/// This function is called on panic.
#[cfg(not(test))]
#[panic_implementation]
#[no_mangle]
pub fn panic(info: &PanicInfo) -> ! {
    println!("{}", info);
    loop {}
}
<|endoftext|>


<commit_before>//! A library for using the Monzo API
//!
//! This library wraps over the Monzo API in a future aware manner.
//!
//! Example usage:
//!
//! ```rust,no_run
//! extern crate monzo;
//! extern crate tokio_core;
//!
//! let mut core = tokio_core::reactor::Core::new().unwrap();
//! let monzo = monzo::Client::new(&core.handle(), "<access_token>");
//! let work = monzo.balance("<account_id>".into());
//! let response = core.run(work).unwrap();
//! println!("Balance: {} {}", response.balance, response.currency);
//! println!("Spent today: {}", response.spend_today);
//! ```

#![crate_name = "monzo"]
#![deny(missing_docs,
        missing_debug_implementations,
        missing_copy_implementations,
        trivial_casts,
        trivial_numeric_casts,
        unsafe_code,
        unused_extern_crates,
        unused_import_braces,
        unused_qualifications)]

#[macro_use]
extern crate error_chain;
extern crate chrono;
extern crate futures;
extern crate hyper;
extern crate serde;
extern crate hyper_tls;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate tokio_core;
extern crate url;

use chrono::DateTime;
use chrono::offset::Utc;
use futures::{Future, Stream};
use hyper::{Body, Method, Request, Uri, Chunk, StatusCode};
use hyper::header::{Authorization, Bearer};
use serde::de;
use serde::de::Deserialize;
use serde::de::Deserializer;
use serde::de::Visitor;
use std::collections::HashMap;
use std::str::FromStr;
use std::string::String;
use tokio_core::reactor::Handle;
use url::Url;

/// Identifier for an account.
pub type AccountId = String;
/// Identifier for a transaction.
pub type TransactionId = String;
/// Identifier of a merchant.
pub type MerchantId = String;
/// Holds an ISO 4217 currency code.
pub type Currency = String;

/// Accounts represent a store of funds, and have a list of transactions.
#[derive(Debug, Deserialize)]
pub struct Account {
    /// The account id.
    pub id: AccountId,
    /// Description of the account.
    pub description: String,
    /// The timestamp when the account was created.
    pub created: DateTime<Utc>,
}

/// Response to the list accounts future.
#[derive(Debug, Deserialize)]
pub struct Accounts {
    /// List of accounts owned by the currenty authorized user.
    pub accounts: Vec<Account>,
}

/// Response to the balance future if successful.
#[derive(Debug, Deserialize)]
pub struct Balance {
    /// The currently available balance of the account, as a 64bit integer in minor units of the
    /// currency, eg. pennies for GBP, or cents for EUR and USD.
    pub balance: i64,
    /// The ISO 4217 currency code.
    pub currency: Currency,
    /// The amount spent from this account today (considered from approx 4am onwards), in minor
    /// units of the currency.
    pub spend_today: i64,
}

/// Deserializes a string but returns None on empty string.
fn none_for_empty_string<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
    T: Deserialize<'de> + FromStr,
    D: Deserializer<'de>,
{
    // This is a Visitor that forwards string types to T's `FromStr` impl if the string is
    // non-empty The `PhantomData` is to keep the compiler from complaining about T being an unused
    // generic type parameter. We need T in order to know the Value type for the Visitor impl.
    struct NonEmptyString<T>(std::marker::PhantomData<Option<T>>);

    impl<'de, T> Visitor<'de> for NonEmptyString<T>
    where
        T: Deserialize<'de> + FromStr,
    {
        type Value = Option<T>;

        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            formatter.write_str("string")
        }

        fn visit_str<E>(self, value: &str) -> Result<Option<T>, E>
        where
            E: de::Error,
        {
            if value.is_empty() {
                return Ok(None);
            } else {
                let res = FromStr::from_str(value);
                match res {
                    Ok(good) => Ok(Some(good)),
                    // TODO: Find a way to propagate the error.
                    Err(_) => Err(de::Error::custom("could not parse string")),
                }
            }
        }

        fn visit_string<E>(self, value: String) -> Result<Option<T>, E>
        where
            E: de::Error,
        {
            self.visit_str(&value)
        }
    }

    deserializer.deserialize_any(NonEmptyString(std::marker::PhantomData))
}

/// Describes a transaction.
#[derive(Debug, Deserialize)]
pub struct Transaction {
    /// Balance in the account after the transaction.
    pub account_balance: i64,
    /// The amount of the transaction in minor units of currency. For example pennies in the case
    /// of GBP. A negative amount indicates a debit (most card transactions will have a negative
    /// amount).
    pub amount: i64,
    /// The timestamp in when the transaction was created.
    pub created: DateTime<Utc>,
    /// The ISO 4217 currency code.
    pub currency: Currency,
    /// Description of the transaction.
    pub description: String,
    /// The transaction id.
    pub id: TransactionId,
    /// This contains the merchant_id of the merchant that this transaction was made at.
    pub merchant: Option<MerchantId>,
    /// Key-value annotations made for transaction. Metadata is private to your application.
    pub metadata: HashMap<String, String>,
    /// Notes attached to the transaction.
    pub notes: String,
    /// Top-ups to an account are represented as transactions with a positive amount and
    /// is_load = true. Other transactions such as refunds, reversals or chargebacks may have a
    /// positive amount but is_load = false
    pub is_load: bool,
    /// The timestamp in UTC (RFC 3339) at which the transaction settled. In most cases, this
    /// happens 24-48 hours after created. If this field is not present, the transaction is
    /// authorised but not yet “complete”.
    ///
    /// Bug: Even though the Monzo documentation says the field is not present when not authorised,
    /// in practice they send an empty string.
    #[serde(deserialize_with = "none_for_empty_string")]
    pub settled: Option<DateTime<Utc>>,
    /// The category can be set for each transaction by the user. Over time we learn which merchant
    /// goes in which category and auto-assign the category of a transaction. If the user hasn’t
    /// set a category, we’ll return the default category of the merchant on this transactions.
    /// Top-ups have category mondo. Valid values are general, eating_out, expenses, transport,
    /// cash, bills, entertainment, shopping, holidays, groceries.
    pub category: String,
    /// This is only present on declined transactions! Valid values are INSUFFICIENT_FUNDS,
    /// CARD_INACTIVE, CARD_BLOCKED or OTHER.
    // TODO: Make this an enum?
    pub decline_reason: Option<String>,
}

/// Response to the transaction future if successful.
#[derive(Debug, Deserialize)]
pub struct Transactions {
    /// List of transactions.
    pub transactions: Vec<Transaction>,
}

/// Response to the futures in case of an error.
#[derive(Debug, Deserialize)]
pub struct Error {
    /// The HTTP response code.
    pub code: Option<String>,
    /// Short description of the error.
    // TODO; Maybe make this an enum since the documentation says it can only contain a certain set
    // of values.
    pub error: Option<String>,
    /// Longer description of the error.
    pub error_description: Option<String>,
    /// Additional information.
    pub message: Option<String>,
}

const ACCOUNT_ID: &'static str = "account_id";

/// Errors for this crate using `error_chain`.
pub mod errors {
    error_chain! {
        errors {
            #[doc = "When the Monzo API returns an error response code with more detailed \
            information."]
            BadResponse(statuscode: ::hyper::StatusCode, error: ::Error)
        }
        foreign_links {
            BadJsonResponse(::serde_json::Error)
            #[doc = "When the Monzo API returns invalid or unexpected json content."];
            NetworkError(::hyper::Error) #[doc = "Returned on network failure."];
        }
    }
}

/// The main interface for this crate.
#[derive(Debug)]
pub struct Client {
    client: hyper::client::Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>,
    access_token: String,
    base_url: Url,
}

/// The main interface for this crate.
impl Client {
    /// Creates a new Monzo client.
    pub fn new(handle: &Handle, access_token: &str) -> Client {
        Client::new_with_base_url(
            handle,
            access_token,
            "https://api.monzo.com".parse().unwrap(),
        )
    }

    /// Creates a new Monzo client with another base url. Useful for tests.
    pub fn new_with_base_url(handle: &Handle, access_token: &str, base_url: Url) -> Client {
        Client {
            client: ::hyper::Client::configure()
                .connector(::hyper_tls::HttpsConnector::new(4, handle).unwrap())
                .build(handle),
            access_token: access_token.into(),
            base_url: base_url,
        }
    }

    fn create_request(&self, uri: Uri) -> Request<Body> {
        let mut req: Request<Body> = Request::new(Method::Get, uri);
        req.headers_mut().set(Authorization(
            Bearer { token: self.access_token.clone() },
        ));
        req
    }

    fn make_request<T: 'static, F: 'static>(
        &self,
        uri: Uri,
        response_handler: F,
    ) -> Box<Future<Item = T, Error = errors::Error>>
    where
        F: Fn(Chunk) -> Result<T, errors::Error>,
    {
        let request = self.create_request(uri);
        let response: hyper::client::FutureResponse = self.client.request(request);
        let future = response
            .map_err(|err: hyper::Error| -> errors::Error { err.into() })
            .and_then(|res| {
                let status = res.status();
                res.body()
                    .concat2()
                    .map_err(|err: hyper::Error| err.into())
                    .and_then(move |body: Chunk| {
                        match status {
                            StatusCode::Ok => {}
                            _ => {
                                let error: Error = serde_json::from_slice(&body)?;
                                return Err(errors::ErrorKind::BadResponse(status, error).into());
                            }
                        };
                        response_handler(body)
                    })
            });

        Box::new(future)
    }

    /// Returns a list of accounts owned by the currently authorised user.
    pub fn accounts(&self) -> Box<Future<Item = Accounts, Error = errors::Error>> {
        let mut url = self.base_url.clone();
        url.path_segments_mut().unwrap().push("accounts");
        let uri: Uri = url.into_string().parse().unwrap();

        self.make_request(uri, |body| {
            let a: Accounts = serde_json::from_slice(&body)?;
            Ok(a)
        })
    }

    /// Retrieve information about an account’s balance.
    pub fn balance(
        &self,
        account_id: AccountId,
    ) -> Box<Future<Item = Balance, Error = errors::Error>> {
        let mut url = self.base_url.clone();
        url.path_segments_mut().unwrap().push("balance");
        url.query_pairs_mut().append_pair(ACCOUNT_ID, &account_id);
        let uri: Uri = url.into_string().parse().unwrap();

        self.make_request(uri, |body| {
            let b: Balance = serde_json::from_slice(&body)?;
            Ok(b)
        })
    }

    /// Returns a list of transactions on the user’s account.
    pub fn transactions(
        &self,
        account_id: AccountId,
    ) -> Box<Future<Item = Transactions, Error = errors::Error>> {
        let mut url = self.base_url.clone();
        url.path_segments_mut().unwrap().push("transactions");
        url.query_pairs_mut().append_pair(ACCOUNT_ID, &account_id);
        let uri: Uri = url.into_string().parse().unwrap();

        self.make_request(uri, |body| {
            let t: Transactions = serde_json::from_slice(&body)?;
            Ok(t)
        })
    }
}
<commit_msg>Add pull request to comment about the settled bug<commit_after>//! A library for using the Monzo API
//!
//! This library wraps over the Monzo API in a future aware manner.
//!
//! Example usage:
//!
//! ```rust,no_run
//! extern crate monzo;
//! extern crate tokio_core;
//!
//! let mut core = tokio_core::reactor::Core::new().unwrap();
//! let monzo = monzo::Client::new(&core.handle(), "<access_token>");
//! let work = monzo.balance("<account_id>".into());
//! let response = core.run(work).unwrap();
//! println!("Balance: {} {}", response.balance, response.currency);
//! println!("Spent today: {}", response.spend_today);
//! ```

#![crate_name = "monzo"]
#![deny(missing_docs,
        missing_debug_implementations,
        missing_copy_implementations,
        trivial_casts,
        trivial_numeric_casts,
        unsafe_code,
        unused_extern_crates,
        unused_import_braces,
        unused_qualifications)]

#[macro_use]
extern crate error_chain;
extern crate chrono;
extern crate futures;
extern crate hyper;
extern crate serde;
extern crate hyper_tls;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate tokio_core;
extern crate url;

use chrono::DateTime;
use chrono::offset::Utc;
use futures::{Future, Stream};
use hyper::{Body, Method, Request, Uri, Chunk, StatusCode};
use hyper::header::{Authorization, Bearer};
use serde::de;
use serde::de::Deserialize;
use serde::de::Deserializer;
use serde::de::Visitor;
use std::collections::HashMap;
use std::str::FromStr;
use std::string::String;
use tokio_core::reactor::Handle;
use url::Url;

/// Identifier for an account.
pub type AccountId = String;
/// Identifier for a transaction.
pub type TransactionId = String;
/// Identifier of a merchant.
pub type MerchantId = String;
/// Holds an ISO 4217 currency code.
pub type Currency = String;

/// Accounts represent a store of funds, and have a list of transactions.
#[derive(Debug, Deserialize)]
pub struct Account {
    /// The account id.
    pub id: AccountId,
    /// Description of the account.
    pub description: String,
    /// The timestamp when the account was created.
    pub created: DateTime<Utc>,
}

/// Response to the list accounts future.
#[derive(Debug, Deserialize)]
pub struct Accounts {
    /// List of accounts owned by the currenty authorized user.
    pub accounts: Vec<Account>,
}

/// Response to the balance future if successful.
#[derive(Debug, Deserialize)]
pub struct Balance {
    /// The currently available balance of the account, as a 64bit integer in minor units of the
    /// currency, eg. pennies for GBP, or cents for EUR and USD.
    pub balance: i64,
    /// The ISO 4217 currency code.
    pub currency: Currency,
    /// The amount spent from this account today (considered from approx 4am onwards), in minor
    /// units of the currency.
    pub spend_today: i64,
}

/// Deserializes a string but returns None on empty string.
fn none_for_empty_string<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
    T: Deserialize<'de> + FromStr,
    D: Deserializer<'de>,
{
    // This is a Visitor that forwards string types to T's `FromStr` impl if the string is
    // non-empty The `PhantomData` is to keep the compiler from complaining about T being an unused
    // generic type parameter. We need T in order to know the Value type for the Visitor impl.
    struct NonEmptyString<T>(std::marker::PhantomData<Option<T>>);

    impl<'de, T> Visitor<'de> for NonEmptyString<T>
    where
        T: Deserialize<'de> + FromStr,
    {
        type Value = Option<T>;

        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            formatter.write_str("string")
        }

        fn visit_str<E>(self, value: &str) -> Result<Option<T>, E>
        where
            E: de::Error,
        {
            if value.is_empty() {
                return Ok(None);
            } else {
                let res = FromStr::from_str(value);
                match res {
                    Ok(good) => Ok(Some(good)),
                    // TODO: Find a way to propagate the error.
                    Err(_) => Err(de::Error::custom("could not parse string")),
                }
            }
        }

        fn visit_string<E>(self, value: String) -> Result<Option<T>, E>
        where
            E: de::Error,
        {
            self.visit_str(&value)
        }
    }

    deserializer.deserialize_any(NonEmptyString(std::marker::PhantomData))
}

/// Describes a transaction.
#[derive(Debug, Deserialize)]
pub struct Transaction {
    /// Balance in the account after the transaction.
    pub account_balance: i64,
    /// The amount of the transaction in minor units of currency. For example pennies in the case
    /// of GBP. A negative amount indicates a debit (most card transactions will have a negative
    /// amount).
    pub amount: i64,
    /// The timestamp in when the transaction was created.
    pub created: DateTime<Utc>,
    /// The ISO 4217 currency code.
    pub currency: Currency,
    /// Description of the transaction.
    pub description: String,
    /// The transaction id.
    pub id: TransactionId,
    /// This contains the merchant_id of the merchant that this transaction was made at.
    pub merchant: Option<MerchantId>,
    /// Key-value annotations made for transaction. Metadata is private to your application.
    pub metadata: HashMap<String, String>,
    /// Notes attached to the transaction.
    pub notes: String,
    /// Top-ups to an account are represented as transactions with a positive amount and
    /// is_load = true. Other transactions such as refunds, reversals or chargebacks may have a
    /// positive amount but is_load = false
    pub is_load: bool,
    /// The timestamp in UTC (RFC 3339) at which the transaction settled. In most cases, this
    /// happens 24-48 hours after created. If this field is not present, the transaction is
    /// authorised but not yet “complete”.
    ///
    /// Bug: Even though the Monzo documentation says the field is not present when not authorised,
    /// in practice they send an empty string. See https://github.com/monzo/docs/pull/59.
    #[serde(deserialize_with = "none_for_empty_string")]
    pub settled: Option<DateTime<Utc>>,
    /// The category can be set for each transaction by the user. Over time we learn which merchant
    /// goes in which category and auto-assign the category of a transaction. If the user hasn’t
    /// set a category, we’ll return the default category of the merchant on this transactions.
    /// Top-ups have category mondo. Valid values are general, eating_out, expenses, transport,
    /// cash, bills, entertainment, shopping, holidays, groceries.
    pub category: String,
    /// This is only present on declined transactions! Valid values are INSUFFICIENT_FUNDS,
    /// CARD_INACTIVE, CARD_BLOCKED or OTHER.
    // TODO: Make this an enum?
    pub decline_reason: Option<String>,
}

/// Response to the transaction future if successful.
#[derive(Debug, Deserialize)]
pub struct Transactions {
    /// List of transactions.
    pub transactions: Vec<Transaction>,
}

/// Response to the futures in case of an error.
#[derive(Debug, Deserialize)]
pub struct Error {
    /// The HTTP response code.
    pub code: Option<String>,
    /// Short description of the error.
    // TODO; Maybe make this an enum since the documentation says it can only contain a certain set
    // of values.
    pub error: Option<String>,
    /// Longer description of the error.
    pub error_description: Option<String>,
    /// Additional information.
    pub message: Option<String>,
}

const ACCOUNT_ID: &'static str = "account_id";

/// Errors for this crate using `error_chain`.
pub mod errors {
    error_chain! {
        errors {
            #[doc = "When the Monzo API returns an error response code with more detailed \
            information."]
            BadResponse(statuscode: ::hyper::StatusCode, error: ::Error)
        }
        foreign_links {
            BadJsonResponse(::serde_json::Error)
            #[doc = "When the Monzo API returns invalid or unexpected json content."];
            NetworkError(::hyper::Error) #[doc = "Returned on network failure."];
        }
    }
}

/// The main interface for this crate.
#[derive(Debug)]
pub struct Client {
    client: hyper::client::Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>,
    access_token: String,
    base_url: Url,
}

/// The main interface for this crate.
impl Client {
    /// Creates a new Monzo client.
    pub fn new(handle: &Handle, access_token: &str) -> Client {
        Client::new_with_base_url(
            handle,
            access_token,
            "https://api.monzo.com".parse().unwrap(),
        )
    }

    /// Creates a new Monzo client with another base url. Useful for tests.
    pub fn new_with_base_url(handle: &Handle, access_token: &str, base_url: Url) -> Client {
        Client {
            client: ::hyper::Client::configure()
                .connector(::hyper_tls::HttpsConnector::new(4, handle).unwrap())
                .build(handle),
            access_token: access_token.into(),
            base_url: base_url,
        }
    }

    fn create_request(&self, uri: Uri) -> Request<Body> {
        let mut req: Request<Body> = Request::new(Method::Get, uri);
        req.headers_mut().set(Authorization(
            Bearer { token: self.access_token.clone() },
        ));
        req
    }

    fn make_request<T: 'static, F: 'static>(
        &self,
        uri: Uri,
        response_handler: F,
    ) -> Box<Future<Item = T, Error = errors::Error>>
    where
        F: Fn(Chunk) -> Result<T, errors::Error>,
    {
        let request = self.create_request(uri);
        let response: hyper::client::FutureResponse = self.client.request(request);
        let future = response
            .map_err(|err: hyper::Error| -> errors::Error { err.into() })
            .and_then(|res| {
                let status = res.status();
                res.body()
                    .concat2()
                    .map_err(|err: hyper::Error| err.into())
                    .and_then(move |body: Chunk| {
                        match status {
                            StatusCode::Ok => {}
                            _ => {
                                let error: Error = serde_json::from_slice(&body)?;
                                return Err(errors::ErrorKind::BadResponse(status, error).into());
                            }
                        };
                        response_handler(body)
                    })
            });

        Box::new(future)
    }

    /// Returns a list of accounts owned by the currently authorised user.
    pub fn accounts(&self) -> Box<Future<Item = Accounts, Error = errors::Error>> {
        let mut url = self.base_url.clone();
        url.path_segments_mut().unwrap().push("accounts");
        let uri: Uri = url.into_string().parse().unwrap();

        self.make_request(uri, |body| {
            let a: Accounts = serde_json::from_slice(&body)?;
            Ok(a)
        })
    }

    /// Retrieve information about an account’s balance.
    pub fn balance(
        &self,
        account_id: AccountId,
    ) -> Box<Future<Item = Balance, Error = errors::Error>> {
        let mut url = self.base_url.clone();
        url.path_segments_mut().unwrap().push("balance");
        url.query_pairs_mut().append_pair(ACCOUNT_ID, &account_id);
        let uri: Uri = url.into_string().parse().unwrap();

        self.make_request(uri, |body| {
            let b: Balance = serde_json::from_slice(&body)?;
            Ok(b)
        })
    }

    /// Returns a list of transactions on the user’s account.
    pub fn transactions(
        &self,
        account_id: AccountId,
    ) -> Box<Future<Item = Transactions, Error = errors::Error>> {
        let mut url = self.base_url.clone();
        url.path_segments_mut().unwrap().push("transactions");
        url.query_pairs_mut().append_pair(ACCOUNT_ID, &account_id);
        let uri: Uri = url.into_string().parse().unwrap();

        self.make_request(uri, |body| {
            let t: Transactions = serde_json::from_slice(&body)?;
            Ok(t)
        })
    }
}
<|endoftext|>


<commit_before>extern crate iron;
#[macro_use]
extern crate router;
extern crate staticfile;
extern crate mount;
extern crate urlencoded;
extern crate rustc_serialize;
extern crate html5ever;
extern crate regex;
extern crate uuid;
extern crate chrono;
extern crate bodyparser;
extern crate serde_json;

use std::net::SocketAddrV4;
use std::net::Ipv4Addr;
use std::path::Path;
use iron::prelude::*;
use iron::status;
use iron::mime::Mime;
use staticfile::Static;
use mount::Mount;
use router::{Router};
use urlencoded::UrlEncodedQuery;
use urlencoded::UrlEncodedBody;
use std::str::FromStr;
use uuid::Uuid;

#[macro_use]
extern crate string_cache;
extern crate pink_spider;

use pink_spider::error::Error;
use pink_spider::scraper::extract;
use pink_spider::model::{Model, Track, Playlist, Album, Artist, Entry, Enclosure, Provider, PaginatedCollection};
use rustc_serialize::json::{ToJson, Json};
use pink_spider::youtube;
use pink_spider::soundcloud;
use pink_spider::get_env;

pub fn index_entries(req: &mut Request) -> IronResult<Response> {
    let (page, per_page) = pagination_params(req);
    let entries          = Entry::find(page, per_page);
    let json_obj: Json   = entries.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, application_json(), json_str)))
}

pub fn index<T: Model>(req: &mut Request) -> IronResult<Response> {
    let (page, per_page) = pagination_params(req);
    let enclosures       = T::find(page, per_page);
    let json_obj: Json   = enclosures.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, application_json(), json_str)))
}

pub fn mget<T: Model>(req: &mut Request) -> IronResult<Response> {
    let ids              = try!(params_as_uuid_array(req));
    let items            = try!(T::mget(ids));
    let json_obj: Json   = items.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, application_json(), json_str)))
}

fn params_as_uuid_array(req: &mut Request) -> IronResult<Vec<Uuid>> {
    match req.get::<bodyparser::Json>() {
        Ok(Some(serde_json::Value::Array(ids))) =>
            Ok(ids
               .iter()
               .map(|v| Uuid::parse_str(v.as_str().unwrap_or("")))
               .filter(|opt| opt.is_ok())
               .map(|opt| opt.unwrap())
               .collect()),
        _ => Err(IronError::from(Error::Unprocessable)),
    }
}

pub fn index_by_entry<T: Enclosure>(req: &mut Request) -> IronResult<Response> {
    let ref entry_id = req.extensions.get::<Router>().unwrap().find("entry_id").unwrap();
    let uuid = try!(Uuid::parse_str(entry_id).map_err(|_| Error::Unprocessable));
    let items = T::find_by_entry_id(uuid);
    let col = PaginatedCollection {
        page:     0,
        per_page: items.len() as i64,
        total:    items.len() as i64,
        items:    items,
    };
    let json_obj: Json   = col.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, application_json(), json_str)))
}

pub fn legacy_playlistify(req: &mut Request) -> IronResult<Response> {
    pub fn playlistify2(req: &mut Request) -> Result<Response, Error> {
        let ref params  = try!(req.get_ref::<UrlEncodedQuery>());
        let url         = try!(params.get("url").ok_or(Error::BadRequest));
        let defaults    = &vec!("false".to_string());
        let force_param = params.get("force").unwrap_or(defaults);
        let force       = force_param.len() > 0 && &force_param[0] == "true";
        let mut entry   = try!(find_or_playlistify_entry(&url[0], force));
        entry.tracks    = entry.tracks
            .iter()
            .filter(|t| t.provider == Provider::YouTube || t.provider == Provider::SoundCloud)
            .map(|t| t.clone())
            .collect();
        let json_obj    = entry.to_json() as Json;
        let json_str    = json_obj.to_string() as String;
        Ok(Response::with((status::Ok, application_json(), json_str)))
    }
    playlistify2(req).map_err(|err| IronError::from(err))
}

pub fn playlistify(req: &mut Request) -> IronResult<Response> {
    pub fn playlistify2(req: &mut Request) -> Result<Response, Error> {
        let ref params  = try!(req.get_ref::<UrlEncodedQuery>());
        let url         = try!(params.get("url").ok_or(Error::BadRequest));
        let defaults    = &vec!("false".to_string());
        let force_param = params.get("force").unwrap_or(defaults);
        let force       = force_param.len() > 0 && &force_param[0] == "true";
        let entry       = try!(find_or_playlistify_entry(&url[0], force));
        let json_obj    = entry.to_json() as Json;
        let json_str    = json_obj.to_string() as String;
        Ok(Response::with((status::Ok, application_json(), json_str)))
    }
    playlistify2(req).map_err(|err| IronError::from(err))
}

pub fn find_or_playlistify_entry(url: &str, force: bool) -> Result<Entry, Error> {
    match Entry::find_by_url(url) {
        Ok(entry) => {
            println!("Get entry from database cache: {}", url);
            if force {
                println!("Update entry: {}", url);
                let entry = try!(playlistify_entry(entry));
                Ok(entry)
            } else {
                Ok(entry)
            }
        },
        Err(_) => {
            let entry = try!(Entry::create_by_url(url.to_string()));
            let entry = try!(playlistify_entry(entry));
            println!("Create new entry to database cache: {}", url);
            Ok(entry)
        },
    }
}

pub fn playlistify_entry(entry: Entry) -> Result<Entry, Error> {
    let mut e = entry.clone();
    let product = try!(extract(&entry.url));
    match product.og_obj {
        Some(og_obj) => {
            e.title       = Some(og_obj.title);
            e.description = og_obj.description;
            e.locale      = og_obj.locale;
            e.visual_url  = og_obj.images.first().map(|i| i.url.clone());
        },
        None => (),
    }
    for t in product.tracks {
        let new_track = try!(Track::find_or_create(t.provider, t.identifier.to_string()));
        let mut track = t.clone();
        track.id      = new_track.id;
        try!(track.fetch_detail().save());
        match entry.tracks.iter().find(|&t| t.id == track.id) {
            Some(_) => (),
            None    => try!(e.add_track(track.clone())),
        }
    }
    for p in product.playlists {
        let new_playlist = try!(Playlist::find_or_create(p.provider, p.identifier.to_string()));
        let mut playlist = p.clone();
        playlist.id      = new_playlist.id;
        try!(playlist.save());
        match entry.playlists.iter().find(|&p| p.id == playlist.id) {
            Some(_) => (),
            None    => try!(e.add_playlist(playlist.clone())),
        }
    }
    for a in product.albums {
        let new_album = try!(Album::find_or_create(a.provider, a.identifier.to_string()));
        let mut album = a.clone();
        album.id      = new_album.id;
        try!(album.save());
        match entry.albums.iter().find(|&a| a.id == album.id) {
            Some(_) => (),
            None    => try!(e.add_album(album.clone())),
        }
    }
    try!(e.save());
    Ok(e)
}

pub fn update_track(req: &mut Request) -> IronResult<Response> {
    let mut track = try!(Track::find_by_id(&query_as_string(req, "track_id")));
    match param_as_string(req, "title") {
        Some(title) => track.title = title,
        None        => (),
    }
    match param_as_string(req, "url") {
        Some(url) => track.url = url,
        None      => (),
    }
    let track = match track.provider {
        Provider::YouTube => match youtube::fetch_video(&track.identifier) {
            Ok(video) => track.update_with_yt_video(&video),
            Err(_)    => track.disable(),
        },
        Provider::SoundCloud => match soundcloud::fetch_track(&track.identifier) {
            Ok(sc_track) => track.update_with_sc_track(&sc_track),
            Err(_)       => track.disable(),
        },
        _ => &mut track,
    };
    try!(track.save());
    let res = track.to_json().to_string();
    Ok(Response::with((status::Ok, application_json(), res)))
}

pub fn show<T: Enclosure>(req: &mut Request) -> IronResult<Response> {
    let provider   = req.extensions.get::<Router>().unwrap().find("provider").unwrap();
    let identifier = req.extensions.get::<Router>().unwrap().find("id").unwrap();
    let p = &Provider::new(provider.to_string());
    match T::find_by(p, identifier) {
        Ok(enclosure) => {
            Ok(Response::with((status::Ok,
                               application_json(),
                               enclosure.to_json().to_string())))
        },
        Err(e) => Ok(e.as_response())
    }
}

pub fn show_by_id<T: Model>(req: &mut Request) -> IronResult<Response> {
    let ref id = req.extensions.get::<Router>().unwrap().find("id").unwrap();
    match T::find_by_id(id) {
        Ok(enclosure) => {
            Ok(Response::with((status::Ok,
                               application_json(),
                               enclosure.to_json().to_string())))
        },
        Err(e) => Ok(e.as_response())
    }
}

fn param_as_string(req: &mut Request, key: &str) -> Option<String> {
    match req.get_ref::<UrlEncodedBody>() {
        Ok(ref params) => match params.get(key) {
            Some(val) => Some(val[0].clone()),
            None      => None
        },
        Err(_) => None
    }
}

fn query_as_string(req: &mut Request, key: &str) -> String {
    return req.extensions.get::<Router>().unwrap()
        .find(key).unwrap().to_string();
}

fn pagination_params(req: &mut Request) -> (i64, i64) {
    match req.get_ref::<UrlEncodedQuery>() {
        Ok(ref params) =>
            (params.get("page").unwrap()[0].to_string().parse::<i64>().unwrap(),
             params.get("per_page").unwrap()[0].to_string().parse::<i64>().unwrap()),
        Err(_) =>
            (0, 10)
    }
}

fn application_json() -> Mime {
    Mime::from_str("application/json").ok().unwrap()
}

pub fn main() {
    let path = Path::new("public");
    let mut mount = Mount::new();
    mount.mount("/web/", Static::new(Path::new(path)));
    let router = router!(
        web:                      get  "/*"                        => mount,
        legacy_playlistify:       get  "/playlistify"                    => legacy_playlistify,
        playlistify:              get  "/v1/playlistify"                 => playlistify,
        index_entries:            get  "/v1/entries"                     => index_entries,
        index_artists:            get  "/v1/artists"                     => index::<Artist>,
        mget_artists:             post "/v1/artists/.mget"               => mget::<Artist>,

        show_track_by_id:         get  "/v1/tracks/:id"                  => show_by_id::<Track>,
        show_track:               get  "/v1/tracks/:provider/:id"        => show::<Track>,
        mget_tracks:              post "/v1/tracks/.mget"                => mget::<Track>,
        update_track:             post "/v1/tracks/:track_id"            => update_track,
        index_tracks:             get  "/v1/tracks"                      => index::<Track>,
        index_tracks_by_entry:    get  "/v1/entries/:entry_id/tracks"    => index_by_entry::<Track>,

        show_playlist_by_id:      get  "/v1/playlists/:id"               => show_by_id::<Playlist>,
        show_playlist:            get  "/v1/playlists/:provider/:id"     => show::<Playlist>,
        mget_playlists:           post "/v1/playlists/.mget"             => mget::<Playlist>,
        index_playlists:          get  "/v1/playlists"                   => index::<Playlist>,
        index_playlists_by_entry: get  "/v1/entries/:entry_id/playlists" => index_by_entry::<Playlist>,

        show_album_by_id:         get  "/v1/albums/:id"                  => show_by_id::<Album>,
        show_album:               get  "/v1/albums/:provider/:id"        => show::<Album>,
        mget_albums:              post "/v1/albums/.mget"                => mget::<Album>,
        index_albums:             get  "/v1/albums"                      => index::<Album>,
        index_albums_by_entry:    get  "/v1/entries/:entry_id/albums"    => index_by_entry::<Album>,
    );
    let port_str = match get_env::var("PORT") {
        Some(n) => n,
        None    => "8080".to_string()
    };
    let port: u16 = match port_str.trim().parse() {
        Ok(n) => n,
        Err(_) => {
            println!("Faild to parse port");
            return;
        }
    };
    println!("PORT {}", port_str);
    let ip = Ipv4Addr::new(0, 0, 0, 0);
    Iron::new(router).http(SocketAddrV4::new(ip, port)).unwrap();
}
<commit_msg>Update spotify track post /v1/tracks/:track_id<commit_after>extern crate iron;
#[macro_use]
extern crate router;
extern crate staticfile;
extern crate mount;
extern crate urlencoded;
extern crate rustc_serialize;
extern crate html5ever;
extern crate regex;
extern crate uuid;
extern crate chrono;
extern crate bodyparser;
extern crate serde_json;

use std::net::SocketAddrV4;
use std::net::Ipv4Addr;
use std::path::Path;
use iron::prelude::*;
use iron::status;
use iron::mime::Mime;
use staticfile::Static;
use mount::Mount;
use router::{Router};
use urlencoded::UrlEncodedQuery;
use urlencoded::UrlEncodedBody;
use std::str::FromStr;
use uuid::Uuid;

#[macro_use]
extern crate string_cache;
extern crate pink_spider;

use pink_spider::error::Error;
use pink_spider::scraper::extract;
use pink_spider::model::{Model, Track, Playlist, Album, Artist, Entry, Enclosure, Provider, PaginatedCollection};
use rustc_serialize::json::{ToJson, Json};
use pink_spider::youtube;
use pink_spider::soundcloud;
use pink_spider::spotify;
use pink_spider::get_env;

pub fn index_entries(req: &mut Request) -> IronResult<Response> {
    let (page, per_page) = pagination_params(req);
    let entries          = Entry::find(page, per_page);
    let json_obj: Json   = entries.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, application_json(), json_str)))
}

pub fn index<T: Model>(req: &mut Request) -> IronResult<Response> {
    let (page, per_page) = pagination_params(req);
    let enclosures       = T::find(page, per_page);
    let json_obj: Json   = enclosures.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, application_json(), json_str)))
}

pub fn mget<T: Model>(req: &mut Request) -> IronResult<Response> {
    let ids              = try!(params_as_uuid_array(req));
    let items            = try!(T::mget(ids));
    let json_obj: Json   = items.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, application_json(), json_str)))
}

fn params_as_uuid_array(req: &mut Request) -> IronResult<Vec<Uuid>> {
    match req.get::<bodyparser::Json>() {
        Ok(Some(serde_json::Value::Array(ids))) =>
            Ok(ids
               .iter()
               .map(|v| Uuid::parse_str(v.as_str().unwrap_or("")))
               .filter(|opt| opt.is_ok())
               .map(|opt| opt.unwrap())
               .collect()),
        _ => Err(IronError::from(Error::Unprocessable)),
    }
}

pub fn index_by_entry<T: Enclosure>(req: &mut Request) -> IronResult<Response> {
    let ref entry_id = req.extensions.get::<Router>().unwrap().find("entry_id").unwrap();
    let uuid = try!(Uuid::parse_str(entry_id).map_err(|_| Error::Unprocessable));
    let items = T::find_by_entry_id(uuid);
    let col = PaginatedCollection {
        page:     0,
        per_page: items.len() as i64,
        total:    items.len() as i64,
        items:    items,
    };
    let json_obj: Json   = col.to_json();
    let json_str: String = json_obj.to_string();
    Ok(Response::with((status::Ok, application_json(), json_str)))
}

pub fn legacy_playlistify(req: &mut Request) -> IronResult<Response> {
    pub fn playlistify2(req: &mut Request) -> Result<Response, Error> {
        let ref params  = try!(req.get_ref::<UrlEncodedQuery>());
        let url         = try!(params.get("url").ok_or(Error::BadRequest));
        let defaults    = &vec!("false".to_string());
        let force_param = params.get("force").unwrap_or(defaults);
        let force       = force_param.len() > 0 && &force_param[0] == "true";
        let mut entry   = try!(find_or_playlistify_entry(&url[0], force));
        entry.tracks    = entry.tracks
            .iter()
            .filter(|t| t.provider == Provider::YouTube || t.provider == Provider::SoundCloud)
            .map(|t| t.clone())
            .collect();
        let json_obj    = entry.to_json() as Json;
        let json_str    = json_obj.to_string() as String;
        Ok(Response::with((status::Ok, application_json(), json_str)))
    }
    playlistify2(req).map_err(|err| IronError::from(err))
}

pub fn playlistify(req: &mut Request) -> IronResult<Response> {
    pub fn playlistify2(req: &mut Request) -> Result<Response, Error> {
        let ref params  = try!(req.get_ref::<UrlEncodedQuery>());
        let url         = try!(params.get("url").ok_or(Error::BadRequest));
        let defaults    = &vec!("false".to_string());
        let force_param = params.get("force").unwrap_or(defaults);
        let force       = force_param.len() > 0 && &force_param[0] == "true";
        let entry       = try!(find_or_playlistify_entry(&url[0], force));
        let json_obj    = entry.to_json() as Json;
        let json_str    = json_obj.to_string() as String;
        Ok(Response::with((status::Ok, application_json(), json_str)))
    }
    playlistify2(req).map_err(|err| IronError::from(err))
}

pub fn find_or_playlistify_entry(url: &str, force: bool) -> Result<Entry, Error> {
    match Entry::find_by_url(url) {
        Ok(entry) => {
            println!("Get entry from database cache: {}", url);
            if force {
                println!("Update entry: {}", url);
                let entry = try!(playlistify_entry(entry));
                Ok(entry)
            } else {
                Ok(entry)
            }
        },
        Err(_) => {
            let entry = try!(Entry::create_by_url(url.to_string()));
            let entry = try!(playlistify_entry(entry));
            println!("Create new entry to database cache: {}", url);
            Ok(entry)
        },
    }
}

pub fn playlistify_entry(entry: Entry) -> Result<Entry, Error> {
    let mut e = entry.clone();
    let product = try!(extract(&entry.url));
    match product.og_obj {
        Some(og_obj) => {
            e.title       = Some(og_obj.title);
            e.description = og_obj.description;
            e.locale      = og_obj.locale;
            e.visual_url  = og_obj.images.first().map(|i| i.url.clone());
        },
        None => (),
    }
    for t in product.tracks {
        let new_track = try!(Track::find_or_create(t.provider, t.identifier.to_string()));
        let mut track = t.clone();
        track.id      = new_track.id;
        try!(track.fetch_detail().save());
        match entry.tracks.iter().find(|&t| t.id == track.id) {
            Some(_) => (),
            None    => try!(e.add_track(track.clone())),
        }
    }
    for p in product.playlists {
        let new_playlist = try!(Playlist::find_or_create(p.provider, p.identifier.to_string()));
        let mut playlist = p.clone();
        playlist.id      = new_playlist.id;
        try!(playlist.save());
        match entry.playlists.iter().find(|&p| p.id == playlist.id) {
            Some(_) => (),
            None    => try!(e.add_playlist(playlist.clone())),
        }
    }
    for a in product.albums {
        let new_album = try!(Album::find_or_create(a.provider, a.identifier.to_string()));
        let mut album = a.clone();
        album.id      = new_album.id;
        try!(album.save());
        match entry.albums.iter().find(|&a| a.id == album.id) {
            Some(_) => (),
            None    => try!(e.add_album(album.clone())),
        }
    }
    try!(e.save());
    Ok(e)
}

pub fn update_track(req: &mut Request) -> IronResult<Response> {
    let mut track = try!(Track::find_by_id(&query_as_string(req, "track_id")));
    match param_as_string(req, "title") {
        Some(title) => track.title = title,
        None        => (),
    }
    match param_as_string(req, "url") {
        Some(url) => track.url = url,
        None      => (),
    }
    let track = match track.provider {
        Provider::YouTube => match youtube::fetch_video(&track.identifier) {
            Ok(video) => track.update_with_yt_video(&video),
            Err(_)    => track.disable(),
        },
        Provider::SoundCloud => match soundcloud::fetch_track(&track.identifier) {
            Ok(sc_track) => track.update_with_sc_track(&sc_track),
            Err(_)       => track.disable(),
        },
        Provider::Spotify => match spotify::fetch_track(&track.identifier) {
            Ok(sp_track) => track.update_with_sp_track(&sp_track),
            Err(_)       => track.disable(),
        },
        _ => &mut track,
    };
    try!(track.save());
    let res = track.to_json().to_string();
    Ok(Response::with((status::Ok, application_json(), res)))
}

pub fn show<T: Enclosure>(req: &mut Request) -> IronResult<Response> {
    let provider   = req.extensions.get::<Router>().unwrap().find("provider").unwrap();
    let identifier = req.extensions.get::<Router>().unwrap().find("id").unwrap();
    let p = &Provider::new(provider.to_string());
    match T::find_by(p, identifier) {
        Ok(enclosure) => {
            Ok(Response::with((status::Ok,
                               application_json(),
                               enclosure.to_json().to_string())))
        },
        Err(e) => Ok(e.as_response())
    }
}

pub fn show_by_id<T: Model>(req: &mut Request) -> IronResult<Response> {
    let ref id = req.extensions.get::<Router>().unwrap().find("id").unwrap();
    match T::find_by_id(id) {
        Ok(enclosure) => {
            Ok(Response::with((status::Ok,
                               application_json(),
                               enclosure.to_json().to_string())))
        },
        Err(e) => Ok(e.as_response())
    }
}

fn param_as_string(req: &mut Request, key: &str) -> Option<String> {
    match req.get_ref::<UrlEncodedBody>() {
        Ok(ref params) => match params.get(key) {
            Some(val) => Some(val[0].clone()),
            None      => None
        },
        Err(_) => None
    }
}

fn query_as_string(req: &mut Request, key: &str) -> String {
    return req.extensions.get::<Router>().unwrap()
        .find(key).unwrap().to_string();
}

fn pagination_params(req: &mut Request) -> (i64, i64) {
    match req.get_ref::<UrlEncodedQuery>() {
        Ok(ref params) =>
            (params.get("page").unwrap()[0].to_string().parse::<i64>().unwrap(),
             params.get("per_page").unwrap()[0].to_string().parse::<i64>().unwrap()),
        Err(_) =>
            (0, 10)
    }
}

fn application_json() -> Mime {
    Mime::from_str("application/json").ok().unwrap()
}

pub fn main() {
    let path = Path::new("public");
    let mut mount = Mount::new();
    mount.mount("/web/", Static::new(Path::new(path)));
    let router = router!(
        web:                      get  "/*"                        => mount,
        legacy_playlistify:       get  "/playlistify"                    => legacy_playlistify,
        playlistify:              get  "/v1/playlistify"                 => playlistify,
        index_entries:            get  "/v1/entries"                     => index_entries,
        index_artists:            get  "/v1/artists"                     => index::<Artist>,
        mget_artists:             post "/v1/artists/.mget"               => mget::<Artist>,

        show_track_by_id:         get  "/v1/tracks/:id"                  => show_by_id::<Track>,
        show_track:               get  "/v1/tracks/:provider/:id"        => show::<Track>,
        mget_tracks:              post "/v1/tracks/.mget"                => mget::<Track>,
        update_track:             post "/v1/tracks/:track_id"            => update_track,
        index_tracks:             get  "/v1/tracks"                      => index::<Track>,
        index_tracks_by_entry:    get  "/v1/entries/:entry_id/tracks"    => index_by_entry::<Track>,

        show_playlist_by_id:      get  "/v1/playlists/:id"               => show_by_id::<Playlist>,
        show_playlist:            get  "/v1/playlists/:provider/:id"     => show::<Playlist>,
        mget_playlists:           post "/v1/playlists/.mget"             => mget::<Playlist>,
        index_playlists:          get  "/v1/playlists"                   => index::<Playlist>,
        index_playlists_by_entry: get  "/v1/entries/:entry_id/playlists" => index_by_entry::<Playlist>,

        show_album_by_id:         get  "/v1/albums/:id"                  => show_by_id::<Album>,
        show_album:               get  "/v1/albums/:provider/:id"        => show::<Album>,
        mget_albums:              post "/v1/albums/.mget"                => mget::<Album>,
        index_albums:             get  "/v1/albums"                      => index::<Album>,
        index_albums_by_entry:    get  "/v1/entries/:entry_id/albums"    => index_by_entry::<Album>,
    );
    let port_str = match get_env::var("PORT") {
        Some(n) => n,
        None    => "8080".to_string()
    };
    let port: u16 = match port_str.trim().parse() {
        Ok(n) => n,
        Err(_) => {
            println!("Faild to parse port");
            return;
        }
    };
    println!("PORT {}", port_str);
    let ip = Ipv4Addr::new(0, 0, 0, 0);
    Iron::new(router).http(SocketAddrV4::new(ip, port)).unwrap();
}
<|endoftext|>


<commit_before>use std::io::Write;
use std::net::TcpListener;
use std::thread;

fn main() {
    let listener = TcpListener::bind(("0.0.0.0", 1883)).unwrap();
    for stream in listener.incoming() {
        thread::spawn(|| {
            let mut stream = stream.unwrap();
            stream.write(b"Hello world\r\n").unwrap();
        });
    }
}
<commit_msg>Now passes connect.feature<commit_after>use std::io::Write;
use std::net::TcpListener;
use std::thread;

fn main() {
    let listener = TcpListener::bind(("0.0.0.0", 1883)).unwrap();
    for stream in listener.incoming() {
        thread::spawn(|| {
            let mut stream = stream.unwrap();
            stream.write(&[32, 2, 0, 0]).unwrap();
        });
    }
}
<|endoftext|>


<commit_before>#![feature(plugin_registrar, rustc_private)]

extern crate rustc;
extern crate rustc_plugin;
extern crate syntax;

use rustc_plugin::Registry;
use syntax::ast::Ident;
use syntax::tokenstream::{TokenStream, TokenTree};
use syntax::codemap::Span;
use syntax::ext::base::{ExtCtxt, MacResult};
use syntax::parse::token::{DelimToken, Token};

mod parser_any_macro;

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("interpolate_idents", interpolate_idents);
}

fn interpolate_idents<'a>(cx: &'a mut ExtCtxt,
              _: Span,
              tts: &[TokenTree]) -> Box<MacResult + 'a> {
    fn concat_idents(tts: TokenStream, delim: DelimToken) -> Option<TokenTree> {
        match delim {
            DelimToken::Bracket => {
                let mut new_ident = String::new();
                let mut new_span: Option<Span> = None;

                for token in tts.trees() {
                    match token {
                        TokenTree::Token(ref span, Token::Ident(ref ident, _)) => {
                            match new_span {
                                Some(ref mut s) => { *s = s.with_hi(span.hi()); },
                                None => { new_span = Some(span.clone()); },
                            }
                            new_ident.push_str(&ident.name.as_str());
                        },
                        _ => return None,
                    }
                }

                match new_span {
                    Some(s) => {
                        let new_ident = Ident::from_str(&new_ident[..]);
                        Some(TokenTree::Token(s, Token::from_ast_ident(new_ident)))
                    },
                    None => None
                }
            },
            _ => None,
        }
    }

    fn map_tts(tts: TokenStream) -> TokenStream {
        // Ignore brackets preceded by a pound symbol (or a pound and an exclamation mark), so as
        // to allow attributes.
        let mut is_prev_pound = false;

        tts.trees().map(|t| {
            if is_prev_pound {
                is_prev_pound = false;
                return t.clone();
            }

            if let TokenTree::Token(_, Token::Pound) = t {
                is_prev_pound = true;
            }

            match t {
                TokenTree::Delimited(s, d) => {
                    match concat_idents(d.tts.clone().into(), d.delim) {
                        Some(t) => t,
                        None => {
                            TokenTree::Delimited(s, syntax::tokenstream::Delimited {
                                delim: d.delim,
                                tts: map_tts(d.tts.into()).into(),
                            })
                        },
                    }
                },
                TokenTree::Token(..) => t.clone(),
            }
        }).collect()
    }

    let tts: TokenStream = map_tts(tts.iter().cloned().collect());
    let tts: Vec<TokenTree> = tts.trees().collect();
    let parser = cx.new_parser_from_tts(&*tts);
    Box::new(parser_any_macro::ParserAnyMacro::new(parser)) as Box<MacResult>
}
<commit_msg>Update `syntax::codemap` to `syntax::source_map`<commit_after>#![feature(plugin_registrar, rustc_private)]

extern crate rustc;
extern crate rustc_plugin;
extern crate syntax;

use rustc_plugin::Registry;
use syntax::ast::Ident;
use syntax::tokenstream::{TokenStream, TokenTree};
use syntax::source_map::Span;
use syntax::ext::base::{ExtCtxt, MacResult};
use syntax::parse::token::{DelimToken, Token};

mod parser_any_macro;

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("interpolate_idents", interpolate_idents);
}

fn interpolate_idents<'a>(cx: &'a mut ExtCtxt,
              _: Span,
              tts: &[TokenTree]) -> Box<MacResult + 'a> {
    fn concat_idents(tts: TokenStream, delim: DelimToken) -> Option<TokenTree> {
        match delim {
            DelimToken::Bracket => {
                let mut new_ident = String::new();
                let mut new_span: Option<Span> = None;

                for token in tts.trees() {
                    match token {
                        TokenTree::Token(ref span, Token::Ident(ref ident, _)) => {
                            match new_span {
                                Some(ref mut s) => { *s = s.with_hi(span.hi()); },
                                None => { new_span = Some(span.clone()); },
                            }
                            new_ident.push_str(&ident.name.as_str());
                        },
                        _ => return None,
                    }
                }

                match new_span {
                    Some(s) => {
                        let new_ident = Ident::from_str(&new_ident[..]);
                        Some(TokenTree::Token(s, Token::from_ast_ident(new_ident)))
                    },
                    None => None
                }
            },
            _ => None,
        }
    }

    fn map_tts(tts: TokenStream) -> TokenStream {
        // Ignore brackets preceded by a pound symbol (or a pound and an exclamation mark), so as
        // to allow attributes.
        let mut is_prev_pound = false;

        tts.trees().map(|t| {
            if is_prev_pound {
                is_prev_pound = false;
                return t.clone();
            }

            if let TokenTree::Token(_, Token::Pound) = t {
                is_prev_pound = true;
            }

            match t {
                TokenTree::Delimited(s, d) => {
                    match concat_idents(d.tts.clone().into(), d.delim) {
                        Some(t) => t,
                        None => {
                            TokenTree::Delimited(s, syntax::tokenstream::Delimited {
                                delim: d.delim,
                                tts: map_tts(d.tts.into()).into(),
                            })
                        },
                    }
                },
                TokenTree::Token(..) => t.clone(),
            }
        }).collect()
    }

    let tts: TokenStream = map_tts(tts.iter().cloned().collect());
    let tts: Vec<TokenTree> = tts.trees().collect();
    let parser = cx.new_parser_from_tts(&*tts);
    Box::new(parser_any_macro::ParserAnyMacro::new(parser)) as Box<MacResult>
}
<|endoftext|>


<commit_before>extern crate num_cpus;

use std::env;
use std::io::{self, Write, StderrLock};
use std::process::{Command, exit};
use std::thread::{self, JoinHandle};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

/* TODO: Functionality can be increased to accept the following syntaxes from GNU Parallel:
 - Stdin support is currently missing.
 - {N}, {N.}, etc.
 - parallel command {1} {2} {3} ::: 1 2 3 ::: 4 5 6 ::: 7 8 9
 - paralllel command ::: a b c :::+ 1 2 3 ::: d e f :::+ 4 5 6
*/

fn main() {
    let stderr = io::stderr();
    let mut ncores = num_cpus::get();
    let mut command = String::new();
    let mut arg_tokens = Vec::new();
    let mut inputs = Vec::new();

    // Let's collect all parameters that we need from the program's arguments.
    // If an error is returned, this will handle that error as efficiently as possible.
    if let Err(why) = parse_arguments(&mut ncores, &mut command, &mut arg_tokens, &mut inputs) {
        let mut stderr = stderr.lock();
        let _ = stderr.write(b"parallel: parsing error: ");
        match why {
            ParseErr::JobsNaN(value) => {
                let _ = stderr.write(b"jobs parameter, '");
                let _ = stderr.write(value.as_bytes());
                let _ = stderr.write(b"', is not a number.\n");
            },
            _ => {
                let message: &[u8] = match why {
                    ParseErr::InputVarsNotDefined => b"input variables were not defined.\n",
                    ParseErr::JobsNoValue         => b"no jobs parameter was defined.\n",
                    _ => unreachable!()
                };
                let _ = stderr.write(message);
            }
        };
        exit(1);
    }

    // If no command was given, then the inputs are actually commands themselves.
    let input_is_command = command.is_empty();

    // It will be useful to know the number of inputs, to know when to quit.
    let num_inputs = inputs.len();

    // Stores the next input to be processed
    let shared_counter = Arc::new(AtomicUsize::new(0));

    // We will share the same list of inputs with each thread.
    let shared_input = Arc::new(inputs);

    // First we will create as many threads as `ncores` specifies.
    // The `threads` vector will contain the thread handles needed to
    // know when to quit the program.
    let mut threads: Vec<JoinHandle<()>> = Vec::with_capacity(ncores);
    for slot in 1..ncores+1 {
        // The command that each input variable will be sent to.
        let command = command.clone();
        // The arguments for the command.
        let argument_tokens = arg_tokens.clone();
        // Allow the thread to gain access to the list of inputs.
        let input = shared_input.clone();
        // Allow the thread to access the current command counter
        let counter = shared_counter.clone();
        // Allow the thread to know when it's time to stop.
        let num_inputs = num_inputs;

        // The actual thread where the work will happen on incoming data.
        let handle: JoinHandle<()> = thread::spawn(move || {
            let slot_number = slot;
            let stderr = io::stderr();
            loop {
                // Obtain the Nth item and it's job ID from the list of inputs.
                let (input_var, job_id) = {
                    // Atomically increment the counter
                    let old_counter = counter.fetch_add(1, Ordering::SeqCst);
                    if old_counter >= num_inputs {
                        break
                    } else {
                        let input_var = &input[old_counter];
                        let job_id = old_counter + 1;
                        (input_var, job_id)
                    }
                };

                if input_is_command {
                    // The inputs are actually the commands.
                    let mut iterator = input_var.split_whitespace();
                    let actual_command = iterator.next().unwrap();
                    let args = iterator.collect::<Vec<&str>>();
                    if let Err(_) = Command::new(actual_command).args(&args).status() {
                        let mut stderr = stderr.lock();
                        let _ = stderr.write(b"parallel: command error: ");
                        let _ = stderr.write(input_var.as_bytes());
                        let _ = stderr.write(b"\n");
                    }
                } else {
                    // Build a command by merging the command template with the input,
                    // and then execute that command.
                    let (slot, job) = (slot_number.to_string(), job_id.to_string());
                    if let Err(cmd_err) = cmd_builder(input_var, &command, &argument_tokens,
                        &slot, &job)
                    {
                        let mut stderr = stderr.lock();
                        cmd_err.handle(&mut stderr);
                    }
                }
            }
        });

        // After the thread has been created, add the important pieces needed by the
        // main thread to the `threads` vector.
        threads.push(handle);
    }

    for thread in threads.into_iter() { thread.join().unwrap(); }
}

enum CommandErr {
    Failed(String, Vec<String>)
}

impl CommandErr {
    fn handle(self, stderr: &mut StderrLock) {
        let _ = stderr.write(b"parallel: command error: ");
        match self {
            CommandErr::Failed(command, arguments) => {
                let _ = stderr.write(command.as_bytes());
                for arg in &arguments {
                    let _ = stderr.write(b" ");
                    let _ = stderr.write(arg.as_bytes());
                }
                let _ = stderr.write(b"\n");
            }
        }
    }
}

#[derive(Clone, PartialEq)]
enum Token {
    Character(char),
    Placeholder,
    RemoveExtension,
    Basename,
    Dirname,
    BaseAndExt,
    Slot,
    Job
}

fn tokenize(template: &str) -> Vec<Token> {
    let mut matching = false;
    let mut tokens = Vec::new();
    let mut pattern = String::new();
    for character in template.chars() {
        match (character, matching) {
            ('{', false) => matching = true,
            ('}', true)  => {
                matching = false;
                if pattern.is_empty() {
                    tokens.push(Token::Placeholder);
                } else {
                    match match_token(&pattern) {
                        Some(token) => tokens.push(token),
                        None => {
                            tokens.push(Token::Character('{'));
                            for character in pattern.chars() {
                                tokens.push(Token::Character(character));
                            }
                            tokens.push(Token::Character('}'));
                        }
                    }
                    pattern.clear();
                }
            }
            (_, false)  => tokens.push(Token::Character(character)),
            (_, true) => pattern.push(character)
        }
    }
    tokens
}

fn match_token(pattern: &str) -> Option<Token> {
    match pattern {
        "."  => Some(Token::RemoveExtension),
        "#"  => Some(Token::Job),
        "%"  => Some(Token::Slot),
        "/"  => Some(Token::Basename),
        "//" => Some(Token::Dirname),
        "/." => Some(Token::BaseAndExt),
        _    => None
    }

}

/// Builds the command and executes it
fn cmd_builder(input: &str, command: &str, arg_tokens: &[Token], slot_id: &str, job_id: &str)
    -> Result<(), CommandErr>
{
    let mut arguments = String::new();
    for arg in arg_tokens {
        match *arg {
            Token::Character(arg) => arguments.push(arg),
            Token::Basename => arguments.push_str(input),
            Token::BaseAndExt => arguments.push_str(basename(remove_extension(input))),
            Token::Dirname => arguments.push_str(dirname(input)),
            Token::Job => arguments.push_str(job_id),
            Token::Placeholder => arguments.push_str(input),
            Token::RemoveExtension => arguments.push_str(remove_extension(input)),
            Token::Slot => arguments.push_str(slot_id)
        }
    }

    let placeholder_exists = arg_tokens.iter().any(|ref x| {
        x == &&Token::BaseAndExt || x == &&Token::Basename || x == &&Token::Dirname ||
        x == &&Token::Job || x == &&Token::Placeholder || x == &&Token::RemoveExtension ||
        x == &&Token::Slot
    });

    if !placeholder_exists {
        arguments.push_str(input);
    }

    let arguments = arguments.split_whitespace().map(|x| x.to_owned()).collect::<Vec<String>>();

    if let Err(_) = Command::new(&command).args(&arguments).status() {
        return Err(CommandErr::Failed(String::from(command), arguments));
    }
    Ok(())
}

/// Removes the extension of a given input
fn remove_extension(input: &str) -> &str {
    let mut index = 0;
    for (id, character) in input.chars().enumerate() {
        if character == '.' { index = id; }
    }
    if index == 0 { input } else { &input[0..index] }
}

fn basename(input: &str) -> &str {
    let mut index = 0;
    for (id, character) in input.chars().enumerate() {
        if character == '/' { index = id; }
    }
    if index == 0 { input } else { &input[index+1..] }
}

fn dirname(input: &str) -> &str {
    let mut index = 0;
    for (id, character) in input.chars().enumerate() {
        if character == '/' { index = id; }
    }
    if index == 0 { input } else { &input[0..index] }
}

enum ParseErr {
    JobsNaN(String),
    JobsNoValue,
    InputVarsNotDefined,
}

// Parses input arguments and stores their values into their associated variabless.
fn parse_arguments(ncores: &mut usize, command: &mut String, arg_tokens: &mut Vec<Token>,
    input_variables: &mut Vec<String>) -> Result<(), ParseErr>
{
    let mut parsing_arguments = true;
    let mut command_is_set    = false;
    let mut raw_args = env::args().skip(1).peekable();
    let mut comm = String::new();
    while let Some(argument) = raw_args.next() {
        if parsing_arguments {
            match argument.as_str() {
                // Defines the number of jobs to run in parallel.
                "-j"  => {
                    match raw_args.peek() {
                        Some(val) => match val.parse::<usize>() {
                            Ok(val) => *ncores = val,
                            Err(_)  => return Err(ParseErr::JobsNaN(val.clone()))
                        },
                        None => return Err(ParseErr::JobsNoValue)
                    }
                    let _ = raw_args.next();
                },
                // Arguments after `:::` are input values.
                ":::" => parsing_arguments = false,
                _ => {
                    if command_is_set {
                        comm.push(' ');
                        comm.push_str(&argument);
                    } else {
                        comm.push_str(&argument);
                        command_is_set = true;
                    }

                }
            }
        } else {
            input_variables.push(argument);
        }
    }

    // This will fill in command and argument information needed by the threads.
    // If there is a space in the argument, then the command has arguments
    match comm.chars().position(|x| x == ' ') {
        Some(pos) => {
            *command    = String::from(&comm[0..pos]);
            *arg_tokens = tokenize(&comm[pos+1..]);
        },
        None => *command = comm
    }

    if input_variables.is_empty() { return Err(ParseErr::InputVarsNotDefined) }
    Ok(())
}
<commit_msg>Fix basename placeholder handling<commit_after>extern crate num_cpus;

use std::env;
use std::io::{self, Write, StderrLock};
use std::process::{Command, exit};
use std::thread::{self, JoinHandle};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

/* TODO: Functionality can be increased to accept the following syntaxes from GNU Parallel:
 - Stdin support is currently missing.
 - {N}, {N.}, etc.
 - parallel command {1} {2} {3} ::: 1 2 3 ::: 4 5 6 ::: 7 8 9
 - paralllel command ::: a b c :::+ 1 2 3 ::: d e f :::+ 4 5 6
*/

fn main() {
    let stderr = io::stderr();
    let mut ncores = num_cpus::get();
    let mut command = String::new();
    let mut arg_tokens = Vec::new();
    let mut inputs = Vec::new();

    // Let's collect all parameters that we need from the program's arguments.
    // If an error is returned, this will handle that error as efficiently as possible.
    if let Err(why) = parse_arguments(&mut ncores, &mut command, &mut arg_tokens, &mut inputs) {
        let mut stderr = stderr.lock();
        let _ = stderr.write(b"parallel: parsing error: ");
        match why {
            ParseErr::JobsNaN(value) => {
                let _ = stderr.write(b"jobs parameter, '");
                let _ = stderr.write(value.as_bytes());
                let _ = stderr.write(b"', is not a number.\n");
            },
            _ => {
                let message: &[u8] = match why {
                    ParseErr::InputVarsNotDefined => b"input variables were not defined.\n",
                    ParseErr::JobsNoValue         => b"no jobs parameter was defined.\n",
                    _ => unreachable!()
                };
                let _ = stderr.write(message);
            }
        };
        exit(1);
    }

    // If no command was given, then the inputs are actually commands themselves.
    let input_is_command = command.is_empty();

    // It will be useful to know the number of inputs, to know when to quit.
    let num_inputs = inputs.len();

    // Stores the next input to be processed
    let shared_counter = Arc::new(AtomicUsize::new(0));

    // We will share the same list of inputs with each thread.
    let shared_input = Arc::new(inputs);

    // First we will create as many threads as `ncores` specifies.
    // The `threads` vector will contain the thread handles needed to
    // know when to quit the program.
    let mut threads: Vec<JoinHandle<()>> = Vec::with_capacity(ncores);
    for slot in 1..ncores+1 {
        // The command that each input variable will be sent to.
        let command = command.clone();
        // The arguments for the command.
        let argument_tokens = arg_tokens.clone();
        // Allow the thread to gain access to the list of inputs.
        let input = shared_input.clone();
        // Allow the thread to access the current command counter
        let counter = shared_counter.clone();
        // Allow the thread to know when it's time to stop.
        let num_inputs = num_inputs;

        // The actual thread where the work will happen on incoming data.
        let handle: JoinHandle<()> = thread::spawn(move || {
            let slot_number = slot;
            let stderr = io::stderr();
            loop {
                // Obtain the Nth item and it's job ID from the list of inputs.
                let (input_var, job_id) = {
                    // Atomically increment the counter
                    let old_counter = counter.fetch_add(1, Ordering::SeqCst);
                    if old_counter >= num_inputs {
                        break
                    } else {
                        let input_var = &input[old_counter];
                        let job_id = old_counter + 1;
                        (input_var, job_id)
                    }
                };

                if input_is_command {
                    // The inputs are actually the commands.
                    let mut iterator = input_var.split_whitespace();
                    let actual_command = iterator.next().unwrap();
                    let args = iterator.collect::<Vec<&str>>();
                    if let Err(_) = Command::new(actual_command).args(&args).status() {
                        let mut stderr = stderr.lock();
                        let _ = stderr.write(b"parallel: command error: ");
                        let _ = stderr.write(input_var.as_bytes());
                        let _ = stderr.write(b"\n");
                    }
                } else {
                    // Build a command by merging the command template with the input,
                    // and then execute that command.
                    let (slot, job) = (slot_number.to_string(), job_id.to_string());
                    if let Err(cmd_err) = cmd_builder(input_var, &command, &argument_tokens,
                        &slot, &job)
                    {
                        let mut stderr = stderr.lock();
                        cmd_err.handle(&mut stderr);
                    }
                }
            }
        });

        // After the thread has been created, add the important pieces needed by the
        // main thread to the `threads` vector.
        threads.push(handle);
    }

    for thread in threads.into_iter() { thread.join().unwrap(); }
}

enum CommandErr {
    Failed(String, Vec<String>)
}

impl CommandErr {
    fn handle(self, stderr: &mut StderrLock) {
        let _ = stderr.write(b"parallel: command error: ");
        match self {
            CommandErr::Failed(command, arguments) => {
                let _ = stderr.write(command.as_bytes());
                for arg in &arguments {
                    let _ = stderr.write(b" ");
                    let _ = stderr.write(arg.as_bytes());
                }
                let _ = stderr.write(b"\n");
            }
        }
    }
}

#[derive(Clone, PartialEq)]
enum Token {
    Character(char),
    Placeholder,
    RemoveExtension,
    Basename,
    Dirname,
    BaseAndExt,
    Slot,
    Job
}

fn tokenize(template: &str) -> Vec<Token> {
    let mut matching = false;
    let mut tokens = Vec::new();
    let mut pattern = String::new();
    for character in template.chars() {
        match (character, matching) {
            ('{', false) => matching = true,
            ('}', true)  => {
                matching = false;
                if pattern.is_empty() {
                    tokens.push(Token::Placeholder);
                } else {
                    match match_token(&pattern) {
                        Some(token) => tokens.push(token),
                        None => {
                            tokens.push(Token::Character('{'));
                            for character in pattern.chars() {
                                tokens.push(Token::Character(character));
                            }
                            tokens.push(Token::Character('}'));
                        }
                    }
                    pattern.clear();
                }
            }
            (_, false)  => tokens.push(Token::Character(character)),
            (_, true) => pattern.push(character)
        }
    }
    tokens
}

fn match_token(pattern: &str) -> Option<Token> {
    match pattern {
        "."  => Some(Token::RemoveExtension),
        "#"  => Some(Token::Job),
        "%"  => Some(Token::Slot),
        "/"  => Some(Token::Basename),
        "//" => Some(Token::Dirname),
        "/." => Some(Token::BaseAndExt),
        _    => None
    }

}

/// Builds the command and executes it
fn cmd_builder(input: &str, command: &str, arg_tokens: &[Token], slot_id: &str, job_id: &str)
    -> Result<(), CommandErr>
{
    let mut arguments = String::new();
    for arg in arg_tokens {
        match *arg {
            Token::Character(arg) => arguments.push(arg),
            Token::Basename => arguments.push_str(basename(input)),
            Token::BaseAndExt => arguments.push_str(basename(remove_extension(input))),
            Token::Dirname => arguments.push_str(dirname(input)),
            Token::Job => arguments.push_str(job_id),
            Token::Placeholder => arguments.push_str(input),
            Token::RemoveExtension => arguments.push_str(remove_extension(input)),
            Token::Slot => arguments.push_str(slot_id)
        }
    }

    let placeholder_exists = arg_tokens.iter().any(|ref x| {
        x == &&Token::BaseAndExt || x == &&Token::Basename || x == &&Token::Dirname ||
        x == &&Token::Job || x == &&Token::Placeholder || x == &&Token::RemoveExtension ||
        x == &&Token::Slot
    });

    if !placeholder_exists {
        arguments.push_str(input);
    }

    let arguments = arguments.split_whitespace().map(|x| x.to_owned()).collect::<Vec<String>>();

    if let Err(_) = Command::new(&command).args(&arguments).status() {
        return Err(CommandErr::Failed(String::from(command), arguments));
    }
    Ok(())
}

/// Removes the extension of a given input
fn remove_extension(input: &str) -> &str {
    let mut index = 0;
    for (id, character) in input.chars().enumerate() {
        if character == '.' { index = id; }
    }
    if index == 0 { input } else { &input[0..index] }
}

fn basename(input: &str) -> &str {
    let mut index = 0;
    for (id, character) in input.chars().enumerate() {
        if character == '/' { index = id; }
    }
    if index == 0 { input } else { &input[index+1..] }
}

fn dirname(input: &str) -> &str {
    let mut index = 0;
    for (id, character) in input.chars().enumerate() {
        if character == '/' { index = id; }
    }
    if index == 0 { input } else { &input[0..index] }
}

enum ParseErr {
    JobsNaN(String),
    JobsNoValue,
    InputVarsNotDefined,
}

// Parses input arguments and stores their values into their associated variabless.
fn parse_arguments(ncores: &mut usize, command: &mut String, arg_tokens: &mut Vec<Token>,
    input_variables: &mut Vec<String>) -> Result<(), ParseErr>
{
    let mut parsing_arguments = true;
    let mut command_is_set    = false;
    let mut raw_args = env::args().skip(1).peekable();
    let mut comm = String::new();
    while let Some(argument) = raw_args.next() {
        if parsing_arguments {
            match argument.as_str() {
                // Defines the number of jobs to run in parallel.
                "-j"  => {
                    match raw_args.peek() {
                        Some(val) => match val.parse::<usize>() {
                            Ok(val) => *ncores = val,
                            Err(_)  => return Err(ParseErr::JobsNaN(val.clone()))
                        },
                        None => return Err(ParseErr::JobsNoValue)
                    }
                    let _ = raw_args.next();
                },
                // Arguments after `:::` are input values.
                ":::" => parsing_arguments = false,
                _ => {
                    if command_is_set {
                        comm.push(' ');
                        comm.push_str(&argument);
                    } else {
                        comm.push_str(&argument);
                        command_is_set = true;
                    }

                }
            }
        } else {
            input_variables.push(argument);
        }
    }

    // This will fill in command and argument information needed by the threads.
    // If there is a space in the argument, then the command has arguments
    match comm.chars().position(|x| x == ' ') {
        Some(pos) => {
            *command    = String::from(&comm[0..pos]);
            *arg_tokens = tokenize(&comm[pos+1..]);
        },
        None => *command = comm
    }

    if input_variables.is_empty() { return Err(ParseErr::InputVarsNotDefined) }
    Ok(())
}
<|endoftext|>