<commit_before>#![allow(unused_imports, dead_code)]
use std::{io, time};
use std::net::{SocketAddr, Shutdown};
use std::collections::VecDeque;
use std::time::Duration;

use actix::{fut, Actor, ActorFuture, Arbiter, Context,
            Handler, Message, ActorResponse, Supervised};
use actix::registry::ArbiterService;
use actix::fut::WrapFuture;
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};

use http::{Uri, HttpTryFrom, Error as HttpError};
use futures::{Async, Future, Poll};
use tokio_core::reactor::Timeout;
use tokio_core::net::{TcpStream, TcpStreamNew};
use tokio_io::{AsyncRead, AsyncWrite};

#[cfg(feature="alpn")]
use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode, Error as OpensslError};
#[cfg(feature="alpn")]
use tokio_openssl::SslConnectorExt;

use HAS_OPENSSL;
use server::IoStream;

#[derive(Debug)]
/// `Connect` type represents message that can be send to `ClientConnector`
/// with connection request.
pub struct Connect(pub Uri);

impl Connect {
    /// Create `Connect` message for specified `Uri`
    pub fn new<U>(uri: U) -> Result<Connect, HttpError> where Uri: HttpTryFrom<U> {
        Ok(Connect(Uri::try_from(uri).map_err(|e| e.into())?))
    }
}

impl Message for Connect {
    type Result = Result<Connection, ClientConnectorError>;
}

/// A set of errors that can occur during connecting to a http host
#[derive(Fail, Debug)]
pub enum ClientConnectorError {
    /// Invalid url
    #[fail(display="Invalid url")]
    InvalidUrl,

    /// SSL feature is not enabled
    #[fail(display="SSL is not supported")]
    SslIsNotSupported,

    /// SSL error
    #[cfg(feature="alpn")]
    #[fail(display="{}", _0)]
    SslError(OpensslError),

    /// Connection error
    #[fail(display = "{}", _0)]
    Connector(ConnectorError),

    /// Connecting took too long
    #[fail(display = "Timeout out while establishing connection")]
    Timeout,

    /// Connector has been disconnected
    #[fail(display = "Internal error: connector has been disconnected")]
    Disconnected,

    /// Connection io error
    #[fail(display = "{}", _0)]
    IoError(io::Error),
}

impl From<ConnectorError> for ClientConnectorError {
    fn from(err: ConnectorError) -> ClientConnectorError {
        ClientConnectorError::Connector(err)
    }
}

pub struct ClientConnector {
    #[cfg(feature="alpn")]
    connector: SslConnector,
}

impl Actor for ClientConnector {
    type Context = Context<ClientConnector>;
}

impl Supervised for ClientConnector {}

impl ArbiterService for ClientConnector {}

impl Default for ClientConnector {
    fn default() -> ClientConnector {
        #[cfg(feature="alpn")]
        {
            let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
            builder.set_verify(SslVerifyMode::NONE);
            ClientConnector {
                connector: builder.build()
            }
        }

        #[cfg(not(feature="alpn"))]
        ClientConnector {}
    }
}

impl ClientConnector {

    #[cfg(feature="alpn")]
    /// Create `ClientConnector` actor with custom `SslConnector` instance.
    ///
    /// By default `ClientConnector` uses very simple ssl configuration.
    /// With `with_connector` method it is possible to use custom `SslConnector`
    /// object.
    ///
    /// ```rust
    /// # #![cfg(feature="alpn")]
    /// # extern crate actix;
    /// # extern crate actix_web;
    /// # extern crate futures;
    /// # use futures::Future;
    /// # use std::io::Write;
    /// extern crate openssl;
    /// use actix::prelude::*;
    /// use actix_web::client::{Connect, ClientConnector};
    ///
    /// use openssl::ssl::{SslMethod, SslConnector};
    ///
    /// fn main() {
    ///     let sys = System::new("test");
    ///
    ///     // Start `ClientConnector` with custom `SslConnector`
    ///     let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build();
    ///     let conn: Address<_> = ClientConnector::with_connector(ssl_conn).start();
    ///
    ///     Arbiter::handle().spawn({
    ///         conn.call_fut(
    ///             Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
    ///                 .map_err(|_| ())
    ///                 .and_then(|res| {
    ///                     if let Ok(mut stream) = res {
    ///                         stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
    ///                     }
    /// #                   Arbiter::system().send(actix::msgs::SystemExit(0));
    ///                     Ok(())
    ///                 })
    ///     });
    ///
    ///     sys.run();
    /// }
    /// ```
    pub fn with_connector(connector: SslConnector) -> ClientConnector {
        ClientConnector {
            connector: connector
        }
    }
}

impl Handler<Connect> for ClientConnector {
    type Result = ActorResponse<ClientConnector, Connection, ClientConnectorError>;

    fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result {
        let uri = &msg.0;

        // host name is required
        if uri.host().is_none() {
            return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl))
        }

        // supported protocols
        let proto = match uri.scheme_part() {
            Some(scheme) => match Protocol::from(scheme.as_str()) {
                Some(proto) => proto,
                None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)),
            },
            None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)),
        };

        // check ssl availability
        if proto.is_secure() && !HAS_OPENSSL { //&& !HAS_TLS {
            return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported))
        }

        let host = uri.host().unwrap().to_owned();
        let port = uri.port().unwrap_or_else(|| proto.port());

        ActorResponse::async(
            Connector::from_registry()
                .call(ResolveConnect::host_and_port(&host, port))
                .into_actor(self)
                .map_err(|_, _, _| ClientConnectorError::Disconnected)
                .and_then(move |res, _act, _| {
                    #[cfg(feature="alpn")]
                    match res {
                        Err(err) => fut::Either::B(fut::err(err.into())),
                        Ok(stream) => {
                            if proto.is_secure() {
                                fut::Either::A(
                                    _act.connector.connect_async(&host, stream)
                                        .map_err(|e| ClientConnectorError::SslError(e))
                                        .map(|stream| Connection{stream: Box::new(stream)})
                                        .into_actor(_act))
                            } else {
                                fut::Either::B(fut::ok(Connection{stream: Box::new(stream)}))
                            }
                        }
                    }

                    #[cfg(not(feature="alpn"))]
                    match res {
                        Err(err) => fut::err(err.into()),
                        Ok(stream) => {
                            if proto.is_secure() {
                                fut::err(ClientConnectorError::SslIsNotSupported)
                            } else {
                                fut::ok(Connection{stream: Box::new(stream)})
                            }
                        }
                    }
                }))
    }
}

#[derive(PartialEq, Hash, Debug, Clone, Copy)]
enum Protocol {
    Http,
    Https,
    Ws,
    Wss,
}

impl Protocol {
    fn from(s: &str) -> Option<Protocol> {
        match s {
            "http" => Some(Protocol::Http),
            "https" => Some(Protocol::Https),
            "ws" => Some(Protocol::Ws),
            "wss" => Some(Protocol::Wss),
            _ => None,
        }
    }

    fn is_secure(&self) -> bool {
        match *self {
            Protocol::Https | Protocol::Wss => true,
            _ => false,
        }
    }

    fn port(&self) -> u16 {
        match *self {
            Protocol::Http | Protocol::Ws => 80,
            Protocol::Https | Protocol::Wss => 443
        }
    }
}

pub struct Connection {
    stream: Box<IoStream>,
}

impl Connection {
    pub fn stream(&mut self) -> &mut IoStream {
        &mut *self.stream
    }
}

impl IoStream for Connection {
    fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
        IoStream::shutdown(&mut *self.stream, how)
    }

    #[inline]
    fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
        IoStream::set_nodelay(&mut *self.stream, nodelay)
    }

    #[inline]
    fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
        IoStream::set_linger(&mut *self.stream, dur)
    }
}

impl io::Read for Connection {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.stream.read(buf)
    }
}

impl AsyncRead for Connection {}

impl io::Write for Connection {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.stream.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.stream.flush()
    }
}

impl AsyncWrite for Connection {
    fn shutdown(&mut self) -> Poll<(), io::Error> {
        self.stream.shutdown()
    }
}
<commit_msg>fix doc test<commit_after>#![allow(unused_imports, dead_code)]
use std::{io, time};
use std::net::{SocketAddr, Shutdown};
use std::collections::VecDeque;
use std::time::Duration;

use actix::{fut, Actor, ActorFuture, Arbiter, Context,
            Handler, Message, ActorResponse, Supervised};
use actix::registry::ArbiterService;
use actix::fut::WrapFuture;
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};

use http::{Uri, HttpTryFrom, Error as HttpError};
use futures::{Async, Future, Poll};
use tokio_core::reactor::Timeout;
use tokio_core::net::{TcpStream, TcpStreamNew};
use tokio_io::{AsyncRead, AsyncWrite};

#[cfg(feature="alpn")]
use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode, Error as OpensslError};
#[cfg(feature="alpn")]
use tokio_openssl::SslConnectorExt;

use HAS_OPENSSL;
use server::IoStream;

#[derive(Debug)]
/// `Connect` type represents message that can be send to `ClientConnector`
/// with connection request.
pub struct Connect(pub Uri);

impl Connect {
    /// Create `Connect` message for specified `Uri`
    pub fn new<U>(uri: U) -> Result<Connect, HttpError> where Uri: HttpTryFrom<U> {
        Ok(Connect(Uri::try_from(uri).map_err(|e| e.into())?))
    }
}

impl Message for Connect {
    type Result = Result<Connection, ClientConnectorError>;
}

/// A set of errors that can occur during connecting to a http host
#[derive(Fail, Debug)]
pub enum ClientConnectorError {
    /// Invalid url
    #[fail(display="Invalid url")]
    InvalidUrl,

    /// SSL feature is not enabled
    #[fail(display="SSL is not supported")]
    SslIsNotSupported,

    /// SSL error
    #[cfg(feature="alpn")]
    #[fail(display="{}", _0)]
    SslError(OpensslError),

    /// Connection error
    #[fail(display = "{}", _0)]
    Connector(ConnectorError),

    /// Connecting took too long
    #[fail(display = "Timeout out while establishing connection")]
    Timeout,

    /// Connector has been disconnected
    #[fail(display = "Internal error: connector has been disconnected")]
    Disconnected,

    /// Connection io error
    #[fail(display = "{}", _0)]
    IoError(io::Error),
}

impl From<ConnectorError> for ClientConnectorError {
    fn from(err: ConnectorError) -> ClientConnectorError {
        ClientConnectorError::Connector(err)
    }
}

pub struct ClientConnector {
    #[cfg(feature="alpn")]
    connector: SslConnector,
}

impl Actor for ClientConnector {
    type Context = Context<ClientConnector>;
}

impl Supervised for ClientConnector {}

impl ArbiterService for ClientConnector {}

impl Default for ClientConnector {
    fn default() -> ClientConnector {
        #[cfg(feature="alpn")]
        {
            let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
            builder.set_verify(SslVerifyMode::NONE);
            ClientConnector {
                connector: builder.build()
            }
        }

        #[cfg(not(feature="alpn"))]
        ClientConnector {}
    }
}

impl ClientConnector {

    #[cfg(feature="alpn")]
    /// Create `ClientConnector` actor with custom `SslConnector` instance.
    ///
    /// By default `ClientConnector` uses very simple ssl configuration.
    /// With `with_connector` method it is possible to use custom `SslConnector`
    /// object.
    ///
    /// ```rust
    /// # #![cfg(feature="alpn")]
    /// # extern crate actix;
    /// # extern crate actix_web;
    /// # extern crate futures;
    /// # use futures::Future;
    /// # use std::io::Write;
    /// extern crate openssl;
    /// use actix::prelude::*;
    /// use actix_web::client::{Connect, ClientConnector};
    ///
    /// use openssl::ssl::{SslMethod, SslConnector};
    ///
    /// fn main() {
    ///     let sys = System::new("test");
    ///
    ///     // Start `ClientConnector` with custom `SslConnector`
    ///     let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build();
    ///     let conn: Address<_> = ClientConnector::with_connector(ssl_conn).start();
    ///
    ///     Arbiter::handle().spawn({
    ///         conn.call(
    ///             Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
    ///                 .map_err(|_| ())
    ///                 .and_then(|res| {
    ///                     if let Ok(mut stream) = res {
    ///                         stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
    ///                     }
    /// #                   Arbiter::system().send(actix::msgs::SystemExit(0));
    ///                     Ok(())
    ///                 })
    ///     });
    ///
    ///     sys.run();
    /// }
    /// ```
    pub fn with_connector(connector: SslConnector) -> ClientConnector {
        ClientConnector {
            connector: connector
        }
    }
}

impl Handler<Connect> for ClientConnector {
    type Result = ActorResponse<ClientConnector, Connection, ClientConnectorError>;

    fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result {
        let uri = &msg.0;

        // host name is required
        if uri.host().is_none() {
            return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl))
        }

        // supported protocols
        let proto = match uri.scheme_part() {
            Some(scheme) => match Protocol::from(scheme.as_str()) {
                Some(proto) => proto,
                None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)),
            },
            None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)),
        };

        // check ssl availability
        if proto.is_secure() && !HAS_OPENSSL { //&& !HAS_TLS {
            return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported))
        }

        let host = uri.host().unwrap().to_owned();
        let port = uri.port().unwrap_or_else(|| proto.port());

        ActorResponse::async(
            Connector::from_registry()
                .call(ResolveConnect::host_and_port(&host, port))
                .into_actor(self)
                .map_err(|_, _, _| ClientConnectorError::Disconnected)
                .and_then(move |res, _act, _| {
                    #[cfg(feature="alpn")]
                    match res {
                        Err(err) => fut::Either::B(fut::err(err.into())),
                        Ok(stream) => {
                            if proto.is_secure() {
                                fut::Either::A(
                                    _act.connector.connect_async(&host, stream)
                                        .map_err(|e| ClientConnectorError::SslError(e))
                                        .map(|stream| Connection{stream: Box::new(stream)})
                                        .into_actor(_act))
                            } else {
                                fut::Either::B(fut::ok(Connection{stream: Box::new(stream)}))
                            }
                        }
                    }

                    #[cfg(not(feature="alpn"))]
                    match res {
                        Err(err) => fut::err(err.into()),
                        Ok(stream) => {
                            if proto.is_secure() {
                                fut::err(ClientConnectorError::SslIsNotSupported)
                            } else {
                                fut::ok(Connection{stream: Box::new(stream)})
                            }
                        }
                    }
                }))
    }
}

#[derive(PartialEq, Hash, Debug, Clone, Copy)]
enum Protocol {
    Http,
    Https,
    Ws,
    Wss,
}

impl Protocol {
    fn from(s: &str) -> Option<Protocol> {
        match s {
            "http" => Some(Protocol::Http),
            "https" => Some(Protocol::Https),
            "ws" => Some(Protocol::Ws),
            "wss" => Some(Protocol::Wss),
            _ => None,
        }
    }

    fn is_secure(&self) -> bool {
        match *self {
            Protocol::Https | Protocol::Wss => true,
            _ => false,
        }
    }

    fn port(&self) -> u16 {
        match *self {
            Protocol::Http | Protocol::Ws => 80,
            Protocol::Https | Protocol::Wss => 443
        }
    }
}

pub struct Connection {
    stream: Box<IoStream>,
}

impl Connection {
    pub fn stream(&mut self) -> &mut IoStream {
        &mut *self.stream
    }
}

impl IoStream for Connection {
    fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
        IoStream::shutdown(&mut *self.stream, how)
    }

    #[inline]
    fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
        IoStream::set_nodelay(&mut *self.stream, nodelay)
    }

    #[inline]
    fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
        IoStream::set_linger(&mut *self.stream, dur)
    }
}

impl io::Read for Connection {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.stream.read(buf)
    }
}

impl AsyncRead for Connection {}

impl io::Write for Connection {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.stream.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.stream.flush()
    }
}

impl AsyncWrite for Connection {
    fn shutdown(&mut self) -> Poll<(), io::Error> {
        self.stream.shutdown()
    }
}
<|endoftext|>


<commit_before>use std::io::Read;
use std::collections::HashMap;

use types::WeChatResult;
use client::WeChatClient;
use session::SessionStore;
use client::response::Media;

#[derive(Debug, Clone)]
pub struct WeChatMedia<'a, T: SessionStore + 'a> {
    client: &'a WeChatClient<T>,
}

impl<'a, T: SessionStore> WeChatMedia<'a, T> {
    #[inline]
    pub fn new(client: &'a WeChatClient<T>) -> WeChatMedia<'a, T> {
        WeChatMedia {
            client: client,
        }
    }

    pub fn upload<S: AsRef<str>, R: Read>(&self, media_type: S, media: &mut R) -> WeChatResult<Media> {
        let mut files = HashMap::new();
        files.insert("media".to_owned(), media);
        let res = try!(
            self.client.upload_file("media/upload", vec![("type", media_type.as_ref())], &mut files)
        );
        let media_id = &res["media_id"];
        let media_id = media_id.as_string().unwrap();
        let created_at = &res["created_at"];
        let created_at = created_at.as_u64().unwrap();
        let media_type_res = &res["type"];
        let media_type_res = media_type_res.as_string().unwrap();
        Ok(Media {
            media_type: media_type_res.to_owned(),
            media_id: media_id.to_owned(),
            created_at: created_at,
        })
    }
}
<commit_msg>Add media.get API, still needs some changes<commit_after>use std::io::Read;
use std::collections::HashMap;

use hyper::method::Method;
use hyper::client::Response;
use rustc_serialize::json::{Json, Object};

use types::WeChatResult;
use errors::WeChatError;
use client::WeChatClient;
use session::SessionStore;
use client::response::Media;

#[derive(Debug, Clone)]
pub struct WeChatMedia<'a, T: SessionStore + 'a> {
    client: &'a WeChatClient<T>,
}

impl<'a, T: SessionStore> WeChatMedia<'a, T> {
    #[inline]
    pub fn new(client: &'a WeChatClient<T>) -> WeChatMedia<'a, T> {
        WeChatMedia {
            client: client,
        }
    }

    pub fn upload<S: AsRef<str>, R: Read>(&self, media_type: S, media: &mut R) -> WeChatResult<Media> {
        let mut files = HashMap::new();
        files.insert("media".to_owned(), media);
        let res = try!(
            self.client.upload_file("media/upload", vec![("type", media_type.as_ref())], &mut files)
        );
        let media_id = &res["media_id"];
        let media_id = media_id.as_string().unwrap();
        let created_at = &res["created_at"];
        let created_at = created_at.as_u64().unwrap();
        let media_type_res = &res["type"];
        let media_type_res = media_type_res.as_string().unwrap();
        Ok(Media {
            media_type: media_type_res.to_owned(),
            media_id: media_id.to_owned(),
            created_at: created_at,
        })
    }

    pub fn get<S: AsRef<str>>(&self, media_id: S) -> WeChatResult<Response> {
        let mut res = try!(
            self.client.request(
                Method::Get,
                "media/get",
                vec![("media_id", media_id.as_ref())],
                &Object::new()
            )
        );
        match Json::from_reader(&mut res) {
            Ok(obj) => {
                match obj.find("errcode") {
                    Some(code) => {
                        let errcode = code.as_i64().unwrap();
                        let errmsg = match obj.find("errmsg") {
                            Some(msg) => {
                                msg.as_string().unwrap()
                            },
                            None => { "" }
                        };
                        return Err(WeChatError::ClientError {
                            errcode: errcode as i32,
                            errmsg: errmsg.to_owned()
                        });
                    },
                    None => {},
                }
            },
            Err(_) => {}
        }
        Ok(res)
    }
}
<|endoftext|>


<commit_before>use specs::*;

use amq_protocol_types::*;
use serde_json::Value;

/* Modified version of AMQProtocolDefinition to handle deserialization */
#[derive(Debug, Deserialize)]
pub struct _AMQProtocolDefinition {
    name:          String,
    #[serde(rename="major-version")]
    major_version: u8,
    #[serde(rename="minor-version")]
    minor_version: u8,
    revision:      u8,
    port:          u32,
    copyright:     Vec<String>,
    domains:       Vec<_AMQPDomain>,
    constants:     Vec<AMQPConstant>,
    classes:       Vec<_AMQPClass>,
}

impl _AMQProtocolDefinition {
    pub fn to_specs(self) -> AMQProtocolDefinition {
        AMQProtocolDefinition {
            name:          self.name,
            major_version: self.major_version,
            minor_version: self.minor_version,
            revision:      self.revision,
            port:          self.port,
            copyright:     self.copyright,
            domains:       self.domains.iter().map(|domain| domain.to_specs()).collect(),
            constants:     self.constants,
            classes:       self.classes.iter().map(|klass| klass.to_specs()).collect(),
        }
    }
}

/* Defined as a two-elems array in the spec */
#[derive(Debug, Deserialize)]
struct _AMQPDomain(String, _AMQPType);

impl _AMQPDomain {
    fn to_specs(&self) -> AMQPDomain {
        AMQPDomain {
            name:      self.0.clone(),
            amqp_type: self.1.to_specs(),
        }
    }
}

/* Subset of AMQPType used in specs for deserialization */
#[derive(Debug, Deserialize)]
enum _AMQPType {
    #[serde(rename="bit")]
    Bit,
    #[serde(rename="octet")]
    Octet,
    #[serde(rename="short")]
    Short,
    #[serde(rename="long")]
    Long,
    #[serde(rename="longlong")]
    LongLong,
    #[serde(rename="shortstr")]
    ShortStr,
    #[serde(rename="longstr")]
    LongStr,
    #[serde(rename="table")]
    Table,
    #[serde(rename="timestamp")]
    Timestamp,
}

impl _AMQPType {
    fn to_specs(&self) -> AMQPType {
        match *self {
            _AMQPType::Bit       => AMQPType::Boolean,
            _AMQPType::Octet     => AMQPType::ShortShortInt,
            _AMQPType::Short     => AMQPType::ShortInt,
            _AMQPType::Long      => AMQPType::LongInt,
            _AMQPType::LongLong  => AMQPType::LongLongInt,
            _AMQPType::ShortStr  => AMQPType::ShortString,
            _AMQPType::LongStr   => AMQPType::LongString,
            _AMQPType::Table     => AMQPType::FieldTable,
            _AMQPType::Timestamp => AMQPType::Timestamp,
        }
    }
}

#[derive(Debug, Deserialize)]
struct _AMQPClass {
    id:         u8,
    methods:    Vec<_AMQPMethod>,
    name:       String,
    properties: Option<Vec<_AMQPProperty>>,
}

impl _AMQPClass {
    fn to_specs(&self) -> AMQPClass {
        AMQPClass {
            id:         self.id,
            methods:    self.methods.iter().map(|method| method.to_specs()).collect(),
            name:       self.name.clone(),
            properties: match self.properties {
                Some(ref properties) => Some(properties.iter().map(|prop| prop.to_specs()).collect()),
                None                 => None,
            },
        }
    }
}

#[derive(Debug, Deserialize)]
struct _AMQPMethod {
    id:          u8,
    arguments:   Vec<_AMQPArgument>,
    name:        String,
    synchronous: Option<bool>,
}

impl _AMQPMethod {
    fn to_specs(&self) -> AMQPMethod {
        AMQPMethod {
            id:          self.id,
            arguments:   self.arguments.iter().map(|arg| arg.to_specs()).collect(),
            name:        self.name.clone(),
            synchronous: self.synchronous,
        }
    }
}

#[derive(Debug, Deserialize)]
struct _AMQPArgument {
    #[serde(rename="type")]
    amqp_type:     Option<_AMQPType>,
    name:          String,
    #[serde(rename="default-value")]
    default_value: Option<Value>,
    domain:        Option<String>,
}

impl _AMQPArgument {
    fn to_specs(&self) -> AMQPArgument {
        AMQPArgument {
            amqp_type:     match self.amqp_type {
                Some(ref amqp_type) => Some(amqp_type.to_specs()),
                None                => None,
            },
            name:          self.name.clone(),
            default_value: self.default_value.clone(),
            domain:        self.domain.clone(),
        }
    }
}

#[derive(Debug, Deserialize)]
struct _AMQPProperty {
    #[serde(rename="type")]
    amqp_type: _AMQPType,
    name:      String,
}

impl _AMQPProperty {
    fn to_specs(&self) -> AMQPProperty {
        AMQPProperty {
            amqp_type: self.amqp_type.to_specs(),
            name:      self.name.clone(),
        }
    }
}
<commit_msg>codegen: add TODO<commit_after>use specs::*;

use amq_protocol_types::*;
use serde_json::Value;

/* Modified version of AMQProtocolDefinition to handle deserialization */
#[derive(Debug, Deserialize)]
pub struct _AMQProtocolDefinition {
    name:          String,
    #[serde(rename="major-version")]
    major_version: u8,
    #[serde(rename="minor-version")]
    minor_version: u8,
    revision:      u8,
    port:          u32,
    copyright:     Vec<String>,
    domains:       Vec<_AMQPDomain>,
    constants:     Vec<AMQPConstant>,
    classes:       Vec<_AMQPClass>,
}

impl _AMQProtocolDefinition {
    pub fn to_specs(self) -> AMQProtocolDefinition {
        AMQProtocolDefinition {
            name:          self.name,
            major_version: self.major_version,
            minor_version: self.minor_version,
            revision:      self.revision,
            port:          self.port,
            copyright:     self.copyright,
            domains:       self.domains.iter().map(|domain| domain.to_specs()).collect(),
            constants:     self.constants,
            classes:       self.classes.iter().map(|klass| klass.to_specs()).collect(),
        }
    }
}

/* Defined as a two-elems array in the spec */
#[derive(Debug, Deserialize)]
struct _AMQPDomain(String, _AMQPType);

impl _AMQPDomain {
    fn to_specs(&self) -> AMQPDomain {
        AMQPDomain {
            name:      self.0.clone(),
            amqp_type: self.1.to_specs(),
        }
    }
}

/* Subset of AMQPType used in specs for deserialization */
#[derive(Debug, Deserialize)]
enum _AMQPType {
    #[serde(rename="bit")]
    Bit,
    #[serde(rename="octet")]
    Octet,
    #[serde(rename="short")]
    Short,
    #[serde(rename="long")]
    Long,
    #[serde(rename="longlong")]
    LongLong,
    #[serde(rename="shortstr")]
    ShortStr,
    #[serde(rename="longstr")]
    LongStr,
    #[serde(rename="table")]
    Table,
    #[serde(rename="timestamp")]
    Timestamp,
}

impl _AMQPType {
    fn to_specs(&self) -> AMQPType {
        match *self {
            _AMQPType::Bit       => AMQPType::Boolean,
            /* FIXME: Should those be Int or UInt ? */
            _AMQPType::Octet     => AMQPType::ShortShortInt,
            _AMQPType::Short     => AMQPType::ShortInt,
            _AMQPType::Long      => AMQPType::LongInt,
            _AMQPType::LongLong  => AMQPType::LongLongInt,
            _AMQPType::ShortStr  => AMQPType::ShortString,
            _AMQPType::LongStr   => AMQPType::LongString,
            _AMQPType::Table     => AMQPType::FieldTable,
            _AMQPType::Timestamp => AMQPType::Timestamp,
        }
    }
}

#[derive(Debug, Deserialize)]
struct _AMQPClass {
    id:         u8,
    methods:    Vec<_AMQPMethod>,
    name:       String,
    properties: Option<Vec<_AMQPProperty>>,
}

impl _AMQPClass {
    fn to_specs(&self) -> AMQPClass {
        AMQPClass {
            id:         self.id,
            methods:    self.methods.iter().map(|method| method.to_specs()).collect(),
            name:       self.name.clone(),
            properties: match self.properties {
                Some(ref properties) => Some(properties.iter().map(|prop| prop.to_specs()).collect()),
                None                 => None,
            },
        }
    }
}

#[derive(Debug, Deserialize)]
struct _AMQPMethod {
    id:          u8,
    arguments:   Vec<_AMQPArgument>,
    name:        String,
    synchronous: Option<bool>,
}

impl _AMQPMethod {
    fn to_specs(&self) -> AMQPMethod {
        AMQPMethod {
            id:          self.id,
            arguments:   self.arguments.iter().map(|arg| arg.to_specs()).collect(),
            name:        self.name.clone(),
            synchronous: self.synchronous,
        }
    }
}

#[derive(Debug, Deserialize)]
struct _AMQPArgument {
    #[serde(rename="type")]
    amqp_type:     Option<_AMQPType>,
    name:          String,
    #[serde(rename="default-value")]
    default_value: Option<Value>,
    domain:        Option<String>,
}

impl _AMQPArgument {
    fn to_specs(&self) -> AMQPArgument {
        AMQPArgument {
            amqp_type:     match self.amqp_type {
                Some(ref amqp_type) => Some(amqp_type.to_specs()),
                None                => None,
            },
            name:          self.name.clone(),
            default_value: self.default_value.clone(),
            domain:        self.domain.clone(),
        }
    }
}

#[derive(Debug, Deserialize)]
struct _AMQPProperty {
    #[serde(rename="type")]
    amqp_type: _AMQPType,
    name:      String,
}

impl _AMQPProperty {
    fn to_specs(&self) -> AMQPProperty {
        AMQPProperty {
            amqp_type: self.amqp_type.to_specs(),
            name:      self.name.clone(),
        }
    }
}
<|endoftext|>


<commit_before>use marksman_escape::Escape;
use regex::Regex;
use std::collections::{BTreeMap, HashMap};
use std::fmt::Write as _;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::iter;
use std::str;

use crate::snake_to_camel;

const PG_TYPE_DAT: &str = include_str!("pg_type.dat");
const PG_RANGE_DAT: &str = include_str!("pg_range.dat");

struct Type {
    name: String,
    variant: String,
    ident: String,
    kind: String,
    element: u32,
    doc: String,
}

pub fn build() {
    let mut file = BufWriter::new(File::create("../postgres-types/src/type_gen.rs").unwrap());
    let types = parse_types();

    make_header(&mut file);
    make_enum(&mut file, &types);
    make_impl(&mut file, &types);
    make_consts(&mut file, &types);
}

struct DatParser<'a> {
    it: iter::Peekable<str::CharIndices<'a>>,
    s: &'a str,
}

impl<'a> DatParser<'a> {
    fn new(s: &'a str) -> DatParser<'a> {
        DatParser {
            it: s.char_indices().peekable(),
            s,
        }
    }

    fn parse_array(&mut self) -> Vec<HashMap<String, String>> {
        self.eat('[');
        let mut vec = vec![];
        while !self.try_eat(']') {
            let object = self.parse_object();
            vec.push(object);
        }
        self.eof();

        vec
    }

    fn parse_object(&mut self) -> HashMap<String, String> {
        let mut object = HashMap::new();

        self.eat('{');
        loop {
            let key = self.parse_ident();
            self.eat('=');
            self.eat('>');
            let value = self.parse_string();
            object.insert(key, value);
            if !self.try_eat(',') {
                break;
            }
        }
        self.eat('}');
        self.eat(',');

        object
    }

    fn parse_ident(&mut self) -> String {
        self.skip_ws();

        let start = match self.it.peek() {
            Some((i, _)) => *i,
            None => return "".to_string(),
        };

        loop {
            match self.it.peek() {
                Some((_, 'a'..='z')) | Some((_, '_')) => {
                    self.it.next();
                }
                Some((i, _)) => return self.s[start..*i].to_string(),
                None => return self.s[start..].to_string(),
            }
        }
    }

    fn parse_string(&mut self) -> String {
        self.skip_ws();

        let mut s = String::new();

        self.eat('\'');
        loop {
            match self.it.next() {
                Some((_, '\'')) => return s,
                Some((_, '\\')) => {
                    let (_, ch) = self.it.next().expect("unexpected eof");
                    s.push(ch);
                }
                Some((_, ch)) => s.push(ch),
                None => panic!("unexpected eof"),
            }
        }
    }

    fn eat(&mut self, target: char) {
        self.skip_ws();

        match self.it.next() {
            Some((_, ch)) if ch == target => {}
            Some((_, ch)) => panic!("expected {} but got {}", target, ch),
            None => panic!("expected {} but got eof", target),
        }
    }

    fn try_eat(&mut self, target: char) -> bool {
        if self.peek(target) {
            self.eat(target);
            true
        } else {
            false
        }
    }

    fn peek(&mut self, target: char) -> bool {
        self.skip_ws();

        match self.it.peek() {
            Some((_, ch)) if *ch == target => true,
            _ => false,
        }
    }

    fn eof(&mut self) {
        self.skip_ws();
        if let Some((_, ch)) = self.it.next() {
            panic!("expected eof but got {}", ch);
        }
    }

    fn skip_ws(&mut self) {
        loop {
            match self.it.peek() {
                Some(&(_, '#')) => self.skip_to('\n'),
                Some(&(_, '\n')) | Some(&(_, ' ')) | Some(&(_, '\t')) => {
                    self.it.next();
                }
                _ => break,
            }
        }
    }

    fn skip_to(&mut self, target: char) {
        for (_, ch) in &mut self.it {
            if ch == target {
                break;
            }
        }
    }
}

fn parse_types() -> BTreeMap<u32, Type> {
    let raw_types = DatParser::new(PG_TYPE_DAT).parse_array();
    let raw_ranges = DatParser::new(PG_RANGE_DAT).parse_array();

    let oids_by_name = raw_types
        .iter()
        .map(|m| (m["typname"].clone(), m["oid"].parse::<u32>().unwrap()))
        .collect::<HashMap<_, _>>();

    let range_elements = raw_ranges
        .iter()
        .map(|m| {
            (
                oids_by_name[&*m["rngtypid"]],
                oids_by_name[&*m["rngsubtype"]],
            )
        })
        .collect::<HashMap<_, _>>();

    let range_vector_re = Regex::new("(range|vector)$").unwrap();
    let array_re = Regex::new("^_(.*)").unwrap();

    let mut types = BTreeMap::new();

    for raw_type in raw_types {
        let oid = raw_type["oid"].parse::<u32>().unwrap();

        let name = raw_type["typname"].clone();

        let ident = range_vector_re.replace(&name, "_$1");
        let ident = array_re.replace(&ident, "${1}_array");
        let variant = snake_to_camel(&ident);
        let ident = ident.to_ascii_uppercase();

        let kind = raw_type["typcategory"].clone();

        // we need to be able to pull composite fields and enum variants at runtime
        if kind == "C" || kind == "E" {
            continue;
        }

        let element = match &*kind {
            "R" => range_elements[&oid],
            "A" => oids_by_name[&raw_type["typelem"]],
            _ => 0,
        };

        let doc_name = array_re.replace(&name, "$1[]").to_ascii_uppercase();
        let mut doc = doc_name.clone();
        if let Some(descr) = raw_type.get("descr") {
            write!(doc, " - {}", descr).unwrap();
        }
        let doc = Escape::new(doc.as_bytes().iter().cloned()).collect();
        let doc = String::from_utf8(doc).unwrap();

        if let Some(array_type_oid) = raw_type.get("array_type_oid") {
            let array_type_oid = array_type_oid.parse::<u32>().unwrap();

            let name = format!("_{}", name);
            let variant = format!("{}Array", variant);
            let doc = format!("{}[]", doc_name);
            let ident = format!("{}_ARRAY", ident);

            let type_ = Type {
                name,
                variant,
                ident,
                kind: "A".to_string(),
                element: oid,
                doc,
            };
            types.insert(array_type_oid, type_);
        }

        let type_ = Type {
            name,
            variant,
            ident,
            kind,
            element,
            doc,
        };
        types.insert(oid, type_);
    }

    types
}

fn make_header(w: &mut BufWriter<File>) {
    write!(
        w,
        "// Autogenerated file - DO NOT EDIT
use std::sync::Arc;

use crate::{{Type, Oid, Kind}};

#[derive(PartialEq, Eq, Debug, Hash)]
pub struct Other {{
    pub name: String,
    pub oid: Oid,
    pub kind: Kind,
    pub schema: String,
}}
"
    )
    .unwrap();
}

fn make_enum(w: &mut BufWriter<File>, types: &BTreeMap<u32, Type>) {
    write!(
        w,
        "
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum Inner {{"
    )
    .unwrap();

    for type_ in types.values() {
        write!(
            w,
            "
    {},",
            type_.variant
        )
        .unwrap();
    }

    write!(
        w,
        r"
    Other(Arc<Other>),
}}

"
    )
    .unwrap();
}

fn make_impl(w: &mut BufWriter<File>, types: &BTreeMap<u32, Type>) {
    write!(
        w,
        "impl Inner {{
    pub fn from_oid(oid: Oid) -> Option<Inner> {{
        match oid {{
",
    )
    .unwrap();

    for (oid, type_) in types {
        writeln!(
            w,
            "            {} => Some(Inner::{}),",
            oid, type_.variant
        )
        .unwrap();
    }

    writeln!(
        w,
        "            _ => None,
        }}
    }}

    pub fn oid(&self) -> Oid {{
        match *self {{",
    )
    .unwrap();

    for (oid, type_) in types {
        writeln!(
            w,
            "            Inner::{} => {},",
            type_.variant, oid
        )
        .unwrap();
    }

    writeln!(
        w,
        "            Inner::Other(ref u) => u.oid,
        }}
    }}

    pub fn kind(&self) -> &Kind {{
        match *self {{",
    )
    .unwrap();

    for type_ in types.values() {
        let kind = match &*type_.kind {
            "P" => "Pseudo".to_owned(),
            "A" => format!("Array(Type(Inner::{}))", types[&type_.element].variant),
            "R" => format!("Range(Type(Inner::{}))", types[&type_.element].variant),
            _ => "Simple".to_owned(),
        };

        writeln!(
            w,
            "            Inner::{} => {{
                &Kind::{}
            }}",
            type_.variant, kind
        )
        .unwrap();
    }

    writeln!(
        w,
        r#"            Inner::Other(ref u) => &u.kind,
        }}
    }}

    pub fn name(&self) -> &str {{
        match *self {{"#,
    )
    .unwrap();

    for type_ in types.values() {
        writeln!(
            w,
            r#"            Inner::{} => "{}","#,
            type_.variant, type_.name
        )
        .unwrap();
    }

    writeln!(
        w,
        "            Inner::Other(ref u) => &u.name,
        }}
    }}
}}"
    )
    .unwrap();
}

fn make_consts(w: &mut BufWriter<File>, types: &BTreeMap<u32, Type>) {
    write!(w, "impl Type {{").unwrap();
    for type_ in types.values() {
        writeln!(
            w,
            "
    /// {docs}
    pub const {ident}: Type = Type(Inner::{variant});",
            docs = type_.doc,
            ident = type_.ident,
            variant = type_.variant
        )
        .unwrap();
    }

    write!(w, "}}").unwrap();
}
<commit_msg>rustfmt<commit_after>use marksman_escape::Escape;
use regex::Regex;
use std::collections::{BTreeMap, HashMap};
use std::fmt::Write as _;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::iter;
use std::str;

use crate::snake_to_camel;

const PG_TYPE_DAT: &str = include_str!("pg_type.dat");
const PG_RANGE_DAT: &str = include_str!("pg_range.dat");

struct Type {
    name: String,
    variant: String,
    ident: String,
    kind: String,
    element: u32,
    doc: String,
}

pub fn build() {
    let mut file = BufWriter::new(File::create("../postgres-types/src/type_gen.rs").unwrap());
    let types = parse_types();

    make_header(&mut file);
    make_enum(&mut file, &types);
    make_impl(&mut file, &types);
    make_consts(&mut file, &types);
}

struct DatParser<'a> {
    it: iter::Peekable<str::CharIndices<'a>>,
    s: &'a str,
}

impl<'a> DatParser<'a> {
    fn new(s: &'a str) -> DatParser<'a> {
        DatParser {
            it: s.char_indices().peekable(),
            s,
        }
    }

    fn parse_array(&mut self) -> Vec<HashMap<String, String>> {
        self.eat('[');
        let mut vec = vec![];
        while !self.try_eat(']') {
            let object = self.parse_object();
            vec.push(object);
        }
        self.eof();

        vec
    }

    fn parse_object(&mut self) -> HashMap<String, String> {
        let mut object = HashMap::new();

        self.eat('{');
        loop {
            let key = self.parse_ident();
            self.eat('=');
            self.eat('>');
            let value = self.parse_string();
            object.insert(key, value);
            if !self.try_eat(',') {
                break;
            }
        }
        self.eat('}');
        self.eat(',');

        object
    }

    fn parse_ident(&mut self) -> String {
        self.skip_ws();

        let start = match self.it.peek() {
            Some((i, _)) => *i,
            None => return "".to_string(),
        };

        loop {
            match self.it.peek() {
                Some((_, 'a'..='z')) | Some((_, '_')) => {
                    self.it.next();
                }
                Some((i, _)) => return self.s[start..*i].to_string(),
                None => return self.s[start..].to_string(),
            }
        }
    }

    fn parse_string(&mut self) -> String {
        self.skip_ws();

        let mut s = String::new();

        self.eat('\'');
        loop {
            match self.it.next() {
                Some((_, '\'')) => return s,
                Some((_, '\\')) => {
                    let (_, ch) = self.it.next().expect("unexpected eof");
                    s.push(ch);
                }
                Some((_, ch)) => s.push(ch),
                None => panic!("unexpected eof"),
            }
        }
    }

    fn eat(&mut self, target: char) {
        self.skip_ws();

        match self.it.next() {
            Some((_, ch)) if ch == target => {}
            Some((_, ch)) => panic!("expected {} but got {}", target, ch),
            None => panic!("expected {} but got eof", target),
        }
    }

    fn try_eat(&mut self, target: char) -> bool {
        if self.peek(target) {
            self.eat(target);
            true
        } else {
            false
        }
    }

    fn peek(&mut self, target: char) -> bool {
        self.skip_ws();

        match self.it.peek() {
            Some((_, ch)) if *ch == target => true,
            _ => false,
        }
    }

    fn eof(&mut self) {
        self.skip_ws();
        if let Some((_, ch)) = self.it.next() {
            panic!("expected eof but got {}", ch);
        }
    }

    fn skip_ws(&mut self) {
        loop {
            match self.it.peek() {
                Some(&(_, '#')) => self.skip_to('\n'),
                Some(&(_, '\n')) | Some(&(_, ' ')) | Some(&(_, '\t')) => {
                    self.it.next();
                }
                _ => break,
            }
        }
    }

    fn skip_to(&mut self, target: char) {
        for (_, ch) in &mut self.it {
            if ch == target {
                break;
            }
        }
    }
}

fn parse_types() -> BTreeMap<u32, Type> {
    let raw_types = DatParser::new(PG_TYPE_DAT).parse_array();
    let raw_ranges = DatParser::new(PG_RANGE_DAT).parse_array();

    let oids_by_name = raw_types
        .iter()
        .map(|m| (m["typname"].clone(), m["oid"].parse::<u32>().unwrap()))
        .collect::<HashMap<_, _>>();

    let range_elements = raw_ranges
        .iter()
        .map(|m| {
            (
                oids_by_name[&*m["rngtypid"]],
                oids_by_name[&*m["rngsubtype"]],
            )
        })
        .collect::<HashMap<_, _>>();

    let range_vector_re = Regex::new("(range|vector)$").unwrap();
    let array_re = Regex::new("^_(.*)").unwrap();

    let mut types = BTreeMap::new();

    for raw_type in raw_types {
        let oid = raw_type["oid"].parse::<u32>().unwrap();

        let name = raw_type["typname"].clone();

        let ident = range_vector_re.replace(&name, "_$1");
        let ident = array_re.replace(&ident, "${1}_array");
        let variant = snake_to_camel(&ident);
        let ident = ident.to_ascii_uppercase();

        let kind = raw_type["typcategory"].clone();

        // we need to be able to pull composite fields and enum variants at runtime
        if kind == "C" || kind == "E" {
            continue;
        }

        let element = match &*kind {
            "R" => range_elements[&oid],
            "A" => oids_by_name[&raw_type["typelem"]],
            _ => 0,
        };

        let doc_name = array_re.replace(&name, "$1[]").to_ascii_uppercase();
        let mut doc = doc_name.clone();
        if let Some(descr) = raw_type.get("descr") {
            write!(doc, " - {}", descr).unwrap();
        }
        let doc = Escape::new(doc.as_bytes().iter().cloned()).collect();
        let doc = String::from_utf8(doc).unwrap();

        if let Some(array_type_oid) = raw_type.get("array_type_oid") {
            let array_type_oid = array_type_oid.parse::<u32>().unwrap();

            let name = format!("_{}", name);
            let variant = format!("{}Array", variant);
            let doc = format!("{}[]", doc_name);
            let ident = format!("{}_ARRAY", ident);

            let type_ = Type {
                name,
                variant,
                ident,
                kind: "A".to_string(),
                element: oid,
                doc,
            };
            types.insert(array_type_oid, type_);
        }

        let type_ = Type {
            name,
            variant,
            ident,
            kind,
            element,
            doc,
        };
        types.insert(oid, type_);
    }

    types
}

fn make_header(w: &mut BufWriter<File>) {
    write!(
        w,
        "// Autogenerated file - DO NOT EDIT
use std::sync::Arc;

use crate::{{Type, Oid, Kind}};

#[derive(PartialEq, Eq, Debug, Hash)]
pub struct Other {{
    pub name: String,
    pub oid: Oid,
    pub kind: Kind,
    pub schema: String,
}}
"
    )
    .unwrap();
}

fn make_enum(w: &mut BufWriter<File>, types: &BTreeMap<u32, Type>) {
    write!(
        w,
        "
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum Inner {{"
    )
    .unwrap();

    for type_ in types.values() {
        write!(
            w,
            "
    {},",
            type_.variant
        )
        .unwrap();
    }

    write!(
        w,
        r"
    Other(Arc<Other>),
}}

"
    )
    .unwrap();
}

fn make_impl(w: &mut BufWriter<File>, types: &BTreeMap<u32, Type>) {
    write!(
        w,
        "impl Inner {{
    pub fn from_oid(oid: Oid) -> Option<Inner> {{
        match oid {{
",
    )
    .unwrap();

    for (oid, type_) in types {
        writeln!(w, "            {} => Some(Inner::{}),", oid, type_.variant).unwrap();
    }

    writeln!(
        w,
        "            _ => None,
        }}
    }}

    pub fn oid(&self) -> Oid {{
        match *self {{",
    )
    .unwrap();

    for (oid, type_) in types {
        writeln!(w, "            Inner::{} => {},", type_.variant, oid).unwrap();
    }

    writeln!(
        w,
        "            Inner::Other(ref u) => u.oid,
        }}
    }}

    pub fn kind(&self) -> &Kind {{
        match *self {{",
    )
    .unwrap();

    for type_ in types.values() {
        let kind = match &*type_.kind {
            "P" => "Pseudo".to_owned(),
            "A" => format!("Array(Type(Inner::{}))", types[&type_.element].variant),
            "R" => format!("Range(Type(Inner::{}))", types[&type_.element].variant),
            _ => "Simple".to_owned(),
        };

        writeln!(
            w,
            "            Inner::{} => {{
                &Kind::{}
            }}",
            type_.variant, kind
        )
        .unwrap();
    }

    writeln!(
        w,
        r#"            Inner::Other(ref u) => &u.kind,
        }}
    }}

    pub fn name(&self) -> &str {{
        match *self {{"#,
    )
    .unwrap();

    for type_ in types.values() {
        writeln!(
            w,
            r#"            Inner::{} => "{}","#,
            type_.variant, type_.name
        )
        .unwrap();
    }

    writeln!(
        w,
        "            Inner::Other(ref u) => &u.name,
        }}
    }}
}}"
    )
    .unwrap();
}

fn make_consts(w: &mut BufWriter<File>, types: &BTreeMap<u32, Type>) {
    write!(w, "impl Type {{").unwrap();
    for type_ in types.values() {
        writeln!(
            w,
            "
    /// {docs}
    pub const {ident}: Type = Type(Inner::{variant});",
            docs = type_.doc,
            ident = type_.ident,
            variant = type_.variant
        )
        .unwrap();
    }

    write!(w, "}}").unwrap();
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate walkdir;

use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::fs::DirBuilder;
use std::path::{Path, PathBuf};
use std::process;
use std::process::{Command, Stdio};
use walkdir::WalkDir;

fn main() {
    let (args, passthrough) = parse_arguments();

    // Find all the native shared libraries that exist in the target directory.
    let native_shared_libs = find_native_libs(&args);

    // Get the SDK path from the ANDROID_HOME env.
    let sdk_path = env::var("ANDROID_HOME").ok().expect("Please set the ANDROID_HOME environment variable");
    let sdk_path = Path::new(&sdk_path);

    // Get the NDK path from NDK_HOME env.
    let ndk_path = env::var("NDK_HOME").ok().expect("Please set the NDK_HOME environment variable");
    let ndk_path = Path::new(&ndk_path);

    // Get the target android platform from ANDROID_PLATFORM env. Expecting "android-{version}"
    let android_platform = env::var("ANDROID_PLATFORM")
        .ok()
        .expect("Please set the ANDROID_PLATFORM environment variable");

    // Get the standalone NDK path from NDK_STANDALONE env.
    //  let standalone_path = env::var("NDK_STANDALONE").ok().unwrap_or("/opt/ndk_standalone".to_string());
    //  let standalone_path = Path::new(&standalone_path);

    let debug = passthrough.contains(&"-d".to_string());

    // Set the build directory that will contain all the necessary files to create the apk
    let directory = args.root_path.join("support").join("android").join("apk");
    let resdir = args.root_path.join("resources/");

    // executing ndk-build
    env::set_var("V", "1");
    if debug {
        env::set_var("NDK_DEBUG", "1");
        env::set_var("APP_OPTIM", "0");
    } else {
        // Overrides android:debuggable propery in the .xml file.
        env::set_var("APP_OPTIM", "1");
    }

    // Copy libservo.so into the jni folder for inclusion in the build
    // TODO: pass/detect target architecture
    {
        let source = &args.target_path.join("libservo.so");
        let target_dir = &directory.join("jni").join("armeabi");
        let _ = DirBuilder::new().recursive(true).create(target_dir);
        let target = target_dir.join("libmain.so");
        println!("Copying the file {:?} to {:?}", source, target);
        fs::copy(source, target).unwrap();
    }

    let ndkcmd = Command::new(ndk_path.join("ndk-build"))
                              .arg("-B")
                              .stdout(Stdio::inherit())
                              .stderr(Stdio::inherit())
                              .current_dir(directory.clone())
                              .status();
    if ndkcmd.is_err() || ndkcmd.unwrap().code().unwrap() != 0 {
        println!("Error while executing program `ndk-build`, or missing program.");
        process::exit(1);
    }

    // Copy the additional native libs into the libs directory.
    for (name, path) in native_shared_libs.iter() {
        let target = &directory.join("libs").join("armeabi").join(name);
        println!("Copying the file {:?} to {:?}", name, target);
        fs::copy(path, target).unwrap();
    }

    // Copy over the resources
    let cpcmd = Command::new("cp")
                             .arg("-R")
                             .arg(&resdir)
                             .arg(&directory.join("assets"))
                             .stdout(Stdio::inherit())
                             .stderr(Stdio::inherit())
                             .current_dir(directory.clone())
                             .status();
    if cpcmd.is_err() || cpcmd.unwrap().code().unwrap() != 0 {
        println!("Error while copying files from the resources dir to the assets dir");
        process::exit(1);
    }

    // Update the project
    let androidcmd = Command::new(sdk_path.join("tools").join("android"))
                                  .arg("update")
                                  .arg("project")
                                  .arg("--name")
                                  .arg("Servo")
                                  .arg("--target")
                                  .arg(&android_platform)
                                  .arg("--path")
                                  .arg(".")
                                  .stdout(Stdio::inherit())
                                  .stderr(Stdio::inherit())
                                  .current_dir(directory.clone())
                                  .status();
    if androidcmd.is_err() || androidcmd.unwrap().code().unwrap() != 0 {
        println!("Error while updating the project with the android command");
        process::exit(1);
    }

    // Build the APK
    let mut antcmd = Command::new("ant");
    if debug {
        antcmd.arg("debug");
    } else {
        antcmd.arg("release");
    }
    let antresult = antcmd
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .current_dir(directory.clone())
        .status();
    if antresult.is_err() || antresult.unwrap().code().unwrap() != 0 {
        println!("Error while executing program `ant`, or missing program.");
        process::exit(1);
    }

    // Copying apk file to the requested output
    // Release builds also need to be signed. For now, we use a simple debug
    // signing key.
    if debug {
        fs::copy(&directory.join("bin").join("Servo-debug.apk"),
                 &args.output).unwrap();
    } else {
        let keystore_dir = env::home_dir().expect("Please have a home directory");
        let keystore_dir = Path::new(&keystore_dir).join(".keystore");
        let keytoolcmd = Command::new("keytool")
                                  .arg("-list")
                                  .arg("-storepass")
                                  .arg("android")
                                  .arg("-alias")
                                  .arg("androiddebugkey")
                                  .arg("-keystore")
                                  .arg(&keystore_dir)
                                  .stdout(Stdio::inherit())
                                  .stderr(Stdio::inherit())
                                  .current_dir(directory.clone())
                                  .status();
        if keytoolcmd.is_err() || keytoolcmd.unwrap().code().unwrap() != 0 {
            let keytoolcreatecmd = Command::new("keytool")
                                  .arg("-genkeypair")
                                  .arg("-keystore")
                                  .arg(&keystore_dir)
                                  .arg("-storepass")
                                  .arg("android")
                                  .arg("-alias")
                                  .arg("androiddebugkey")
                                  .arg("-keypass")
                                  .arg("android")
                                  .arg("-dname")
                                  .arg("CN=Android Debug,O=Android,C=US")
                                  .arg("-keyalg")
                                  .arg("RSA")
                                  .arg("-validity")
                                  .arg("365")
                                  .stdout(Stdio::inherit())
                                  .stderr(Stdio::inherit())
                                  .current_dir(directory.clone())
                                  .status();
            if keytoolcreatecmd.is_err() ||
               keytoolcreatecmd.unwrap().code().unwrap() != 0 {
                   println!("Error while using `keytool` to create the debug keystore.");
                   process::exit(1);
               }
        }

        let jarsigncmd = Command::new("jarsigner")
                                  .arg("-digestalg")
                                  .arg("SHA1")
                                  .arg("-sigalg")
                                  .arg("MD5withRSA")
                                  .arg("-storepass")
                                  .arg("android")
                                  .arg("-keystore")
                                  .arg(&keystore_dir)
                                  .arg(&directory.join("bin").join("Servo-release-unsigned.apk"))
                                  .arg("androiddebugkey")
                                  .stdout(Stdio::inherit())
                                  .stderr(Stdio::inherit())
                                  .current_dir(directory.clone())
                                  .status();
        if jarsigncmd.is_err() || jarsigncmd.unwrap().code().unwrap() != 0 {
            println!("Error while using `jarsign` to sign the APK.");
            process::exit(1);
        }

        fs::copy(&directory.join("bin").join("Servo-release-unsigned.apk"),
                 &args.output).unwrap();
    }

}

struct Args {
    output: PathBuf,
    root_path: PathBuf,
    target_path: PathBuf,
    shared_libraries: HashSet<String>,
}

fn parse_arguments() -> (Args, Vec<String>) {
    let mut result_output = None;
    let mut result_root_path = None;
    let mut result_target_path = None;
    let mut result_shared_libraries = HashSet::new();
    let mut result_passthrough = Vec::new();

    let args = env::args();
    let mut args = args.skip(1);

    loop {
        let arg = match args.next() {
            None => return (
                Args {
                    output: result_output.expect("Could not find -o argument"),
                    root_path: result_root_path.expect("Could not find -r enlistment root argument"),
                    target_path: result_target_path.expect("Could not find -t target path argument"),
                    shared_libraries: result_shared_libraries,
                },
                result_passthrough
            ),
            Some(arg) => arg
        };

        match &*arg {
            "-o" => {
                result_output = Some(PathBuf::from(args.next().expect("-o must be followed by the output name")));
            },
            "-r" => {
                result_root_path =
                    Some(PathBuf::from(args.next().expect("-r must be followed by the enlistment root directory")));
            },
            "-t" => {
                result_target_path =
                    Some(PathBuf::from(args.next().expect("-t must be followed by the target output directory")));
            },
            "-l" => {
                let name = args.next().expect("-l must be followed by a library name");
                result_shared_libraries.insert(vec!["lib", &name, ".so"].concat());

                // Also pass these through.
                result_passthrough.push(arg);
                result_passthrough.push(name);
            }
            _ => {
                if arg.starts_with("-l") {
                    result_shared_libraries.insert(vec!["lib", &arg[2..], ".so"].concat());
                }
                result_passthrough.push(arg)
            }
        };
    }
}

fn find_native_libs(args: &Args) -> HashMap<String, PathBuf> {
    let mut native_shared_libs: HashMap<String, PathBuf> = HashMap::new();

    // Add requested .so files
    for dir_entry in WalkDir::new(&args.target_path) {
        let dir_entry = dir_entry.unwrap();
        let path = dir_entry.path();

        match (path.file_name(), path.extension()) {
            (Some(file_name), Some(extension)) => {
                let file_name = file_name.to_str().unwrap();

                if file_name.starts_with("lib") &&
                    extension == "so" &&
                    args.shared_libraries.contains(file_name) {
                        println!("Adding the file {:?}", file_name);
                        native_shared_libs.insert(file_name.to_string(), path.to_path_buf().clone());
                        break;
                }
            }
            _ => {}
        }
    }

    native_shared_libs
}
<commit_msg>Add C++ runtime shared object to the Android APK<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate walkdir;

use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::fs::DirBuilder;
use std::path::{Path, PathBuf};
use std::process;
use std::process::{Command, Stdio};
use walkdir::WalkDir;

fn main() {
    let (args, passthrough) = parse_arguments();

    // Find all the native shared libraries that exist in the target directory.
    let mut native_shared_libs = find_native_libs(&args);

    // Get the SDK path from the ANDROID_HOME env.
    let sdk_path = env::var("ANDROID_HOME").ok().expect("Please set the ANDROID_HOME environment variable");
    let sdk_path = Path::new(&sdk_path);

    // Get the NDK path from NDK_HOME env.
    let ndk_path = env::var("NDK_HOME").ok().expect("Please set the NDK_HOME environment variable");
    let ndk_path = Path::new(&ndk_path);

    // Get the target android platform from ANDROID_PLATFORM env. Expecting "android-{version}"
    let android_platform = env::var("ANDROID_PLATFORM")
        .ok()
        .expect("Please set the ANDROID_PLATFORM environment variable");

    // Add the C++ runtime .so
    {
        let libcpp_base_path = ndk_path.join("sources").join("cxx-stl").join("llvm-libc++").join("libs");
        let libcpp_filename = "libc++_shared.so";
        let libcpp_path = libcpp_base_path.join("armeabi").join(libcpp_filename);
        native_shared_libs.insert(libcpp_filename.to_string(), libcpp_path);
    }

    // Get the standalone NDK path from NDK_STANDALONE env.
    //  let standalone_path = env::var("NDK_STANDALONE").ok().unwrap_or("/opt/ndk_standalone".to_string());
    //  let standalone_path = Path::new(&standalone_path);

    let debug = passthrough.contains(&"-d".to_string());

    // Set the build directory that will contain all the necessary files to create the apk
    let directory = args.root_path.join("support").join("android").join("apk");
    let resdir = args.root_path.join("resources/");

    // executing ndk-build
    env::set_var("V", "1");
    if debug {
        env::set_var("NDK_DEBUG", "1");
        env::set_var("APP_OPTIM", "0");
    } else {
        // Overrides android:debuggable propery in the .xml file.
        env::set_var("APP_OPTIM", "1");
    }

    // Copy libservo.so into the jni folder for inclusion in the build
    // TODO: pass/detect target architecture
    {
        let source = &args.target_path.join("libservo.so");
        let target_dir = &directory.join("jni").join("armeabi");
        let _ = DirBuilder::new().recursive(true).create(target_dir);
        let target = target_dir.join("libmain.so");
        println!("Copying the file {:?} to {:?}", source, target);
        fs::copy(source, target).unwrap();
    }

    let ndkcmd = Command::new(ndk_path.join("ndk-build"))
                              .arg("-B")
                              .stdout(Stdio::inherit())
                              .stderr(Stdio::inherit())
                              .current_dir(directory.clone())
                              .status();
    if ndkcmd.is_err() || ndkcmd.unwrap().code().unwrap() != 0 {
        println!("Error while executing program `ndk-build`, or missing program.");
        process::exit(1);
    }

    // Copy the additional native libs into the libs directory.
    for (name, path) in native_shared_libs.iter() {
        let target = &directory.join("libs").join("armeabi").join(name);
        println!("Copying the file {:?} to {:?}", name, target);
        fs::copy(path, target).unwrap();
    }

    // Copy over the resources
    let cpcmd = Command::new("cp")
                             .arg("-R")
                             .arg(&resdir)
                             .arg(&directory.join("assets"))
                             .stdout(Stdio::inherit())
                             .stderr(Stdio::inherit())
                             .current_dir(directory.clone())
                             .status();
    if cpcmd.is_err() || cpcmd.unwrap().code().unwrap() != 0 {
        println!("Error while copying files from the resources dir to the assets dir");
        process::exit(1);
    }

    // Update the project
    let androidcmd = Command::new(sdk_path.join("tools").join("android"))
                                  .arg("update")
                                  .arg("project")
                                  .arg("--name")
                                  .arg("Servo")
                                  .arg("--target")
                                  .arg(&android_platform)
                                  .arg("--path")
                                  .arg(".")
                                  .stdout(Stdio::inherit())
                                  .stderr(Stdio::inherit())
                                  .current_dir(directory.clone())
                                  .status();
    if androidcmd.is_err() || androidcmd.unwrap().code().unwrap() != 0 {
        println!("Error while updating the project with the android command");
        process::exit(1);
    }

    // Build the APK
    let mut antcmd = Command::new("ant");
    if debug {
        antcmd.arg("debug");
    } else {
        antcmd.arg("release");
    }
    let antresult = antcmd
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .current_dir(directory.clone())
        .status();
    if antresult.is_err() || antresult.unwrap().code().unwrap() != 0 {
        println!("Error while executing program `ant`, or missing program.");
        process::exit(1);
    }

    // Copying apk file to the requested output
    // Release builds also need to be signed. For now, we use a simple debug
    // signing key.
    if debug {
        fs::copy(&directory.join("bin").join("Servo-debug.apk"),
                 &args.output).unwrap();
    } else {
        let keystore_dir = env::home_dir().expect("Please have a home directory");
        let keystore_dir = Path::new(&keystore_dir).join(".keystore");
        let keytoolcmd = Command::new("keytool")
                                  .arg("-list")
                                  .arg("-storepass")
                                  .arg("android")
                                  .arg("-alias")
                                  .arg("androiddebugkey")
                                  .arg("-keystore")
                                  .arg(&keystore_dir)
                                  .stdout(Stdio::inherit())
                                  .stderr(Stdio::inherit())
                                  .current_dir(directory.clone())
                                  .status();
        if keytoolcmd.is_err() || keytoolcmd.unwrap().code().unwrap() != 0 {
            let keytoolcreatecmd = Command::new("keytool")
                                  .arg("-genkeypair")
                                  .arg("-keystore")
                                  .arg(&keystore_dir)
                                  .arg("-storepass")
                                  .arg("android")
                                  .arg("-alias")
                                  .arg("androiddebugkey")
                                  .arg("-keypass")
                                  .arg("android")
                                  .arg("-dname")
                                  .arg("CN=Android Debug,O=Android,C=US")
                                  .arg("-keyalg")
                                  .arg("RSA")
                                  .arg("-validity")
                                  .arg("365")
                                  .stdout(Stdio::inherit())
                                  .stderr(Stdio::inherit())
                                  .current_dir(directory.clone())
                                  .status();
            if keytoolcreatecmd.is_err() ||
               keytoolcreatecmd.unwrap().code().unwrap() != 0 {
                   println!("Error while using `keytool` to create the debug keystore.");
                   process::exit(1);
               }
        }

        let jarsigncmd = Command::new("jarsigner")
                                  .arg("-digestalg")
                                  .arg("SHA1")
                                  .arg("-sigalg")
                                  .arg("MD5withRSA")
                                  .arg("-storepass")
                                  .arg("android")
                                  .arg("-keystore")
                                  .arg(&keystore_dir)
                                  .arg(&directory.join("bin").join("Servo-release-unsigned.apk"))
                                  .arg("androiddebugkey")
                                  .stdout(Stdio::inherit())
                                  .stderr(Stdio::inherit())
                                  .current_dir(directory.clone())
                                  .status();
        if jarsigncmd.is_err() || jarsigncmd.unwrap().code().unwrap() != 0 {
            println!("Error while using `jarsign` to sign the APK.");
            process::exit(1);
        }

        fs::copy(&directory.join("bin").join("Servo-release-unsigned.apk"),
                 &args.output).unwrap();
    }

}

struct Args {
    output: PathBuf,
    root_path: PathBuf,
    target_path: PathBuf,
    shared_libraries: HashSet<String>,
}

fn parse_arguments() -> (Args, Vec<String>) {
    let mut result_output = None;
    let mut result_root_path = None;
    let mut result_target_path = None;
    let mut result_shared_libraries = HashSet::new();
    let mut result_passthrough = Vec::new();

    let args = env::args();
    let mut args = args.skip(1);

    loop {
        let arg = match args.next() {
            None => return (
                Args {
                    output: result_output.expect("Could not find -o argument"),
                    root_path: result_root_path.expect("Could not find -r enlistment root argument"),
                    target_path: result_target_path.expect("Could not find -t target path argument"),
                    shared_libraries: result_shared_libraries,
                },
                result_passthrough
            ),
            Some(arg) => arg
        };

        match &*arg {
            "-o" => {
                result_output = Some(PathBuf::from(args.next().expect("-o must be followed by the output name")));
            },
            "-r" => {
                result_root_path =
                    Some(PathBuf::from(args.next().expect("-r must be followed by the enlistment root directory")));
            },
            "-t" => {
                result_target_path =
                    Some(PathBuf::from(args.next().expect("-t must be followed by the target output directory")));
            },
            "-l" => {
                let name = args.next().expect("-l must be followed by a library name");
                result_shared_libraries.insert(vec!["lib", &name, ".so"].concat());

                // Also pass these through.
                result_passthrough.push(arg);
                result_passthrough.push(name);
            }
            _ => {
                if arg.starts_with("-l") {
                    result_shared_libraries.insert(vec!["lib", &arg[2..], ".so"].concat());
                }
                result_passthrough.push(arg)
            }
        };
    }
}

fn find_native_libs(args: &Args) -> HashMap<String, PathBuf> {
    let mut native_shared_libs: HashMap<String, PathBuf> = HashMap::new();

    // Add requested .so files
    for dir_entry in WalkDir::new(&args.target_path) {
        let dir_entry = dir_entry.unwrap();
        let path = dir_entry.path();

        match (path.file_name(), path.extension()) {
            (Some(file_name), Some(extension)) => {
                let file_name = file_name.to_str().unwrap();

                if file_name.starts_with("lib") &&
                    extension == "so" &&
                    args.shared_libraries.contains(file_name) {
                        println!("Adding the file {:?}", file_name);
                        native_shared_libs.insert(file_name.to_string(), path.to_path_buf().clone());
                        break;
                }
            }
            _ => {}
        }
    }

    native_shared_libs
}
<|endoftext|>


<commit_before>use ::inflector::Inflector;
use ::syn::Ident;
use ::yaml_rust::Yaml;

use infer::TokensResult;

fn ident(param: &str) -> Ident {
    Ident::new(param.to_snake_case())
}

fn infer_type(schema: &Yaml) -> TokensResult {
    match schema["type"].as_str() {
        None => Err(From::from("Parameter schema type must be a string.")),
        Some("integer") => {
            match schema["format"].as_str() {
                None => Err(From::from("Parameter schema format must be a string.")),
                Some("int32") => Ok(quote!{i32}),
                Some("int64") => Ok(quote!{i64}),
                Some(_) => Err(From::from("Invalid format for `integer` type.")),
            }
        },
        Some("number") => {
            match schema["format"].as_str() {
                None => Err(From::from("Parameter schema format must be a string.")),
                Some("float") => Ok(quote!{f32}),
                Some("double") => Ok(quote!{f64}),
                Some(_) => Err(From::from("Invalid format for `number` type.")),
            }
        },
        //TODO: the rest!
        Some(ptype) => Err(From::from(format!("Parameter type `{}` invalid", ptype))),
    }
}

pub(super) fn infer_v3(schema: &Yaml) -> TokensResult {
    let ident = ident(schema["name"]
        .as_str().expect("Parameter name must be a string.")
    );
    let type_tt = infer_type(&schema["schema"])?;

    Ok(quote!{#ident: #type_tt})
}
<commit_msg>Add inference of string and Boolean query parameter types<commit_after>use ::inflector::Inflector;
use ::syn::Ident;
use ::yaml_rust::Yaml;

use infer::TokensResult;

fn ident(param: &str) -> Ident {
    Ident::new(param.to_snake_case())
}

fn infer_type(schema: &Yaml) -> TokensResult {
    match schema["type"].as_str() {
        None => Err(From::from("Parameter schema type must be a string.")),
        Some("integer") => {
            match schema["format"].as_str() {
                None => Err(From::from("Parameter schema format must be a string.")),
                Some("int32") => Ok(quote!{i32}),
                Some("int64") => Ok(quote!{i64}),
                Some(_) => Err(From::from("Invalid format for `integer` type.")),
            }
        },
        Some("number") => {
            match schema["format"].as_str() {
                None => Err(From::from("Parameter schema format must be a string.")),
                Some("float") => Ok(quote!{f32}),
                Some("double") => Ok(quote!{f64}),
                Some(_) => Err(From::from("Invalid format for `number` type.")),
            }
        },
        Some("string") => {
            match schema["format"].as_str() {
                None => Ok(quote!{String}),
                Some("byte") => Ok(quote!{tapioca::Base64}),
                Some("binary") => Ok(quote!{&[u8]}),
                Some("date") => Ok(quote!{tapioca::Date}),
                Some("date-time") => Ok(quote!{tapioca::DateTime}),
                Some("password") => Ok(quote!{String}),
                Some(_) => Ok(quote!{String}),
            }
        },
        Some("boolean") => {
            match schema["format"].as_str() {
                None => Ok(quote!{bool}),
                Some(_) => Err(From::from("Unexpected format for `boolean` type.")),
            }
        },
        Some(ptype) => Err(From::from(format!("Parameter type `{}` invalid", ptype))),
    }
}

pub(super) fn infer_v3(schema: &Yaml) -> TokensResult {
    let ident = ident(schema["name"]
        .as_str().expect("Parameter name must be a string.")
    );
    let type_tt = infer_type(&schema["schema"])?;

    Ok(quote!{#ident: #type_tt})
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::fs;
use std::path::*;
use std::io::{Result, Write};
use case::CaseExt;

use env::Env;
use file_saver::*;
use library;
use nameutil::*;
use super::ffi_type::ffi_type;
use super::functions;
use super::statics;
use super::super::general::{self, tabs};
use traits::*;

pub fn generate(env: &Env) {
    println!("generating sys for {}", env.config.library_name);

    let path =  PathBuf::from(&env.config.target_path)
        .join(file_name_sys("lib"));

    let parent = path.parent().unwrap();
    //TODO: do only if not exists
    let _ = fs::create_dir(parent);

    println!("Generating file {:?}", path);
    save_to_file(&path, &mut |w| generate_lib(w, env));
}

fn generate_lib<W: Write>(w: &mut W, env: &Env) -> Result<()>{
    try!(general::start_comments(w, &env.config));
    try!(statics::begin(w));

    try!(generate_extern_crates(w, env));
    try!(statics::after_extern_crates(w));

    if env.config.library_name != "GLib" {
        try!(statics::use_glib_ffi(w));
    } else {
        try!(statics::only_for_glib(w));
    }
    if env.config.library_name == "Gtk" {
        try!(statics::only_for_gtk(w));
    }

    let ns = env.library.namespace(library::MAIN_NAMESPACE);
    let classes = prepare(ns);
    let interfaces = prepare(ns);

    try!(generate_aliases(w, env, &prepare(ns)));
    try!(generate_enums(w, &ns.name, &prepare(ns)));
    try!(generate_bitfields(w, &ns.name, &prepare(ns)));
    try!(generate_unions(w, &prepare(ns)));
    try!(functions::generate_callbacks(w, env, &prepare(ns)));
    try!(generate_records(w, env, &prepare(ns)));
    try!(generate_classes_structs(w, &classes));
    try!(generate_interfaces_structs(w, &interfaces));

    try!(writeln!(w, ""));
    try!(writeln!(w, "extern \"C\" {{"));
    try!(functions::generate_classes_funcs(w, env, &classes));
    try!(functions::generate_interfaces_funcs(w, env, &interfaces));

    //TODO: other functions
    try!(writeln!(w, "\n}}"));

    Ok(())
}

fn generate_extern_crates<W: Write>(w: &mut W, env: &Env) -> Result<()>{
    for library_name in &env.config.external_libraries {
        try!(writeln!(w, "extern crate {0}_sys as {0}_ffi;", crate_name(library_name)));
    }

    Ok(())
}

fn prepare<T: Ord>(ns: &library::Namespace) -> Vec<&T>
where library::Type: MaybeRef<T> {
    let mut vec: Vec<&T> = Vec::with_capacity(ns.types.len());
    for typ in ns.types.iter().filter_map(|t| t.as_ref()) {
        if let Some(ref x) = typ.maybe_ref() {
            vec.push(x);
        }
    }
    vec.sort();
    vec
}

fn generate_aliases<W: Write>(w: &mut W, env: &Env, items: &[&library::Alias])
        -> Result<()> {
    try!(writeln!(w, ""));
    for item in items {
        let (comment, c_type) = match ffi_type(env, item.typ, &item.target_c_type) {
            Ok(x) => ("", x),
            Err(x) => ("//", x),
        };
        try!(writeln!(w, "{}pub type {} = {};", comment, item.c_identifier, c_type));
    }

    Ok(())
}

fn generate_bitfields<W: Write>(w: &mut W, ns_name: &str, items: &[&library::Bitfield])
        -> Result<()> {
    try!(writeln!(w, ""));
    for item in items {
        try!(writeln!(w, "bitflags! {{\n{}#[repr(C)]\n{0}flags {}: c_uint {{", tabs(1), item.name));
        for member in &item.members {
            let val: i64 = member.value.parse().unwrap();
            try!(writeln!(w, "{}const {} = {},",
                          tabs(2), strip_prefix(ns_name, &member.c_identifier), val as u32));
        }
        try!(writeln!(w, "{}}}\n}}", tabs(1)));
        try!(writeln!(w, "pub type {} = {};", item.c_type, item.name));
        try!(writeln!(w, ""));
    }

    Ok(())
}

fn generate_enums<W: Write>(w: &mut W, ns_name: &str, items: &[&library::Enumeration])
        -> Result<()> {
    try!(writeln!(w, ""));
    for item in items {
        if item.members.len() == 1 {
            try!(writeln!(w, "pub type {} = c_int;", item.name));
            try!(writeln!(w, "pub const {}: {} = {};",
                          item.members[0].c_identifier, item.name, item.members[0].value));
            try!(writeln!(w, "pub type {} = {};", item.c_type, item.name));
            try!(writeln!(w, ""));
            continue;
        }

        let mut vals = HashMap::new();
        try!(writeln!(w, "#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n#[repr(C)]"));
        try!(writeln!(w, "pub enum {} {{", item.name));
        for member in &item.members {
            if vals.get(&member.value).is_some() {
                continue;
            }
            try!(writeln!(w, "{}{} = {},",
                          tabs(1), &prepare_enum_member_name(&member.name), member.value));
            vals.insert(member.value.clone(), member.name.clone());
        }
        try!(writeln!(w, "}}"));
        for member in &item.members {
            try!(writeln!(w, "pub const {}: {} = {1}::{};",
                          prepare_enum_member_name(strip_prefix(ns_name, &member.c_identifier)),
                          item.name, &prepare_enum_member_name(vals.get(&member.value).unwrap())));
        }
        try!(writeln!(w, "pub type {} = {};", item.c_type, item.name));
        try!(writeln!(w, ""));
    }

    Ok(())
}

fn generate_unions<W: Write>(w: &mut W, items: &[&library::Union])
        -> Result<()> {
    try!(writeln!(w, ""));
    for item in items {
        try!(writeln!(w, "pub type {} = c_void;", item.name));
        if let Some(ref c_type) = item.c_type {
            try!(writeln!(w, "pub type {} = {};", c_type, item.name));
        }
        try!(writeln!(w, ""));
    }

    Ok(())
}

fn prepare_enum_member_name(name: &str) -> String {
    let cameled = name.to_camel();
    if name.chars().next().unwrap().is_digit(10) {
        format!("_{}", cameled)
    } else {
        cameled
    }
}

fn generate_classes_structs<W: Write>(w: &mut W, classes: &[&library::Class]) -> Result<()> {
    try!(writeln!(w, ""));
    for klass in classes {
        try!(writeln!(w, "#[repr(C)]\npub struct {};", klass.c_type));
    }

    Ok(())
}

fn generate_interfaces_structs<W: Write>(w: &mut W, interfaces: &[&library::Interface]) -> Result<()> {
    try!(writeln!(w, ""));
    for interface in interfaces {
        try!(writeln!(w, "#[repr(C)]\npub struct {};", interface.c_type));
    }

    Ok(())
}

fn generate_records<W: Write>(w: &mut W, env: &Env, records: &[&library::Record]) -> Result<()> {
    try!(writeln!(w, ""));
    for record in records {
        let mut lines = Vec::new();
        let mut commented = false;
        let mut truncated = false;
        for field in &record.fields {
            let is_union = env.library.type_(field.typ).maybe_ref_as::<library::Union>().is_some();
            let is_bits = field.bits.is_some();
            if !truncated && (is_union || is_bits) {
                warn!("Record `{}` field `{}` not expressible in Rust, truncated",
                      record.name, field.name);
                lines.push(format!("{}_truncated_record_marker: (),", tabs(1)));
                truncated = true;
            }
            if truncated {
                if is_union {
                    lines.push(format!("{}//union,", tabs(1)));
                }
                else {
                    let bits = field.bits.map(|n| format!(": {}", n)).unwrap_or("".into());
                    lines.push(
                        format!("{}//{}: {}{},", tabs(1), field.name,
                                field.c_type.as_ref().map(|s| &s[..]).unwrap_or("fn"), bits));
                };
                continue;
            }

            let vis = if field.private { "" } else { "pub " };

            if let Some(ref c_type) = field.c_type {
                let name = mangle_keywords(&*field.name);
                let c_type = ffi_type(env, field.typ, c_type);
                lines.push(format!("{}{}{}: {},", tabs(1), vis, name, c_type.as_str()));
                if c_type.is_err() {
                    commented = true;
                }
            }
            else {
                let name = mangle_keywords(&*field.name);
                if let Some(ref func) =
                        env.library.type_(field.typ).maybe_ref_as::<library::Function>() {
                    let (com, sig) = functions::function_signature(env, func, true);
                    lines.push(format!("{}{}{}: fn{},", tabs(1), vis, name, sig));
                    commented |= com;
                }
                else {
                    lines.push(format!("{}{}{}: [{:?} {}],", tabs(1),
                        vis, name, field.typ, field.typ.full_name(&env.library)));
                    commented = true;
                }
            }
        }
        let comment = if commented { "//" } else { "" };
        if lines.is_empty() {
            try!(writeln!(w, "{}#[repr(C)]\n{0}pub struct {};\n", comment, record.c_type));
        }
        else {
            try!(writeln!(w, "{}#[repr(C)]\n{0}pub struct {} {{", comment, record.c_type));
            for line in lines {
                try!(writeln!(w, "{}{}", comment, line));
            }
            try!(writeln!(w, "{}}}\n", comment));
        }
    }
    Ok(())
}
<commit_msg>sys: if a record field c:type is missing, guess it from the type<commit_after>use std::collections::HashMap;
use std::fs;
use std::path::*;
use std::io::{Result, Write};
use case::CaseExt;

use env::Env;
use file_saver::*;
use library;
use nameutil::*;
use super::ffi_type::ffi_type;
use super::functions;
use super::statics;
use super::super::general::{self, tabs};
use traits::*;

pub fn generate(env: &Env) {
    println!("generating sys for {}", env.config.library_name);

    let path =  PathBuf::from(&env.config.target_path)
        .join(file_name_sys("lib"));

    let parent = path.parent().unwrap();
    //TODO: do only if not exists
    let _ = fs::create_dir(parent);

    println!("Generating file {:?}", path);
    save_to_file(&path, &mut |w| generate_lib(w, env));
}

fn generate_lib<W: Write>(w: &mut W, env: &Env) -> Result<()>{
    try!(general::start_comments(w, &env.config));
    try!(statics::begin(w));

    try!(generate_extern_crates(w, env));
    try!(statics::after_extern_crates(w));

    if env.config.library_name != "GLib" {
        try!(statics::use_glib_ffi(w));
    } else {
        try!(statics::only_for_glib(w));
    }
    if env.config.library_name == "Gtk" {
        try!(statics::only_for_gtk(w));
    }

    let ns = env.library.namespace(library::MAIN_NAMESPACE);
    let classes = prepare(ns);
    let interfaces = prepare(ns);

    try!(generate_aliases(w, env, &prepare(ns)));
    try!(generate_enums(w, &ns.name, &prepare(ns)));
    try!(generate_bitfields(w, &ns.name, &prepare(ns)));
    try!(generate_unions(w, &prepare(ns)));
    try!(functions::generate_callbacks(w, env, &prepare(ns)));
    try!(generate_records(w, env, &prepare(ns)));
    try!(generate_classes_structs(w, &classes));
    try!(generate_interfaces_structs(w, &interfaces));

    try!(writeln!(w, ""));
    try!(writeln!(w, "extern \"C\" {{"));
    try!(functions::generate_classes_funcs(w, env, &classes));
    try!(functions::generate_interfaces_funcs(w, env, &interfaces));

    //TODO: other functions
    try!(writeln!(w, "\n}}"));

    Ok(())
}

fn generate_extern_crates<W: Write>(w: &mut W, env: &Env) -> Result<()>{
    for library_name in &env.config.external_libraries {
        try!(writeln!(w, "extern crate {0}_sys as {0}_ffi;", crate_name(library_name)));
    }

    Ok(())
}

fn prepare<T: Ord>(ns: &library::Namespace) -> Vec<&T>
where library::Type: MaybeRef<T> {
    let mut vec: Vec<&T> = Vec::with_capacity(ns.types.len());
    for typ in ns.types.iter().filter_map(|t| t.as_ref()) {
        if let Some(ref x) = typ.maybe_ref() {
            vec.push(x);
        }
    }
    vec.sort();
    vec
}

fn generate_aliases<W: Write>(w: &mut W, env: &Env, items: &[&library::Alias])
        -> Result<()> {
    try!(writeln!(w, ""));
    for item in items {
        let (comment, c_type) = match ffi_type(env, item.typ, &item.target_c_type) {
            Ok(x) => ("", x),
            Err(x) => ("//", x),
        };
        try!(writeln!(w, "{}pub type {} = {};", comment, item.c_identifier, c_type));
    }

    Ok(())
}

fn generate_bitfields<W: Write>(w: &mut W, ns_name: &str, items: &[&library::Bitfield])
        -> Result<()> {
    try!(writeln!(w, ""));
    for item in items {
        try!(writeln!(w, "bitflags! {{\n{}#[repr(C)]\n{0}flags {}: c_uint {{", tabs(1), item.name));
        for member in &item.members {
            let val: i64 = member.value.parse().unwrap();
            try!(writeln!(w, "{}const {} = {},",
                          tabs(2), strip_prefix(ns_name, &member.c_identifier), val as u32));
        }
        try!(writeln!(w, "{}}}\n}}", tabs(1)));
        try!(writeln!(w, "pub type {} = {};", item.c_type, item.name));
        try!(writeln!(w, ""));
    }

    Ok(())
}

fn generate_enums<W: Write>(w: &mut W, ns_name: &str, items: &[&library::Enumeration])
        -> Result<()> {
    try!(writeln!(w, ""));
    for item in items {
        if item.members.len() == 1 {
            try!(writeln!(w, "pub type {} = c_int;", item.name));
            try!(writeln!(w, "pub const {}: {} = {};",
                          item.members[0].c_identifier, item.name, item.members[0].value));
            try!(writeln!(w, "pub type {} = {};", item.c_type, item.name));
            try!(writeln!(w, ""));
            continue;
        }

        let mut vals = HashMap::new();
        try!(writeln!(w, "#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n#[repr(C)]"));
        try!(writeln!(w, "pub enum {} {{", item.name));
        for member in &item.members {
            if vals.get(&member.value).is_some() {
                continue;
            }
            try!(writeln!(w, "{}{} = {},",
                          tabs(1), &prepare_enum_member_name(&member.name), member.value));
            vals.insert(member.value.clone(), member.name.clone());
        }
        try!(writeln!(w, "}}"));
        for member in &item.members {
            try!(writeln!(w, "pub const {}: {} = {1}::{};",
                          prepare_enum_member_name(strip_prefix(ns_name, &member.c_identifier)),
                          item.name, &prepare_enum_member_name(vals.get(&member.value).unwrap())));
        }
        try!(writeln!(w, "pub type {} = {};", item.c_type, item.name));
        try!(writeln!(w, ""));
    }

    Ok(())
}

fn generate_unions<W: Write>(w: &mut W, items: &[&library::Union])
        -> Result<()> {
    try!(writeln!(w, ""));
    for item in items {
        try!(writeln!(w, "pub type {} = c_void;", item.name));
        if let Some(ref c_type) = item.c_type {
            try!(writeln!(w, "pub type {} = {};", c_type, item.name));
        }
        try!(writeln!(w, ""));
    }

    Ok(())
}

fn prepare_enum_member_name(name: &str) -> String {
    let cameled = name.to_camel();
    if name.chars().next().unwrap().is_digit(10) {
        format!("_{}", cameled)
    } else {
        cameled
    }
}

fn generate_classes_structs<W: Write>(w: &mut W, classes: &[&library::Class]) -> Result<()> {
    try!(writeln!(w, ""));
    for klass in classes {
        try!(writeln!(w, "#[repr(C)]\npub struct {};", klass.c_type));
    }

    Ok(())
}

fn generate_interfaces_structs<W: Write>(w: &mut W, interfaces: &[&library::Interface]) -> Result<()> {
    try!(writeln!(w, ""));
    for interface in interfaces {
        try!(writeln!(w, "#[repr(C)]\npub struct {};", interface.c_type));
    }

    Ok(())
}

fn generate_records<W: Write>(w: &mut W, env: &Env, records: &[&library::Record]) -> Result<()> {
    try!(writeln!(w, ""));
    for record in records {
        let mut lines = Vec::new();
        let mut commented = false;
        let mut truncated = false;
        for field in &record.fields {
            let is_union = env.library.type_(field.typ).maybe_ref_as::<library::Union>().is_some();
            let is_bits = field.bits.is_some();
            if !truncated && (is_union || is_bits) {
                warn!("Record `{}` field `{}` not expressible in Rust, truncated",
                      record.name, field.name);
                lines.push(format!("{}_truncated_record_marker: (),", tabs(1)));
                truncated = true;
            }
            if truncated {
                if is_union {
                    lines.push(format!("{}//union,", tabs(1)));
                }
                else {
                    let bits = field.bits.map(|n| format!(": {}", n)).unwrap_or("".into());
                    lines.push(
                        format!("{}//{}: {}{},", tabs(1), field.name,
                                field.c_type.as_ref().map(|s| &s[..]).unwrap_or("fn"), bits));
                };
                continue;
            }

            let vis = if field.private { "" } else { "pub " };

            if let Some(ref c_type) = field.c_type {
                let name = mangle_keywords(&*field.name);
                let c_type = ffi_type(env, field.typ, c_type);
                lines.push(format!("{}{}{}: {},", tabs(1), vis, name, c_type.as_str()));
                if c_type.is_err() {
                    commented = true;
                }
            }
            else {
                let name = mangle_keywords(&*field.name);
                if let Some(ref func) =
                        env.library.type_(field.typ).maybe_ref_as::<library::Function>() {
                    let (com, sig) = functions::function_signature(env, func, true);
                    lines.push(format!("{}{}{}: fn{},", tabs(1), vis, name, sig));
                    commented |= com;
                }
                else if let Some(c_type) = env.library.type_(field.typ).get_glib_name() {
                    warn!("Record `{}`, field `{}` missing c:type assumed `{}`",
                          record.name, field.name, c_type);
                    let c_type = ffi_type(env, field.typ, c_type);
                    lines.push(format!("{}{}{}: {},", tabs(1), vis, name, c_type.as_str()));
                    if c_type.is_err() {
                        commented = true;
                    }
                }
                else {
                    lines.push(format!("{}{}{}: [{:?} {}],", tabs(1),
                        vis, name, field.typ, field.typ.full_name(&env.library)));
                    commented = true;
                }
            }
        }
        let comment = if commented { "//" } else { "" };
        if lines.is_empty() {
            try!(writeln!(w, "{}#[repr(C)]\n{0}pub struct {};\n", comment, record.c_type));
        }
        else {
            try!(writeln!(w, "{}#[repr(C)]\n{0}pub struct {} {{", comment, record.c_type));
            for line in lines {
                try!(writeln!(w, "{}{}", comment, line));
            }
            try!(writeln!(w, "{}}}\n", comment));
        }
    }
    Ok(())
}
<|endoftext|>