<commit_before>//! Endpoints for getting and synchronizing events.

/// [GET /_matrix/client/r0/rooms/{roomId}/state](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state)
pub mod get_state_events {
    use ruma_api_macros::ruma_api;
    use ruma_events::collections::only;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get state events for a room.",
            method: Method::Get,
            name: "get_state_events",
            path: "/_matrix/client/r0/rooms/:room_id/state",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// The room to look up the state for.
            #[ruma_api(path)]
            pub room_id: RoomId,
        }

        response {
            /// If the user is a member of the room this will be the current state of the room as a
            /// list of events. If the user has left the room then this will be the state of the
            /// room when they left as a list of events.
            #[ruma_api(body)]
            pub room_state: Vec<only::StateEvent>,
        }
    }
}

/// [GET /_matrix/client/r0/rooms/{roomId}/state/{eventType}](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype)
pub mod get_state_events_for_empty_key {
    use ruma_api_macros::ruma_api;
    use ruma_events::EventType;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get state events of a given type associated with the empty key.",
            method: Method::Get,
            name: "get_state_events_for_empty_key",
            path: "/_matrix/client/r0/rooms/:room_id/state/:event_type",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// The room to look up the state for.
            #[ruma_api(path)]
            pub room_id: RoomId,
            /// The type of state to look up.
            #[ruma_api(path)]
            pub event_type: EventType,
        }

        response {
            /// The content of the state event.
            #[ruma_api(body)]
            pub content: ::serde_json::Value,
        }
    }
}

/// [GET /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-state-key)
pub mod get_state_events_for_key {
    use ruma_api_macros::ruma_api;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get state events associated with a given key.",
            method: Method::Get,
            name: "get_state_events_for_key",
            path: "/_matrix/client/r0/rooms/:room_id/state/:event_type/:state_key",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// The room to look up the state for.
            #[ruma_api(path)]
            pub room_id: RoomId,
            /// The type of state to look up.
            #[ruma_api(path)]
            pub event_type: String,
            /// The key of the state to look up.
            #[ruma_api(path)]
            pub state_key: String,
        }

        response {
            /// The content of the state event.
            #[ruma_api(body)]
            pub content: ::serde_json::Value,
        }
    }
}

/// [GET /_matrix/client/r0/rooms/{roomId}/members](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-members)
pub mod get_member_events {
    use ruma_api_macros::ruma_api;
    use ruma_events::room::member::MemberEvent;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get membership events for a room.",
            method: Method::Get,
            name: "get_member_events",
            path: "/_matrix/client/r0/rooms/:room_id/members",
            rate_limited: false,
            requires_authentication: false,
            // TODO: not marked as requiring auth in the spec, but
            // will return a 403 error is user is not a member of the
            // room anyway...
        }

        request {
            /// The room to get the member events for.
            #[ruma_api(path)]
            pub room_id: RoomId,
        }

        response {
            /// A list of member events.
            pub chunk: Vec<MemberEvent>
        }
    }
}

/// [GET /_matrix/client/r0/rooms/{roomId}/messages](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages)
pub mod get_message_events {
    use ruma_api_macros::ruma_api;
    use ruma_events::collections::only;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get message events for a room.",
            method: Method::Get,
            name: "get_message_events",
            path: "/_matrix/client/r0/rooms/:room_id/messages",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// The room to get events from.
            #[ruma_api(path)]
            pub room_id: RoomId,
            /// The token to start returning events from.
            ///
            /// This token can be obtained from a
            /// prev_batch token returned for each room by the sync API, or from a start or end token
            /// returned by a previous request to this endpoint.
            pub from: String,
            /// The token to stop returning events at.
            ///
            /// This token can be obtained from a prev_batch
            /// token returned for each room by the sync endpoint, or from a start or end token returned
            /// by a previous request to this endpoint.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub to: Option<String>,
            /// The direction to return events from.
            pub dir: Direction,
            /// The maximum number of events to return.
            ///
            /// Default: 10.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub limit: Option<u64>,
        }

        response {
            /// The token the pagination starts from.
            pub start: String,
            /// A list of room events.
            pub chunk: Vec<only::RoomEvent>,
            /// The token the pagination ends at.
            pub end: String,
        }
    }

    /// The direction to return events from.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub enum Direction {
        /// Return events backwards in time from the requested `from` token.
        #[serde(rename="b")]
        Backward,
        /// Return events forwards in time from the requested `from` token.
        #[serde(rename="f")]
        Forward,
    }
}

/// [GET /_matrix/client/r0/sync](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync)
pub mod sync_events {
    use std::collections::HashMap;

    use ruma_api_macros::ruma_api;
    use ruma_events::collections::{all, only};
    use ruma_identifiers::RoomId;

    use r0::filter::FilterDefinition;

    ruma_api! {
        metadata {
            description: "Get all new events from all rooms since the last sync or a given point of time.",
            method: Method::Get,
            name: "sync",
            path: "/_matrix/client/r0/sync",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// A filter represented either as its full JSON definition or the ID of a saved filter.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub filter: Option<Filter>,
            /// A point in time to continue a sync from.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub since: Option<String>,
            /// Controls whether to include the full state for all rooms the user is a member of.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub full_state: Option<bool>,
            /// Controls whether the client is automatically marked as online by polling this API.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub set_presence: Option<SetPresence>,
            /// The maximum time to poll in milliseconds before returning this request.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub timeout: Option<u64>,
        }

        response {
            /// The batch token to supply in the `since` param of the next `/sync` request.
            pub next_batch: String,
            /// Updates to rooms.
            pub rooms: Rooms,
            /// Updates to the presence status of other users.
            pub presence: Presence,
        }

    }

    /// Whether to set presence or not during sync.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub enum SetPresence {
        /// Do not set the presence of the user calling this API.
        #[serde(rename="offline")]
        Offline,
    }

    /// A filter represented either as its full JSON definition or the ID of a saved filter.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub enum Filter {
        /// A complete filter definition serialized to JSON.
        FilterDefinition(FilterDefinition),
        /// The ID of a filter saved on the server.
        FilterId(String),
    }

    /// Updates to rooms.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Rooms {
        /// The rooms that the user has left or been banned from.
        pub leave: HashMap<RoomId, LeftRoom>,
        /// The rooms that the user has joined.
        pub join: HashMap<RoomId, JoinedRoom>,
        /// The rooms that the user has been invited to.
        pub invite: HashMap<RoomId, InvitedRoom>,
    }

    /// Historical updates to left rooms.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct LeftRoom {
        /// The timeline of messages and state changes in the room up to the point when the user
        /// left.
        pub timeline: Timeline,
        /// The state updates for the room up to the start of the timeline.
        pub state: State,
    }

    /// Updates to joined rooms.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct JoinedRoom {
        /// Counts of unread notifications for this room.
        pub unread_notifications: UnreadNotificationsCount,
        /// The timeline of messages and state changes in the room.
        pub timeline: Timeline,
        /// Updates to the state, between the time indicated by the `since` parameter, and the start
        /// of the `timeline` (or all state up to the start of the `timeline`, if `since` is not
        /// given, or `full_state` is true).
        pub state: State,
        /// The private data that this user has attached to this room.
        pub account_data: AccountData,
        /// The ephemeral events in the room that aren't recorded in the timeline or state of the
        /// room. e.g. typing.
        pub ephemeral: Ephemeral,
    }

    /// unread notifications count
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct UnreadNotificationsCount {
        /// The number of unread notifications for this room with the highlight flag set.
        #[serde(skip_serializing_if = "Option::is_none")]
        pub highlight_count: Option<u64>,
        /// The total number of unread notifications for this room.
        #[serde(skip_serializing_if = "Option::is_none")]
        pub notification_count: Option<u64>,
    }

    /// Events in the room.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Timeline {
        /// True if the number of events returned was limited by the `limit` on the filter.
        pub limited: bool,
        /// A token that can be supplied to to the `from` parameter of the
        /// `/rooms/{roomId}/messages` endpoint.
        pub prev_batch: String,
        /// A list of events.
        pub events: Vec<all::RoomEvent>,
    }

    /// State events in the room.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct State {
        /// A list of state events.
        pub events: Vec<only::StateEvent>,
    }

    /// The private data that this user has attached to this room.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct AccountData {
        /// A list of events.
        pub events: Vec<only::Event>,
    }

    /// Ephemeral events not recorded in the timeline or state of the room.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Ephemeral {
        /// A list of events.
        pub events: Vec<only::Event>,
    }

    /// Updates to the rooms that the user has been invited to.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct InvitedRoom {
        /// The state of a room that the user has been invited to.
        pub invite_state: InviteState,
    }

    /// The state of a room that the user has been invited to.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct InviteState {
        /// A list of state events.
        pub events: Vec<only::StateEvent>,
    }

    /// Updates to the presence status of other users.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Presence {
        /// A list of events.
        pub events: Vec<only::Event>,
    }
}
<commit_msg>Pass sync request as query parameters instead of data<commit_after>//! Endpoints for getting and synchronizing events.

/// [GET /_matrix/client/r0/rooms/{roomId}/state](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state)
pub mod get_state_events {
    use ruma_api_macros::ruma_api;
    use ruma_events::collections::only;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get state events for a room.",
            method: Method::Get,
            name: "get_state_events",
            path: "/_matrix/client/r0/rooms/:room_id/state",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// The room to look up the state for.
            #[ruma_api(path)]
            pub room_id: RoomId,
        }

        response {
            /// If the user is a member of the room this will be the current state of the room as a
            /// list of events. If the user has left the room then this will be the state of the
            /// room when they left as a list of events.
            #[ruma_api(body)]
            pub room_state: Vec<only::StateEvent>,
        }
    }
}

/// [GET /_matrix/client/r0/rooms/{roomId}/state/{eventType}](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype)
pub mod get_state_events_for_empty_key {
    use ruma_api_macros::ruma_api;
    use ruma_events::EventType;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get state events of a given type associated with the empty key.",
            method: Method::Get,
            name: "get_state_events_for_empty_key",
            path: "/_matrix/client/r0/rooms/:room_id/state/:event_type",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// The room to look up the state for.
            #[ruma_api(path)]
            pub room_id: RoomId,
            /// The type of state to look up.
            #[ruma_api(path)]
            pub event_type: EventType,
        }

        response {
            /// The content of the state event.
            #[ruma_api(body)]
            pub content: ::serde_json::Value,
        }
    }
}

/// [GET /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-state-key)
pub mod get_state_events_for_key {
    use ruma_api_macros::ruma_api;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get state events associated with a given key.",
            method: Method::Get,
            name: "get_state_events_for_key",
            path: "/_matrix/client/r0/rooms/:room_id/state/:event_type/:state_key",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// The room to look up the state for.
            #[ruma_api(path)]
            pub room_id: RoomId,
            /// The type of state to look up.
            #[ruma_api(path)]
            pub event_type: String,
            /// The key of the state to look up.
            #[ruma_api(path)]
            pub state_key: String,
        }

        response {
            /// The content of the state event.
            #[ruma_api(body)]
            pub content: ::serde_json::Value,
        }
    }
}

/// [GET /_matrix/client/r0/rooms/{roomId}/members](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-members)
pub mod get_member_events {
    use ruma_api_macros::ruma_api;
    use ruma_events::room::member::MemberEvent;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get membership events for a room.",
            method: Method::Get,
            name: "get_member_events",
            path: "/_matrix/client/r0/rooms/:room_id/members",
            rate_limited: false,
            requires_authentication: false,
            // TODO: not marked as requiring auth in the spec, but
            // will return a 403 error is user is not a member of the
            // room anyway...
        }

        request {
            /// The room to get the member events for.
            #[ruma_api(path)]
            pub room_id: RoomId,
        }

        response {
            /// A list of member events.
            pub chunk: Vec<MemberEvent>
        }
    }
}

/// [GET /_matrix/client/r0/rooms/{roomId}/messages](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages)
pub mod get_message_events {
    use ruma_api_macros::ruma_api;
    use ruma_events::collections::only;
    use ruma_identifiers::RoomId;

    ruma_api! {
        metadata {
            description: "Get message events for a room.",
            method: Method::Get,
            name: "get_message_events",
            path: "/_matrix/client/r0/rooms/:room_id/messages",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// The room to get events from.
            #[ruma_api(path)]
            pub room_id: RoomId,
            /// The token to start returning events from.
            ///
            /// This token can be obtained from a
            /// prev_batch token returned for each room by the sync API, or from a start or end token
            /// returned by a previous request to this endpoint.
            pub from: String,
            /// The token to stop returning events at.
            ///
            /// This token can be obtained from a prev_batch
            /// token returned for each room by the sync endpoint, or from a start or end token returned
            /// by a previous request to this endpoint.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub to: Option<String>,
            /// The direction to return events from.
            pub dir: Direction,
            /// The maximum number of events to return.
            ///
            /// Default: 10.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub limit: Option<u64>,
        }

        response {
            /// The token the pagination starts from.
            pub start: String,
            /// A list of room events.
            pub chunk: Vec<only::RoomEvent>,
            /// The token the pagination ends at.
            pub end: String,
        }
    }

    /// The direction to return events from.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub enum Direction {
        /// Return events backwards in time from the requested `from` token.
        #[serde(rename="b")]
        Backward,
        /// Return events forwards in time from the requested `from` token.
        #[serde(rename="f")]
        Forward,
    }
}

/// [GET /_matrix/client/r0/sync](https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync)
pub mod sync_events {
    use std::collections::HashMap;

    use ruma_api_macros::ruma_api;
    use ruma_events::collections::{all, only};
    use ruma_identifiers::RoomId;

    use r0::filter::FilterDefinition;

    ruma_api! {
        metadata {
            description: "Get all new events from all rooms since the last sync or a given point of time.",
            method: Method::Get,
            name: "sync",
            path: "/_matrix/client/r0/sync",
            rate_limited: false,
            requires_authentication: true,
        }

        request {
            /// A filter represented either as its full JSON definition or the ID of a saved filter.
            #[serde(skip_serializing_if = "Option::is_none")]
            #[ruma_api(query)]
            pub filter: Option<Filter>,
            /// A point in time to continue a sync from.
            #[serde(skip_serializing_if = "Option::is_none")]
            #[ruma_api(query)]
            pub since: Option<String>,
            /// Controls whether to include the full state for all rooms the user is a member of.
            #[serde(skip_serializing_if = "Option::is_none")]
            #[ruma_api(query)]
            pub full_state: Option<bool>,
            /// Controls whether the client is automatically marked as online by polling this API.
            #[serde(skip_serializing_if = "Option::is_none")]
            #[ruma_api(query)]
            pub set_presence: Option<SetPresence>,
            /// The maximum time to poll in milliseconds before returning this request.
            #[serde(skip_serializing_if = "Option::is_none")]
            #[ruma_api(query)]
            pub timeout: Option<u64>,
        }

        response {
            /// The batch token to supply in the `since` param of the next `/sync` request.
            pub next_batch: String,
            /// Updates to rooms.
            pub rooms: Rooms,
            /// Updates to the presence status of other users.
            pub presence: Presence,
        }

    }

    /// Whether to set presence or not during sync.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub enum SetPresence {
        /// Do not set the presence of the user calling this API.
        #[serde(rename="offline")]
        Offline,
    }

    /// A filter represented either as its full JSON definition or the ID of a saved filter.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub enum Filter {
        /// A complete filter definition serialized to JSON.
        FilterDefinition(FilterDefinition),
        /// The ID of a filter saved on the server.
        FilterId(String),
    }

    /// Updates to rooms.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Rooms {
        /// The rooms that the user has left or been banned from.
        pub leave: HashMap<RoomId, LeftRoom>,
        /// The rooms that the user has joined.
        pub join: HashMap<RoomId, JoinedRoom>,
        /// The rooms that the user has been invited to.
        pub invite: HashMap<RoomId, InvitedRoom>,
    }

    /// Historical updates to left rooms.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct LeftRoom {
        /// The timeline of messages and state changes in the room up to the point when the user
        /// left.
        pub timeline: Timeline,
        /// The state updates for the room up to the start of the timeline.
        pub state: State,
    }

    /// Updates to joined rooms.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct JoinedRoom {
        /// Counts of unread notifications for this room.
        pub unread_notifications: UnreadNotificationsCount,
        /// The timeline of messages and state changes in the room.
        pub timeline: Timeline,
        /// Updates to the state, between the time indicated by the `since` parameter, and the start
        /// of the `timeline` (or all state up to the start of the `timeline`, if `since` is not
        /// given, or `full_state` is true).
        pub state: State,
        /// The private data that this user has attached to this room.
        pub account_data: AccountData,
        /// The ephemeral events in the room that aren't recorded in the timeline or state of the
        /// room. e.g. typing.
        pub ephemeral: Ephemeral,
    }

    /// unread notifications count
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct UnreadNotificationsCount {
        /// The number of unread notifications for this room with the highlight flag set.
        #[serde(skip_serializing_if = "Option::is_none")]
        pub highlight_count: Option<u64>,
        /// The total number of unread notifications for this room.
        #[serde(skip_serializing_if = "Option::is_none")]
        pub notification_count: Option<u64>,
    }

    /// Events in the room.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Timeline {
        /// True if the number of events returned was limited by the `limit` on the filter.
        pub limited: bool,
        /// A token that can be supplied to to the `from` parameter of the
        /// `/rooms/{roomId}/messages` endpoint.
        pub prev_batch: String,
        /// A list of events.
        pub events: Vec<all::RoomEvent>,
    }

    /// State events in the room.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct State {
        /// A list of state events.
        pub events: Vec<only::StateEvent>,
    }

    /// The private data that this user has attached to this room.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct AccountData {
        /// A list of events.
        pub events: Vec<only::Event>,
    }

    /// Ephemeral events not recorded in the timeline or state of the room.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Ephemeral {
        /// A list of events.
        pub events: Vec<only::Event>,
    }

    /// Updates to the rooms that the user has been invited to.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct InvitedRoom {
        /// The state of a room that the user has been invited to.
        pub invite_state: InviteState,
    }

    /// The state of a room that the user has been invited to.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct InviteState {
        /// A list of state events.
        pub events: Vec<only::StateEvent>,
    }

    /// Updates to the presence status of other users.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Presence {
        /// A list of events.
        pub events: Vec<only::Event>,
    }
}
<|endoftext|>


<commit_before>#[derive(Debug)]
pub struct Request {
    method: String,
    path: String,
    body: Option<String>
}

pub trait ToParams {
    fn to_params(&self) -> Vec<(&str, &str)>;
}

pub trait ToRequest {
    fn to_request(&self) -> Request;
}

impl Request {
    pub fn new(method: String, path: String, body: Option<String>) -> Request {
        Request { method, path, body }
    }

    pub fn method(&self) -> &str {
        &self.method
    }

    pub fn path(&self) -> &str {
        &self.path
    }

    pub fn body(&self) -> Option<&str> {
        match self.body {
            Some(ref body) => Some(body.as_str()),
            _ => None
        }
    }
}
<commit_msg>request: borrow method, params, and split path() into endpoint() & params()<commit_after>pub trait ToParams {
    fn to_params(&self) -> Vec<(&str, &str)>;
}

pub trait ToRequest {
    fn to_request(&self) -> Request;
}

#[derive(Debug)]
pub struct Request<'m, 'ep, 'key, 'value> {
    method: &'m str,
    endpoint: &'ep str,
    params: Vec<(&'key str, &'value str)>,
    body: Option<String>
}

impl<'m, 'ep, 'key, 'value> Request<'m, 'ep, 'key, 'value> {
    pub fn new(method: &'m str, endpoint: &'ep str, params: Vec<(&'key str, &'value str)>, body: Option<String>) -> Request<'m, 'ep, 'key, 'value> {
        Request { method, endpoint, params, body }
    }

    pub fn method(&self) -> &str {
        self.method
    }

    pub fn endpoint(&self) -> &str {
        self.endpoint
    }

    pub fn params(&self) -> &Vec<(&str, &str)> {
        self.params.as_ref()
    }

    pub fn url_params(&self) -> String {
        self.params.iter()
            .map(|&(param, value)| param.to_owned() + "=" + value)
            .collect::<Vec<String>>()
            .join("&")
    }

    pub fn path(&self) -> String {
        self.endpoint.to_owned() + "?" + self.url_params().as_str()
    }

    pub fn body(&self) -> Option<&str> {
        match self.body {
            Some(ref body) => Some(body.as_str()),
            _ => None
        }
    }
}
<|endoftext|>


<commit_before>use itertools::{multipeek, MultiPeek};
use std::str;

#[derive(Debug, PartialEq)]
pub enum Token {
    LeftParen,
    RightParen,
    LeftBrace,
    RightBrace,
    Comma,
    Dot,
    Minus,
    Plus,
    Semicolon,
    Slash,
    Star,
    Bang,
    BangEqual,
    Equal,
    EqualEqual,
    Greater,
    GreaterEqual,
    Less,
    LessEqual,
    Identifier(String),
    StringLiteral(String),
    NumberLiteral(f64),
    // Keywords
    And,
    Class,
    Else,
    False,
    Fun,
    For,
    If,
    Nil,
    Or,
    Print,
    Return,
    Super,
    This,
    True,
    Var,
    While,

    Eof,
    // The book doesn't have tokens for comments and
    // whitespaces. Introducing them the scanner can
    // deal with them uniformly and in a more
    // functional way.
    Comment,
    Whitespace,
}

#[derive(Debug)]
pub struct TokenWithContext {
    token: Token,
    // Takes a copy. Tokens can outlive the file they came from
    lexeme: String,
    line: usize,
}

struct Scanner<'a> {
    current: usize,
    current_lexeme: String,
    line: usize,
    source: MultiPeek<str::Chars<'a>>,
}

fn is_digit(c: char) -> bool {
    c >= '0' && c <= '9'
}

fn is_alpha(c: char) -> bool {
    (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
}

fn is_alphanumeric(c: char) -> bool {
    is_digit(c) || is_alpha(c)
}

fn is_whitespace(c: char) -> bool {
    match c {
        ' ' => true,
        '\r' => true,
        '\t' => true,
        '\n' => true,
        _ => false,
    }
}

impl<'a> Scanner<'a> {
    fn initialize(source: &String) -> Scanner {
        Scanner {
            current: 0,
            current_lexeme: "".into(),
            line: 1, // 1-indexed,
            source: multipeek(source.chars()),
        }
    }

    fn is_at_end(&mut self) -> bool {
        self.source.reset_peek();
        match self.source.peek() {
            Some(_) => false,
            None => true,
        }
    }

    fn peek_check(&mut self, check: &Fn(char) -> bool) -> bool {
        self.source.reset_peek();
        match self.source.peek() {
            Some(&c) => check(c),
            None => false,
        }
    }

    fn peek_check2(&mut self, check1: &Fn(char) -> bool, check2: &Fn(char) -> bool) -> bool {
        self.source.reset_peek();
        match self.source.peek() {
            Some(&p1) => {
                match self.source.peek() {
                    Some(&p2) => check1(p1) && check2(p2),
                    None => false,
                }
            }
            None => false,
        }
    }

    fn advance(&mut self) -> char {
        self.current += 1;
        // TODO: handle None!
        let c = self.source.next().unwrap();
        self.current_lexeme.push(c);
        if c == '\n' {
            self.line += 1;
        }
        c
    }

    fn is_match(&mut self, expected: char) -> bool {
        if self.peek_check(&|c| c == expected) {
            let _ = self.source.next();
            self.current += 1;
            true
        } else {
            false
        }
    }

    fn add_context(&mut self, token: Token) -> TokenWithContext {
        let result = TokenWithContext {
            token: token,
            lexeme: self.current_lexeme.clone(),
            line: self.line,
        };
        self.current_lexeme = "".into();
        result
    }

    fn string(&mut self) -> Result<Token, String> {
        let initial_line = self.line;
        self.source.reset_peek();
        while self.peek_check(&|c| c != '"') {
            self.advance();
        }
        if self.is_at_end() {
            return Err(format!("Unterminated string at line {}", initial_line));
        }
        self.advance();
        let literal_length = self.current_lexeme.len() - 2;
        // Trims delimiters
        let literal = self.current_lexeme.chars().skip(1).take(literal_length).collect();
        Ok(Token::StringLiteral(literal))
    }

    fn number(&mut self) -> Token {
        while self.peek_check(&is_digit) {
            self.advance();
        }
        if self.peek_check2(&|p1| p1 == '.', &is_digit) {
            self.advance(); // Consume the .
            while self.peek_check(&is_digit) {
                self.advance();
            }
        }
        let value = self.current_lexeme.parse::<f64>().unwrap();
        Token::NumberLiteral(value)
    }

    fn identifier(&mut self) -> Token {
        while self.peek_check(&is_alphanumeric) {
            self.advance();
        }
        // TODO: take a ref in the first place
        match self.current_lexeme.as_ref() {
            "and" => Token::And,
            "class" => Token::Class,
            "else" => Token::Else,
            "false" => Token::False,
            "for" => Token::For,
            "fun" => Token::Fun,
            "if" => Token::If,
            "nil" => Token::Nil,
            "or" => Token::Or,
            "print" => Token::Print,
            "return" => Token::Return,
            "super" => Token::Super,
            "this" => Token::This,
            "true" => Token::True,
            "var" => Token::Var,
            "while" => Token::While,
            identifier => Token::Identifier(identifier.into()),
        }

    }

    fn scan_next(&mut self) -> Result<TokenWithContext, String> {
        let token = match self.advance() {
            '(' => Token::LeftParen,
            ')' => Token::RightParen,
            '{' => Token::LeftBrace,
            '}' => Token::RightBrace,
            ',' => Token::Comma,
            '.' => Token::Dot,
            '-' => Token::Minus,
            '+' => Token::Plus,
            ';' => Token::Semicolon,
            '*' => Token::Star,
            '!' => {
                if self.is_match('=') {
                    Token::BangEqual
                } else {
                    Token::Bang
                }
            }
            '=' => {
                if self.is_match('=') {
                    Token::EqualEqual
                } else {
                    Token::Equal
                }
            }
            '<' => {
                if self.is_match('=') {
                    Token::LessEqual
                } else {
                    Token::Less
                }
            }
            '>' => {
                if self.is_match('=') {
                    Token::GreaterEqual
                } else {
                    Token::Greater
                }
            }
            '/' => {
                if self.is_match('/') {
                    // Comments go on till the end of the line
                    while self.peek_check(&|c| c != '\n') {
                        self.advance();
                    }
                    Token::Comment
                } else {
                    Token::Slash
                }
            }
            '"' => try!(self.string()),
            c if is_whitespace(c) => Token::Whitespace,
            c if is_digit(c) => self.number(),
            c if is_alpha(c) => self.identifier(),
            c => {
                return Err(format!("Unexpected character {} at line {}, pos {}",
                                   c,
                                   self.line,
                                   self.current - 1));
            }
        };
        Ok(self.add_context(token))
    }
}

pub fn scan(source: &String) -> Result<Vec<TokenWithContext>, String> {
    let mut scanner = Scanner::initialize(source);
    let mut tokens = Vec::new();
    while !scanner.is_at_end() {
        let token_with_context = try!(scanner.scan_next());
        match token_with_context.token {
            // Ignoring tokens we don't care about
            Token::Comment => {}
            Token::Whitespace => {}
            _ => tokens.push(token_with_context),
        }
    }
    tokens.push(TokenWithContext {
        token: Token::Eof,
        lexeme: "".into(),
        line: scanner.line,
    });
    Ok(tokens)
}

#[cfg(test)]
mod tests {
    use scanner::*;

    #[test]
    fn single_token() {
        let tokens = scan(&"+".into()).unwrap();
        assert_eq!(tokens[0].token, Token::Plus);
    }

    #[test]
    fn expression() {
        let tokens = scan(&"1+2".into()).unwrap();
        assert_eq!(tokens[0].token, Token::NumberLiteral(1.0f64));
        assert_eq!(tokens[1].token, Token::Plus);
        assert_eq!(tokens[2].token, Token::NumberLiteral(2.0f64));
        assert_eq!(tokens[3].token, Token::Eof);
    }

    #[test]
    fn expression_with_whitespaces() {
        let tokens = scan(&"1 + 2".into()).unwrap();
        assert_eq!(tokens[0].token, Token::NumberLiteral(1.0f64));
        assert_eq!(tokens[1].token, Token::Plus);
        assert_eq!(tokens[2].token, Token::NumberLiteral(2.0f64));
        assert_eq!(tokens[3].token, Token::Eof);
    }

    #[test]
    fn assignement_with_comment() {
        let tokens = scan(&"var a = 1.0; // A comment".into()).unwrap();
        assert_eq!(tokens[0].token, Token::Var);
        assert_eq!(tokens[1].token, Token::Identifier("a".into()));
        assert_eq!(tokens[2].token, Token::Equal);
        assert_eq!(tokens[3].token, Token::NumberLiteral(1.0f64));
        assert_eq!(tokens[4].token, Token::Semicolon);
        assert_eq!(tokens[5].token, Token::Eof);
    }

    #[test]
    fn multiline_statements() {
        let tokens = scan(&r#"var a = 1.0;
                              var b = "Hello";"#
                .into())
            .unwrap();
        assert_eq!(tokens[0].token, Token::Var);
        assert_eq!(tokens[1].token, Token::Identifier("a".into()));
        assert_eq!(tokens[2].token, Token::Equal);
        assert_eq!(tokens[3].token, Token::NumberLiteral(1.0f64));
        assert_eq!(tokens[4].token, Token::Semicolon);
        assert_eq!(tokens[5].token, Token::Var);
        assert_eq!(tokens[6].token, Token::Identifier("b".into()));
        assert_eq!(tokens[7].token, Token::Equal);
        assert_eq!(tokens[8].token, Token::StringLiteral("Hello".into()));
        assert_eq!(tokens[9].token, Token::Semicolon);
        assert_eq!(tokens[10].token, Token::Eof);
        assert_eq!(tokens[1].line, 1);
        assert_eq!(tokens[9].line, 2);
    }
}
<commit_msg>Renamed is_match to advance_if_match<commit_after>use itertools::{multipeek, MultiPeek};
use std::str;

#[derive(Debug, PartialEq)]
pub enum Token {
    LeftParen,
    RightParen,
    LeftBrace,
    RightBrace,
    Comma,
    Dot,
    Minus,
    Plus,
    Semicolon,
    Slash,
    Star,
    Bang,
    BangEqual,
    Equal,
    EqualEqual,
    Greater,
    GreaterEqual,
    Less,
    LessEqual,
    Identifier(String),
    StringLiteral(String),
    NumberLiteral(f64),
    // Keywords
    And,
    Class,
    Else,
    False,
    Fun,
    For,
    If,
    Nil,
    Or,
    Print,
    Return,
    Super,
    This,
    True,
    Var,
    While,

    Eof,
    // The book doesn't have tokens for comments and
    // whitespaces. Introducing them the scanner can
    // deal with them uniformly and in a more
    // functional way.
    Comment,
    Whitespace,
}

#[derive(Debug)]
pub struct TokenWithContext {
    token: Token,
    // Takes a copy. Tokens can outlive the file they came from
    lexeme: String,
    line: usize,
}

struct Scanner<'a> {
    current: usize,
    current_lexeme: String,
    line: usize,
    source: MultiPeek<str::Chars<'a>>,
}

fn is_digit(c: char) -> bool {
    c >= '0' && c <= '9'
}

fn is_alpha(c: char) -> bool {
    (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
}

fn is_alphanumeric(c: char) -> bool {
    is_digit(c) || is_alpha(c)
}

fn is_whitespace(c: char) -> bool {
    match c {
        ' ' => true,
        '\r' => true,
        '\t' => true,
        '\n' => true,
        _ => false,
    }
}

impl<'a> Scanner<'a> {
    fn initialize(source: &String) -> Scanner {
        Scanner {
            current: 0,
            current_lexeme: "".into(),
            line: 1, // 1-indexed,
            source: multipeek(source.chars()),
        }
    }

    fn is_at_end(&mut self) -> bool {
        self.source.reset_peek();
        match self.source.peek() {
            Some(_) => false,
            None => true,
        }
    }

    fn peek_check(&mut self, check: &Fn(char) -> bool) -> bool {
        self.source.reset_peek();
        match self.source.peek() {
            Some(&c) => check(c),
            None => false,
        }
    }

    fn peek_check2(&mut self, check1: &Fn(char) -> bool, check2: &Fn(char) -> bool) -> bool {
        self.source.reset_peek();
        match self.source.peek() {
            Some(&p1) => {
                match self.source.peek() {
                    Some(&p2) => check1(p1) && check2(p2),
                    None => false,
                }
            }
            None => false,
        }
    }

    fn advance(&mut self) -> char {
        self.current += 1;
        // TODO: handle None!
        let c = self.source.next().unwrap();
        self.current_lexeme.push(c);
        if c == '\n' {
            self.line += 1;
        }
        c
    }

    fn advance_if_match(&mut self, expected: char) -> bool {
        if self.peek_check(&|c| c == expected) {
            let _ = self.advance();
            true
        } else {
            false
        }
    }

    fn add_context(&mut self, token: Token) -> TokenWithContext {
        let result = TokenWithContext {
            token: token,
            lexeme: self.current_lexeme.clone(),
            line: self.line,
        };
        self.current_lexeme = "".into();
        result
    }

    fn string(&mut self) -> Result<Token, String> {
        let initial_line = self.line;
        self.source.reset_peek();
        while self.peek_check(&|c| c != '"') {
            self.advance();
        }
        if self.is_at_end() {
            return Err(format!("Unterminated string at line {}", initial_line));
        }
        self.advance();
        let literal_length = self.current_lexeme.len() - 2;
        // Trims delimiters
        let literal = self.current_lexeme.chars().skip(1).take(literal_length).collect();
        Ok(Token::StringLiteral(literal))
    }

    fn number(&mut self) -> Token {
        while self.peek_check(&is_digit) {
            self.advance();
        }
        if self.peek_check2(&|p1| p1 == '.', &is_digit) {
            self.advance(); // Consume the .
            while self.peek_check(&is_digit) {
                self.advance();
            }
        }
        let value = self.current_lexeme.parse::<f64>().unwrap();
        Token::NumberLiteral(value)
    }

    fn identifier(&mut self) -> Token {
        while self.peek_check(&is_alphanumeric) {
            self.advance();
        }
        // TODO: take a ref in the first place
        match self.current_lexeme.as_ref() {
            "and" => Token::And,
            "class" => Token::Class,
            "else" => Token::Else,
            "false" => Token::False,
            "for" => Token::For,
            "fun" => Token::Fun,
            "if" => Token::If,
            "nil" => Token::Nil,
            "or" => Token::Or,
            "print" => Token::Print,
            "return" => Token::Return,
            "super" => Token::Super,
            "this" => Token::This,
            "true" => Token::True,
            "var" => Token::Var,
            "while" => Token::While,
            identifier => Token::Identifier(identifier.into()),
        }

    }

    fn scan_next(&mut self) -> Result<TokenWithContext, String> {
        let token = match self.advance() {
            '(' => Token::LeftParen,
            ')' => Token::RightParen,
            '{' => Token::LeftBrace,
            '}' => Token::RightBrace,
            ',' => Token::Comma,
            '.' => Token::Dot,
            '-' => Token::Minus,
            '+' => Token::Plus,
            ';' => Token::Semicolon,
            '*' => Token::Star,
            '!' => {
                if self.advance_if_match('=') {
                    Token::BangEqual
                } else {
                    Token::Bang
                }
            }
            '=' => {
                if self.advance_if_match('=') {
                    Token::EqualEqual
                } else {
                    Token::Equal
                }
            }
            '<' => {
                if self.advance_if_match('=') {
                    Token::LessEqual
                } else {
                    Token::Less
                }
            }
            '>' => {
                if self.advance_if_match('=') {
                    Token::GreaterEqual
                } else {
                    Token::Greater
                }
            }
            '/' => {
                if self.advance_if_match('/') {
                    // Comments go on till the end of the line
                    while self.peek_check(&|c| c != '\n') {
                        self.advance();
                    }
                    Token::Comment
                } else {
                    Token::Slash
                }
            }
            '"' => try!(self.string()),
            c if is_whitespace(c) => Token::Whitespace,
            c if is_digit(c) => self.number(),
            c if is_alpha(c) => self.identifier(),
            c => {
                return Err(format!("Unexpected character {} at line {}, pos {}",
                                   c,
                                   self.line,
                                   self.current - 1));
            }
        };
        Ok(self.add_context(token))
    }
}

pub fn scan(source: &String) -> Result<Vec<TokenWithContext>, String> {
    let mut scanner = Scanner::initialize(source);
    let mut tokens = Vec::new();
    while !scanner.is_at_end() {
        let token_with_context = try!(scanner.scan_next());
        match token_with_context.token {
            // Ignoring tokens we don't care about
            Token::Comment => {}
            Token::Whitespace => {}
            _ => tokens.push(token_with_context),
        }
    }
    tokens.push(TokenWithContext {
        token: Token::Eof,
        lexeme: "".into(),
        line: scanner.line,
    });
    Ok(tokens)
}

#[cfg(test)]
mod tests {
    use scanner::*;

    #[test]
    fn single_token() {
        let tokens = scan(&"+".into()).unwrap();
        assert_eq!(tokens[0].token, Token::Plus);
    }

    #[test]
    fn expression() {
        let tokens = scan(&"1+2".into()).unwrap();
        assert_eq!(tokens[0].token, Token::NumberLiteral(1.0f64));
        assert_eq!(tokens[1].token, Token::Plus);
        assert_eq!(tokens[2].token, Token::NumberLiteral(2.0f64));
        assert_eq!(tokens[3].token, Token::Eof);
    }

    #[test]
    fn expression_with_whitespaces() {
        let tokens = scan(&"1 + 2".into()).unwrap();
        assert_eq!(tokens[0].token, Token::NumberLiteral(1.0f64));
        assert_eq!(tokens[1].token, Token::Plus);
        assert_eq!(tokens[2].token, Token::NumberLiteral(2.0f64));
        assert_eq!(tokens[3].token, Token::Eof);
    }

    #[test]
    fn assignement_with_comment() {
        let tokens = scan(&"var a = 1.0; // A comment".into()).unwrap();
        assert_eq!(tokens[0].token, Token::Var);
        assert_eq!(tokens[1].token, Token::Identifier("a".into()));
        assert_eq!(tokens[2].token, Token::Equal);
        assert_eq!(tokens[3].token, Token::NumberLiteral(1.0f64));
        assert_eq!(tokens[4].token, Token::Semicolon);
        assert_eq!(tokens[5].token, Token::Eof);
    }

    #[test]
    fn multiline_statements() {
        let tokens = scan(&r#"var a = 1.0;
                              var b = "Hello";"#
                .into())
            .unwrap();
        assert_eq!(tokens[0].token, Token::Var);
        assert_eq!(tokens[1].token, Token::Identifier("a".into()));
        assert_eq!(tokens[2].token, Token::Equal);
        assert_eq!(tokens[3].token, Token::NumberLiteral(1.0f64));
        assert_eq!(tokens[4].token, Token::Semicolon);
        assert_eq!(tokens[5].token, Token::Var);
        assert_eq!(tokens[6].token, Token::Identifier("b".into()));
        assert_eq!(tokens[7].token, Token::Equal);
        assert_eq!(tokens[8].token, Token::StringLiteral("Hello".into()));
        assert_eq!(tokens[9].token, Token::Semicolon);
        assert_eq!(tokens[10].token, Token::Eof);
        assert_eq!(tokens[1].line, 1);
        assert_eq!(tokens[9].line, 2);
    }
}
<|endoftext|>


<commit_before>extern crate git2;

use std::env;
use rustc_serialize::json;
use std::fs;
use std::path::{Path, PathBuf};
use git2::{Repository, Error, Signature, StatusOptions, ErrorCode};
use std::fs::File;
use std::io::prelude::*;
use glob::glob;

#[derive(RustcEncodable, RustcDecodable, Debug, Clone)]
enum RepositoryType {
    Github,
}

#[derive(RustcEncodable, RustcDecodable, Debug, Clone)]
pub struct RepositoryDescriptor {
    name: String,
    url: Option<String>,
    repo_type: RepositoryType
}

#[derive(RustcEncodable, RustcDecodable, Debug, Clone)]
pub struct Job {
    name: Option<String>,
    description: Option<String>,
    repository: Option<RepositoryDescriptor>
}

#[derive(Debug)]
pub struct Storage {
    git_local_repo_path: PathBuf,
    path: PathBuf,
}

impl Storage {

    pub fn new() -> Storage {
        let path = env::current_exe().ok().unwrap().parent().unwrap().to_path_buf();

        info!("{}", path.display());

        let mut git_local_repo_path = path.clone();
        git_local_repo_path.push(".data/");

        Storage {path: path, git_local_repo_path: git_local_repo_path}
    }

    pub fn bootstrap(&self) {
        info!("does file exist ? {:?}", self.path);
        //let mut git_local_repo_path = self.path.clone();
        //git_local_repo_path.push(".data/");
        if !self.exists() {
            info!("creating storage directory {:?}", self.git_local_repo_path);
            let x = fs::create_dir_all(self.git_local_repo_path.as_path());
            info!("create file result {:?}", x);

            match Repository::init(self.git_local_repo_path.as_path()) {
                Ok(_) => info!("repo initialized successfully" ),
                Err(e) => panic!("failed to init: {}", e),
            };
        }
        else {
            info!("skipped repo initialization, directory already exists.");
        }

        let repo = match Repository::open(self.git_local_repo_path.as_path()) {
            Ok(repo) => repo,
            Err(e) => panic!("failed to init: {}", e),
        };
        info!("opened repo {:?}", repo.path());

        let statuses = repo.statuses(None);
        info!("statuses {}", statuses.unwrap().len());
        info!("bootstrap done");
    }

    fn path(&self) -> PathBuf  {
        let mut path = self.path.clone();
        path.push(".data");
        return path;
    }

    // fn repo<'a>(&self) -> &'a Repository {
    //     let repo: &Repository = match Repository::open(self.git_local_repo_path.as_path()) {
    //         Ok(x) => &x,
    //         Err(e) => panic!("failed to init: {}", e),
    //     };
    //     info!("opened repo {:?}", repo.path());
    //     return &repo;
    // }

   fn show_statuses(&self, repo: &Repository) {
       let mut opts = StatusOptions::new();
        opts.include_ignored(false).include_untracked(true);
       //let statuses = try!(repo.statuses(None));
       match repo.statuses(Some(&mut opts)) {
           Ok(statuses) => {
               for entry in statuses.iter().filter(|e| e.status() != git2::STATUS_CURRENT) {
                   let path = entry.path();
                   info!("path: {}", path.unwrap_or(""));

                   let status = match entry.status() {
                    s if s.contains(git2::STATUS_INDEX_NEW) => "new file: ",
                    s if s.contains(git2::STATUS_INDEX_MODIFIED) => "modified: ",
                    s if s.contains(git2::STATUS_INDEX_DELETED) => "deleted: ",
                    s if s.contains(git2::STATUS_INDEX_RENAMED) => "renamed: ",
                    s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => "typechange:",
                    s if s.contains(git2::STATUS_WT_NEW) => "wt new: ",
                    _ => continue,
                   };

                   info!("{} {}", path.unwrap_or(""), status);
               }

               info!("status length {}", statuses.len());
           },
           _ => panic!("failed to get statuses")
       }
   }

    fn stage(&self, repo: &Repository, path: &Path) {
        let mut index = match repo.index() {
            Ok(index) => index,
            Err(e) => panic!("failed to get index {}", e)
        };

        //index.add_path(path);
        let path_spec = vec!("*.json");

        let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
            let status = repo.status_file(path).unwrap();
            let ret = if status.contains(git2::STATUS_WT_MODIFIED) ||
                status.contains(git2::STATUS_WT_NEW) {
                    info!("add '{}'", path.display());
                    0
                } else {
                    1
                };

            ret
        };

        let cb = cb as &mut git2::IndexMatchedPath;

        index.add_all(path_spec.iter(), git2::ADD_DEFAULT, Some(cb));

        info!("added {:?}", path);
        self.show_statuses(&repo);
    }

    fn commit(&self, repo: &Repository) {
        let mut index = repo.index().unwrap();
        let id = index.write_tree().unwrap();
        let tree = repo.find_tree(id).unwrap();
        let sig = repo.signature().unwrap();

        repo.commit(Some("HEAD"), &sig, &sig, "automated commit", &tree, &[]).unwrap();
    }

    fn exists(&self) -> bool {
        return fs::metadata(self.path()).is_ok();
    }

    pub fn save(&self, job: Job) {
        let job_as_json = json::as_pretty_json(&job);
        let mut job_as_json_path = PathBuf::from(self.git_local_repo_path.clone());
        job_as_json_path.push("job1.json");
        info!("adding...{}", job_as_json);
        info!("job_as_json_path {}", job_as_json_path.display());

        match File::create(job_as_json_path.as_path()) {
            Ok(ref mut file) => {
                info!("created file, ready for writing");
                write!(file, "{}", job_as_json)
            },
            Err(e) => panic!("unable to create file for writing {}", e)
        };

        //let repo = self.repo();
         let repo = match Repository::open(self.git_local_repo_path.as_path()) {
            Ok(x) => x,
            Err(e) => panic!("failed to init: {}", e),
        };
        info!("opened repo {:?}", repo.path());

        self.show_statuses(&repo);

        self.stage(&repo, job_as_json_path.as_path());

        //self.commit(&repo);

    }

    pub fn list(&self) {
        let path = self.git_local_repo_path.to_string_lossy();
        info!("path {}", path);
        // path.push_str("*");
        // info!("path {}", path);
        for entry in glob(&format!("{}/*", path)).unwrap() {
            match entry {
                Ok(p) => info!("{:?}", p),

                // if the path matched but was unreadable,
                // thereby preventing its contents from matching
                Err(e) => println!("{:?}", e)
            }
        }
    }
}

pub fn bootstrap() -> Storage {
    let storage = Storage::new();

    storage.bootstrap();
    storage.save(Job{
        name: Some("test".to_string()),
        description: Some("dsc".to_string()),
        repository: None });
    return storage;
}
<commit_msg>Success!! bootstrap, git commit initial commit, and successfully added first job file.<commit_after>extern crate git2;

use std::env;
use rustc_serialize::json;
use std::fs;
use std::path::{Path, PathBuf};
use git2::{Repository, Error, Signature, StatusOptions, ErrorCode};
use std::fs::File;
use std::io::prelude::*;
use glob::glob;

#[derive(RustcEncodable, RustcDecodable, Debug, Clone)]
enum RepositoryType {
    Github,
}

const REPO_DIR: &'static str = "bldr-repo-data";

#[derive(RustcEncodable, RustcDecodable, Debug, Clone)]
pub struct RepositoryDescriptor {
    name: String,
    url: Option<String>,
    repo_type: RepositoryType
}

#[derive(RustcEncodable, RustcDecodable, Debug, Clone)]
pub struct Job {
    name: Option<String>,
    description: Option<String>,
    repository: Option<RepositoryDescriptor>
}

#[derive(Debug)]
pub struct Storage {
    git_local_repo_path: PathBuf,
    path: PathBuf,
}

impl Storage {

    pub fn new() -> Storage {
        //let path = env::current_exe().ok().unwrap().parent().unwrap().to_path_buf();

        let cwd = env::current_dir().unwrap();
        info!("The current directory is {}", cwd.display());

        //info!("{}", path.display());

        let mut git_local_repo_path = cwd.clone(); //path.clone();
        git_local_repo_path.push(REPO_DIR);

        Storage {path: cwd, git_local_repo_path: git_local_repo_path}
    }

    pub fn bootstrap(&self) {
        info!("does file exist ? {:?}", self.path);
        if !self.exists() {
            info!("creating storage directory {:?}", self.git_local_repo_path);
            let x = fs::create_dir_all(self.git_local_repo_path.as_path());
            info!("create file result {:?}", x);

            match Repository::init(self.git_local_repo_path.as_path()) {
                Ok(_) => {
                    info!("repo initialized successfully" );
                    info!("Created empty initial commit");

                },
                Err(e) => panic!("failed to init: {}", e),
            };
        }
        else {
            info!("skipped repo initialization, directory already exists.");
        }

        let repo = match Repository::open(self.git_local_repo_path.as_path()) {
            Ok(repo) => repo,
            Err(e) => panic!("failed to init: {}", e),
        };
        info!("opened repo {:?}", repo.path());
        self.create_initial_commit(&repo);

        let statuses = repo.statuses(None);
        info!("statuses {}", statuses.unwrap().len());
        info!("bootstrap done");
    }

    fn path(&self) -> PathBuf  {
        let mut path = self.path.clone();
        path.push(REPO_DIR);
        return path;
    }

    // fn repo<'a>(&self) -> &'a Repository {
    //     let repo: &Repository = match Repository::open(self.git_local_repo_path.as_path()) {
    //         Ok(x) => &x,
    //         Err(e) => panic!("failed to init: {}", e),
    //     };
    //     info!("opened repo {:?}", repo.path());
    //     return &repo;
    // }

   fn show_statuses(&self, repo: &Repository) {
       let mut opts = StatusOptions::new();
        opts.include_ignored(false).include_untracked(true);
       //let statuses = try!(repo.statuses(None));
       match repo.statuses(Some(&mut opts)) {
           Ok(statuses) => {
               for entry in statuses.iter().filter(|e| e.status() != git2::STATUS_CURRENT) {
                   let path = entry.path();
                   info!("path: {}", path.unwrap_or(""));

                   let status = match entry.status() {
                    s if s.contains(git2::STATUS_INDEX_NEW) => "new file: ",
                    s if s.contains(git2::STATUS_INDEX_MODIFIED) => "modified: ",
                    s if s.contains(git2::STATUS_INDEX_DELETED) => "deleted: ",
                    s if s.contains(git2::STATUS_INDEX_RENAMED) => "renamed: ",
                    s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => "typechange:",
                    s if s.contains(git2::STATUS_WT_NEW) => "wt new: ",
                    _ => continue,
                   };

                   info!("{} {}", path.unwrap_or(""), status);
               }

               info!("status length {}", statuses.len());
           },
           _ => panic!("failed to get statuses")
       }
   }

    fn stage(&self, repo: &Repository, path: &Path) {
        info!("stage");
        let mut index = match repo.index() {
            Ok(index) => index,
            Err(e) => panic!("failed to get index {}", e)
        };

        //index.add_path(path);
        let path_spec = vec!("*.json");

        let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
            let status = repo.status_file(path).unwrap();
            let ret = if status.contains(git2::STATUS_WT_MODIFIED) ||
                status.contains(git2::STATUS_WT_NEW) {
                    info!("add '{}'", path.display());
                    0
                } else {
                    1
                };

            ret
        };

        let cb = cb as &mut git2::IndexMatchedPath;

        index.add_all(path_spec.iter(), git2::ADD_DEFAULT, Some(cb));

        info!("added {:?}", path);
        self.show_statuses(&repo);

        match index.write() {
            Ok(_) => info!("index write ok."),
            Err(e) => panic!("Error: failed to write index {}", e)
        };
    }

    fn commit(&self, repo: &Repository) {
        info!("commit");
        let mut index = repo.index().unwrap();
        let id = index.write_tree().unwrap();

        let head = repo.refname_to_id("HEAD").unwrap();
        //info!("HEAD: {}", head);
         match repo.find_commit(head) {
            Ok(commit) => {
            info!("commit: {}", commit.id());
            // let parents = commit.parents().len();
            // info!("HEAD: {:?}, parents: {}", id, parents);

                let tree = repo.find_tree(id).unwrap();
                let sig = repo.signature().unwrap();
                repo.commit(Some("HEAD"), &sig, &sig, "automated commit", &tree, &[&commit]).unwrap();
            },
             Err(e) => panic!("error {}", e)
         }
    }

    /// Unlike regular "git init", this example shows how to create an initial empty
    /// commit in the repository. This is the helper function that does that.
    fn create_initial_commit(&self, repo: &Repository) -> Result<(), Error> {
        // First use the config to initialize a commit signature for the user.
        let sig = try!(repo.signature());

        // Now let's create an empty tree for this commit
        let tree_id = {
            let mut index = try!(repo.index());

            // Outside of this example, you could call index.add_path()
            // here to put actual files into the index. For our purposes, we'll
            // leave it empty for now.

            try!(index.write_tree())
        };

        let tree = try!(repo.find_tree(tree_id));

        // Ready to create the initial commit.
        //
        // Normally creating a commit would involve looking up the current HEAD
        // commit and making that be the parent of the initial commit, but here this
        // is the first commit so there will be no parent.
        try!(repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[]));

        Ok(())
    }

    fn exists(&self) -> bool {
        return fs::metadata(self.path()).is_ok();
    }

    pub fn save(&self, job: Job) {
        info!("save");
        let job_as_json = json::as_pretty_json(&job);
        let mut job_as_json_path = PathBuf::from(self.git_local_repo_path.clone());
        job_as_json_path.push("job1.json");
        info!("adding...{}", job_as_json);
        info!("job_as_json_path {}", job_as_json_path.display());

        match File::create(job_as_json_path.as_path()) {
            Ok(ref mut file) => {
                info!("created file, ready for writing");
                write!(file, "{}", job_as_json)
            },
            Err(e) => panic!("unable to create file for writing {}", e)
        };

        //let repo = self.repo();
         let repo = match Repository::open(self.git_local_repo_path.as_path()) {
            Ok(x) => x,
            Err(e) => panic!("failed to init: {}", e),
        };
        info!("opened repo {:?}", repo.path());

        // http://stackoverflow.com/questions/27672722/libgit2-commit-example
        // Get the index and write it to a tree */
        // git_repository_index(&index, repo);
        // git_index_write_tree(tree, index);
        // git_reference_name_to_id(parent_id, repo, "HEAD");
        // git_commit_lookup(&parent, repo, parent_id);

        self.show_statuses(&repo);

        self.stage(&repo, job_as_json_path.as_path());

        self.commit(&repo);

    }

    pub fn list(&self) {
        let path = self.git_local_repo_path.to_string_lossy();
        info!("path {}", path);
        // path.push_str("*");
        // info!("path {}", path);
        for entry in glob(&format!("{}/*", path)).unwrap() {
            match entry {
                Ok(p) => info!("{:?}", p),

                // if the path matched but was unreadable,
                // thereby preventing its contents from matching
                Err(e) => println!("{:?}", e)
            }
        }
    }
}

pub fn bootstrap() -> Storage {
    let storage = Storage::new();

    storage.bootstrap();
    storage.save(Job{
        name: Some("test".to_string()),
        description: Some("dsc".to_string()),
        repository: None });
    return storage;
}
<|endoftext|>


<commit_before>//! Create textures and build texture atlas.

use gfx::CommandBuffer;
use gfx::Device;
use image;
use image::{ GenericImage, ImageBuffer, RgbaImage, Pixel, SubImage };
use std::num::Float;
use std::collections::HashMap;
use std::collections::hash_map::Entry::{ Occupied, Vacant };
use std::mem;

pub use gfx_texture::Texture;

/// Loads RGBA image from path.
fn load_rgba8(path: &Path) -> Result<RgbaImage, String> {
    Ok(match image::open(path) {
        Ok(image::ImageRgba8(img)) => img,
        Ok(image::ImageRgb8(img)) => {
            let (w, h) = img.dimensions();
            // We're forced to use Box::new on the closure because ImageBuffer
            // is defined in the "image" crate.
            ImageBuffer::from_fn(w, h, Box::new(|&: x, y| img.get_pixel(x, y).to_rgba()))
        }
        Ok(img) => {
            return Err(format!("Unsupported color type {:?} in '{}'",
                img.color(), path.display()));
        }
        Err(e)  => {
            return Err(format!("Could not load '{}': {:?}", path.display(), e));
        }
    })
}

/// A 256x256 image that stores colors.
pub struct ColorMap {
    image: RgbaImage
}

impl ColorMap {
    /// Creates a new `ColorMap` from path.
    pub fn from_path(path: &Path) -> Result<ColorMap, String> {
        let img = try!(load_rgba8(path));

        match img.dimensions() {
            (256, 256) => Ok(ColorMap {image: img}),
            (w, h) => Err(format!("ColorMap expected 256x256, found {}x{} in '{}'",
                                  w, h, path.display()))
        }
    }

    /// Gets RGB color from the color map.
    pub fn get(&self, x: f32, y: f32) -> [u8; 3] {
        // Clamp to [0.0, 1.0].
        let x = x.max(0.0).min(1.0);
        let y = y.max(0.0).min(1.0);

        // Scale y from [0.0, 1.0] to [0.0, x], forming a triangle.
        let y = x * y;

        // Origin is in the bottom-right corner.
        let x = ((1.0 - x) * 255.0) as u8;
        let y = ((1.0 - y) * 255.0) as u8;

        let col = self.image.get_pixel(x as u32, y as u32).channels();
        [col[0], col[1], col[2]]
    }
}

/// Builds an atlas of textures.
pub struct AtlasBuilder {
    image: RgbaImage,
    // Base path for loading tiles.
    path: Path,
    // Size of an individual tile.
    unit_width: u32,
    unit_height: u32,
    // Size of the entirely occupied square, in tiles.
    completed_tiles_size: u32,
    // Position in the current strip.
    position: u32,
    // Position cache for loaded tiles (in pixels).
    tile_positions: HashMap<String, (u32, u32)>,
    // Lowest-alpha cache for rectangles in the atlas.
    min_alpha_cache: HashMap<(u32, u32, u32, u32), u8>
}

impl AtlasBuilder {
    /// Creates a new `AtlasBuilder`.
    pub fn new(path: Path, unit_width: u32, unit_height: u32) -> AtlasBuilder {
        AtlasBuilder {
            image: ImageBuffer::new(unit_width * 4, unit_height * 4),
            path: path,
            unit_width: unit_width,
            unit_height: unit_height,
            completed_tiles_size: 0,
            position: 0,
            tile_positions: HashMap::new(),
            min_alpha_cache: HashMap::new()
        }
    }

    /// Loads a file into the texture atlas.
    /// Checks if the file is loaded and returns position within the atlas.
    /// The name should be specified without file extension.
    /// PNG is the only supported format.
    pub fn load(&mut self, name: &str) -> (u32, u32) {
        match self.tile_positions.get(name) {
            Some(pos) => return *pos,
            None => {}
        }

        let mut path = self.path.join(name);
        path.set_extension("png");
        let img = load_rgba8(&path).unwrap();

        let (iw, ih) = img.dimensions();
        assert!(iw == self.unit_width);
        assert!((ih % self.unit_height) == 0);
        if ih > self.unit_height {
            println!("ignoring {} extra frames in '{}'", (ih / self.unit_height) - 1, name);
        }

        let (uw, uh) = (self.unit_width, self.unit_height);
        let (w, h) = self.image.dimensions();
        let size = self.completed_tiles_size;

        // Expand the image buffer if necessary.
        if self.position == 0 && (uw * size >= w || uh * size >= h) {
            let old = mem::replace(&mut self.image, ImageBuffer::new(w * 2, h * 2));
            for ix in range(0, w) {
                for iy in range(0, h) {
                    *self.image.get_pixel_mut(ix, iy) = old[(ix, iy)];
                }
            }

            /*
            let mut dest = SubImage::new(&mut self.image, 0, 0, w, h);
            for ((_, _, a), b) in dest.pixels_mut().zip(old.pixels()) {
                *a = *b;
            }
            */
        }

        let (x, y) = if self.position < size {
            (self.position, size)
        } else {
            (size, self.position - size)
        };

        self.position += 1;
        if self.position >= size * 2 + 1 {
            self.position = 0;
            self.completed_tiles_size += 1;
        }

        {
            let [x, y, w, h] = [x * uw, y * uh, uw, uh];
            for ix in range(0, w) {
                for iy in range(0, h) {
                    *self.image.get_pixel_mut(ix + x, iy + y) = img[(ix, iy)];
                }
            }
        }

        /*
        let mut dest = SubImage::new(&mut self.image, x * uw, y * uh, uw, uh);
        for ((_, _, a), b) in dest.pixels_mut().zip(img.pixels()) {
            *a = *b;
        }
        */

        *match self.tile_positions.entry(name.to_string()) {
            Occupied(entry) => entry.into_mut(),
            Vacant(entry) => entry.insert((x * uw, y * uh))
        }
    }

    /// Finds the minimum alpha value in a given sub texture of the image.
    pub fn min_alpha(&mut self, rect: [u32; 4]) -> u8 {
        let [x, y, w, h] = rect;
        match self.min_alpha_cache.get(&(x, y, w, h)) {
            Some(alpha) => return *alpha,
            None => {}
        }

        let tile = SubImage::new(&mut self.image, x, y, w, h);
        let min_alpha = tile.pixels().map(|(_, _, p)| p[3])
            .min().unwrap_or(0);
        self.min_alpha_cache.insert((x, y, w, h), min_alpha);
        min_alpha
    }

    /// Returns the complete texture atlas as a texture.
    pub fn complete<D: Device<C>, C: CommandBuffer>(self, d: &mut D) -> Texture {
        Texture::from_rgba8(self.image, d)
    }
}
<commit_msg>Update to latest Rust.<commit_after>//! Create textures and build texture atlas.

use gfx::CommandBuffer;
use gfx::Device;
use image;
use image::{ GenericImage, ImageBuffer, RgbaImage, Pixel, SubImage };
use std::num::Float;
use std::collections::HashMap;
use std::collections::hash_map::Entry::{ Occupied, Vacant };
use std::mem;

pub use gfx_texture::Texture;

/// Loads RGBA image from path.
fn load_rgba8(path: &Path) -> Result<RgbaImage, String> {
    Ok(match image::open(path) {
        Ok(image::ImageRgba8(img)) => img,
        Ok(image::ImageRgb8(img)) => {
            let (w, h) = img.dimensions();
            // We're forced to use Box::new on the closure because ImageBuffer
            // is defined in the "image" crate.
            ImageBuffer::from_fn(w, h, Box::new(|&: x, y| img.get_pixel(x, y).to_rgba()))
        }
        Ok(img) => {
            return Err(format!("Unsupported color type {:?} in '{}'",
                img.color(), path.display()));
        }
        Err(e)  => {
            return Err(format!("Could not load '{}': {:?}", path.display(), e));
        }
    })
}

/// A 256x256 image that stores colors.
pub struct ColorMap {
    image: RgbaImage
}

impl ColorMap {
    /// Creates a new `ColorMap` from path.
    pub fn from_path(path: &Path) -> Result<ColorMap, String> {
        let img = try!(load_rgba8(path));

        match img.dimensions() {
            (256, 256) => Ok(ColorMap {image: img}),
            (w, h) => Err(format!("ColorMap expected 256x256, found {}x{} in '{}'",
                                  w, h, path.display()))
        }
    }

    /// Gets RGB color from the color map.
    pub fn get(&self, x: f32, y: f32) -> [u8; 3] {
        // Clamp to [0.0, 1.0].
        let x = x.max(0.0).min(1.0);
        let y = y.max(0.0).min(1.0);

        // Scale y from [0.0, 1.0] to [0.0, x], forming a triangle.
        let y = x * y;

        // Origin is in the bottom-right corner.
        let x = ((1.0 - x) * 255.0) as u8;
        let y = ((1.0 - y) * 255.0) as u8;

        let col = self.image.get_pixel(x as u32, y as u32).channels();
        [col[0], col[1], col[2]]
    }
}

/// Builds an atlas of textures.
pub struct AtlasBuilder {
    image: RgbaImage,
    // Base path for loading tiles.
    path: Path,
    // Size of an individual tile.
    unit_width: u32,
    unit_height: u32,
    // Size of the entirely occupied square, in tiles.
    completed_tiles_size: u32,
    // Position in the current strip.
    position: u32,
    // Position cache for loaded tiles (in pixels).
    tile_positions: HashMap<String, (u32, u32)>,
    // Lowest-alpha cache for rectangles in the atlas.
    min_alpha_cache: HashMap<(u32, u32, u32, u32), u8>
}

impl AtlasBuilder {
    /// Creates a new `AtlasBuilder`.
    pub fn new(path: Path, unit_width: u32, unit_height: u32) -> AtlasBuilder {
        AtlasBuilder {
            image: ImageBuffer::new(unit_width * 4, unit_height * 4),
            path: path,
            unit_width: unit_width,
            unit_height: unit_height,
            completed_tiles_size: 0,
            position: 0,
            tile_positions: HashMap::new(),
            min_alpha_cache: HashMap::new()
        }
    }

    /// Loads a file into the texture atlas.
    /// Checks if the file is loaded and returns position within the atlas.
    /// The name should be specified without file extension.
    /// PNG is the only supported format.
    pub fn load(&mut self, name: &str) -> (u32, u32) {
        match self.tile_positions.get(name) {
            Some(pos) => return *pos,
            None => {}
        }

        let mut path = self.path.join(name);
        path.set_extension("png");
        let img = load_rgba8(&path).unwrap();

        let (iw, ih) = img.dimensions();
        assert!(iw == self.unit_width);
        assert!((ih % self.unit_height) == 0);
        if ih > self.unit_height {
            println!("ignoring {} extra frames in '{}'", (ih / self.unit_height) - 1, name);
        }

        let (uw, uh) = (self.unit_width, self.unit_height);
        let (w, h) = self.image.dimensions();
        let size = self.completed_tiles_size;

        // Expand the image buffer if necessary.
        if self.position == 0 && (uw * size >= w || uh * size >= h) {
            let old = mem::replace(&mut self.image, ImageBuffer::new(w * 2, h * 2));
            for ix in range(0, w) {
                for iy in range(0, h) {
                    *self.image.get_pixel_mut(ix, iy) = old[(ix, iy)];
                }
            }

            /*
            let mut dest = SubImage::new(&mut self.image, 0, 0, w, h);
            for ((_, _, a), b) in dest.pixels_mut().zip(old.pixels()) {
                *a = *b;
            }
            */
        }

        let (x, y) = if self.position < size {
            (self.position, size)
        } else {
            (size, self.position - size)
        };

        self.position += 1;
        if self.position >= size * 2 + 1 {
            self.position = 0;
            self.completed_tiles_size += 1;
        }

        {
            let [x, y, w, h] = [x * uw, y * uh, uw, uh];
            for ix in range(0, w) {
                for iy in range(0, h) {
                    *self.image.get_pixel_mut(ix + x, iy + y) = img[(ix, iy)];
                }
            }
        }

        /*
        let mut dest = SubImage::new(&mut self.image, x * uw, y * uh, uw, uh);
        for ((_, _, a), b) in dest.pixels_mut().zip(img.pixels()) {
            *a = *b;
        }
        */

        *match self.tile_positions.entry(name.to_string()) {
            Occupied(entry) => entry.into_mut(),
            Vacant(entry) => entry.insert((x * uw, y * uh))
        }
    }

    /// Finds the minimum alpha value in a given sub texture of the image.
    pub fn min_alpha(&mut self, rect: [u32; 4]) -> u8 {
        let [x, y, w, h] = rect;
        match self.min_alpha_cache.get(&(x, y, w, h)) {
            Some(alpha) => return *alpha,
            None => {}
        }

        let tile = SubImage::new(&mut self.image, x, y, w, h);
        let min_alpha = tile.pixels().map(|(_, _, p)| p[3])
            .min().unwrap_or(0);
        self.min_alpha_cache.insert((x, y, w, h), min_alpha);
        min_alpha
    }

    /// Returns the complete texture atlas as a texture.
    pub fn complete<D: Device>(self, d: &mut D) -> Texture {
        Texture::from_rgba8::<D>(self.image, d)
    }
}
<|endoftext|>


<commit_before>
//! Utilities for loading and using textures

use png;
use gl;
use gl::types::*;
use std::io;
use std::path::Path;
use std::fs::File;

/// A wraper around a OpenGL texture object which can be modified
#[derive(Debug)]
pub struct Texture {
    texture: GLuint,
    pub format: TextureFormat,
    pub width: u32,
    pub height: u32,
}

impl Texture {
    /// Creates a texture from a image file.
    pub fn from_file<P>(path: P) -> io::Result<Texture> where P: AsRef<Path> {
        let mut texture = Texture::new();
        texture.load_file(path)?;
        Ok(texture)
    }

    /// Creates a new texture without any ascociated data
    pub fn new() -> Texture {
        let mut texture = 0;

        unsafe {
            gl::GenTextures(1, &mut texture);
            gl::BindTexture(gl::TEXTURE_2D, texture);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint);
        }

        Texture {
            texture: texture,
            format: TextureFormat::RGB_8,
            width: 0,
            height: 0,
        }
    }

    /// Attempts to load data from the given image file into this texture. Note that
    /// it is usually more convenient to create a new texture from a file using
    /// [`from_file(path)`](struct.Texture.html#method.from_file)
    ///
    /// # Example
    /// ```rust,no_run
    /// use gondola::texture::Texture;
    ///
    /// let mut texture = Texture::new();
    /// texture.load_file("assets/test.png").expect("Failed to load texture");
    /// ```
    pub fn load_file<P>(&mut self, path: P) -> io::Result<()> where P: AsRef<Path> {
        let (info, data) = load_image(path.as_ref())?;
        let texture_format = match (info.color_type, info.bit_depth) {
            (png::ColorType::RGBA, png::BitDepth::Eight) => TextureFormat::RGBA_8,
            (png::ColorType::RGB, png::BitDepth::Eight)  => TextureFormat::RGB_8,
            other => {
                let message = format!(
                    "Unsuported texture format ({:?}, {:?}) in \"{}\" ({}:{})",
                    other.0, other.1,
                    path.as_ref().to_string_lossy(),
                    module_path!(), line!()
                );
                return Err(io::Error::new(io::ErrorKind::Other, message));
            }
        };
        self.load_data(&data, info.width, info.height, texture_format);
        Ok(())
    }

    /// Sets the data this texture points to
    pub fn load_data(&mut self, data: &[u8], width: u32, height: u32, format: TextureFormat) {
        unsafe {
            gl::BindTexture(gl::TEXTURE_2D, self.texture);
            gl::TexImage2D(gl::TEXTURE_2D, 0, // Mipmap level
                           format as GLint, // Internal format
                           width as GLsizei, height as GLsizei, 0, // Size and border
                           format.unsized_format(), // Data format
                           gl::UNSIGNED_BYTE, data.as_ptr() as *const GLvoid);
        }

        self.width = width;
        self.height = height;
        self.format = format;
    }

    /// Binds this texture to the given texture unit
    pub fn bind(&self, unit: u32) {
        unsafe {
            gl::ActiveTexture(gl::TEXTURE0 + unit as GLenum);
            gl::BindTexture(gl::TEXTURE_2D, self.texture);
        }
    }

    /// Sets the filter that is applied when this texture is rendered at a size larger
    /// or smaller sizes than the native size of the texture. A separate filter can be
    /// set for magnification and minification.
    pub fn set_filter(&mut self, mag: TextureFilter, min: TextureFilter) {
        unsafe {
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, mag as GLint);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, min as GLint);
        }
    }

    /// Sets the texture filter, allowing for a separate filter to be used when mipmapping
    pub fn set_mipmap_filter(&mut self, mag: TextureFilter, mipmap_mag: TextureFilter,
                             min: TextureFilter, mipmap_min: TextureFilter) {
        unsafe {
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, TextureFilter::mipmap_filter(mag, mipmap_mag) as GLint);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, TextureFilter::mipmap_filter(min, mipmap_min) as GLint);
        }
    }
}

impl Drop for Texture {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteTextures(1, &self.texture);
        }
    }
}

/// Loads image data from a file
fn load_image(path: &Path) -> io::Result<(png::OutputInfo, Vec<u8>)> {
    let decoder = png::Decoder::new(File::open(path)?);
    let (info, mut reader) = decoder.read_info()?;
    let mut buf = vec![0; info.buffer_size()];
    reader.next_frame(&mut buf)?;

    Ok((info, buf))
}

/// Represents an OpenGL texture filter. Use in OpenGL functions like ´TextureFilter::* as GLenum´
#[derive(Debug, Copy, Clone)]
pub enum TextureFilter {
    Nearest = gl::NEAREST as isize,
    Linear  = gl::LINEAR  as isize,
}
impl TextureFilter {
    /// Retrieves a OpenGL mipmap filter for mipmaping. The returned `GLenum` can
    /// be used in the same scenarios as ´TextureFilter::* as GLenum´
    fn mipmap_filter(normal: TextureFilter, mipmap: TextureFilter) -> GLenum {
        match normal {
            TextureFilter::Nearest => match mipmap {
                TextureFilter::Nearest => gl::NEAREST_MIPMAP_NEAREST,
                TextureFilter::Linear => gl::NEAREST_MIPMAP_LINEAR,
            },
            TextureFilter::Linear => match mipmap {
                TextureFilter::Nearest => gl::LINEAR_MIPMAP_NEAREST,
                TextureFilter::Linear => gl::LINEAR_MIPMAP_LINEAR,
            },
        }
    }
}

#[allow(non_camel_case_types, dead_code)]
#[derive(Debug, Copy, Clone)]
/// Represents a OpenGL texture format. Use in OpenGL functions like `TextureFormat::* as GLenum`
pub enum TextureFormat {
    RGBA_F32 = gl::RGBA32F as isize,
    RGBA_F16 = gl::RGBA16F as isize,
    RGB_F32  = gl::RGB32F as isize,
    RGB_F16  = gl::RGB16F as isize,
    R_F32    = gl::R32F as isize,
    R_F16    = gl::R16F as isize,

    RGBA_8   = gl::RGBA8 as isize,
    RGB_8    = gl::RGB8 as isize,
    R_8      = gl::R8 as isize,
}
impl TextureFormat {
    /// Retrives the unsized version of the given format
    pub fn unsized_format(&self) -> GLenum {
        match *self {
            TextureFormat::RGBA_F32 | TextureFormat::RGBA_F16 | TextureFormat::RGBA_8 => gl::RGBA,
            TextureFormat::RGB_F32 | TextureFormat::RGB_F16 | TextureFormat::RGB_8 => gl::RGB,
            TextureFormat::R_F32 | TextureFormat::R_F16 | TextureFormat::R_8 => gl::RED
        }
    }
}

<commit_msg>Can now set a subregion of a texture<commit_after>
//! Utilities for loading and using textures

use png;
use gl;
use gl::types::*;
use std::io;
use std::ptr;
use std::path::Path;
use std::fs::File;

/// A wraper around a OpenGL texture object which can be modified
#[derive(Debug)]
pub struct Texture {
    texture: GLuint,
    pub format: TextureFormat,
    pub width: u32,
    pub height: u32,
}

impl Texture {
    /// Creates a texture from a image file.
    pub fn from_file<P>(path: P) -> io::Result<Texture> where P: AsRef<Path> {
        let mut texture = Texture::new();
        texture.load_file(path)?;
        Ok(texture)
    }

    /// Creates a new texture without any ascociated data
    pub fn new() -> Texture {
        let mut texture = 0;

        unsafe {
            gl::GenTextures(1, &mut texture);
            gl::BindTexture(gl::TEXTURE_2D, texture);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint);
        }

        Texture {
            texture: texture,
            format: TextureFormat::RGB_8,
            width: 0,
            height: 0,
        }
    }

    /// Attempts to load data from the given image file into this texture. Note that
    /// it is usually more convenient to create a new texture from a file using
    /// [`from_file(path)`](struct.Texture.html#method.from_file)
    ///
    /// # Example
    /// ```rust,no_run
    /// use gondola::texture::Texture;
    ///
    /// let mut texture = Texture::new();
    /// texture.load_file("assets/test.png").expect("Failed to load texture");
    /// ```
    pub fn load_file<P>(&mut self, path: P) -> io::Result<()> where P: AsRef<Path> {
        let (info, data) = load_image(path.as_ref())?;
        let texture_format = match (info.color_type, info.bit_depth) {
            (png::ColorType::RGBA, png::BitDepth::Eight) => TextureFormat::RGBA_8,
            (png::ColorType::RGB, png::BitDepth::Eight)  => TextureFormat::RGB_8,
            other => {
                let message = format!(
                    "Unsuported texture format ({:?}, {:?}) in \"{}\" ({}:{})",
                    other.0, other.1,
                    path.as_ref().to_string_lossy(),
                    module_path!(), line!()
                );
                return Err(io::Error::new(io::ErrorKind::Other, message));
            }
        };
        self.load_data(&data, info.width, info.height, texture_format);
        Ok(())
    }

    /// Sets the data this texture points to
    pub fn load_data(&mut self, data: &[u8], width: u32, height: u32, format: TextureFormat) {
        unsafe {
            gl::BindTexture(gl::TEXTURE_2D, self.texture);
            gl::TexImage2D(gl::TEXTURE_2D, 0, // Mipmap level
                           format as GLint, // Internal format
                           width as GLsizei, height as GLsizei, 0, // Size and border
                           format.unsized_format(), // Data format
                           gl::UNSIGNED_BYTE, data.as_ptr() as *const GLvoid);
        }

        self.width = width;
        self.height = height;
        self.format = format;
    }

    /// Sets the data in a sub-region of this texture. The data is expected to be in the
    /// format this texture was initialized to. This texture needs to be initialized
    /// before this method can be used.
    /// Note that there is a debug assertion in place to ensure that the given region
    /// is within the bounds of this texture. If debug assertions are not enabled this
    /// function will return without taking any action.
    pub fn load_data_to_region(&mut self, data: &[u8], x: u32, y: u32, width: u32, height: u32) {
        if x + width > self.width && y + height > self.height {
            debug_assert!(false, "Invalid region passed ({}:{}) Region: (x: {}, y: {}, width: {}, height: {})",
                          module_path!(), line!(),
                          x, y, width, height);
            return;
        }
        unsafe {
            gl::BindTexture(gl::TEXTURE_2D, self.texture);
            gl::TexSubImage2D(gl::TEXTURE_2D, 0,
                              x as GLint, y as GLint,
                              width as GLsizei, height as GLsizei,
                              self.format.unsized_format(), // It is unclear whether opengl allows a different format here
                              gl::UNSIGNED_BYTE, data.as_ptr() as *const GLvoid);
        }
    }

    /// Converts this texture to a empty texture of the given size. The contents
    /// of the texture after this operation are undefined.
    pub fn initialize(&mut self, width: u32, height: u32, format: TextureFormat) {
        unsafe {
            gl::BindTexture(gl::TEXTURE_2D, self.texture);
            gl::TexImage2D(gl::TEXTURE_2D, 0, // Mipmap level
                           format as GLint, // Internal format
                           width as GLsizei, height as GLsizei, 0, // Size and border
                           format.unsized_format(), // Data format
                           gl::UNSIGNED_BYTE, ptr::null());
        }

        self.width = width;
        self.height = height;
        self.format = format;
    }

    /// Binds this texture to the given texture unit
    pub fn bind(&self, unit: u32) {
        unsafe {
            gl::ActiveTexture(gl::TEXTURE0 + unit as GLenum);
            gl::BindTexture(gl::TEXTURE_2D, self.texture);
        }
    }

    /// Sets the filter that is applied when this texture is rendered at a size larger
    /// or smaller sizes than the native size of the texture. A separate filter can be
    /// set for magnification and minification.
    pub fn set_filter(&mut self, mag: TextureFilter, min: TextureFilter) {
        unsafe {
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, mag as GLint);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, min as GLint);
        }
    }

    /// Sets the texture filter, allowing for a separate filter to be used when mipmapping
    pub fn set_mipmap_filter(&mut self, mag: TextureFilter, mipmap_mag: TextureFilter,
                             min: TextureFilter, mipmap_min: TextureFilter) {
        unsafe {
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, TextureFilter::mipmap_filter(mag, mipmap_mag) as GLint);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, TextureFilter::mipmap_filter(min, mipmap_min) as GLint);
        }
    }
}

impl Drop for Texture {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteTextures(1, &self.texture);
        }
    }
}

/// Loads image data from a file
fn load_image(path: &Path) -> io::Result<(png::OutputInfo, Vec<u8>)> {
    let decoder = png::Decoder::new(File::open(path)?);
    let (info, mut reader) = decoder.read_info()?;
    let mut buf = vec![0; info.buffer_size()];
    reader.next_frame(&mut buf)?;

    Ok((info, buf))
}

/// Represents an OpenGL texture filter. Use in OpenGL functions like ´TextureFilter::* as GLenum´
#[derive(Debug, Copy, Clone)]
pub enum TextureFilter {
    Nearest = gl::NEAREST as isize,
    Linear  = gl::LINEAR  as isize,
}
impl TextureFilter {
    /// Retrieves a OpenGL mipmap filter for mipmaping. The returned `GLenum` can
    /// be used in the same scenarios as ´TextureFilter::* as GLenum´
    fn mipmap_filter(normal: TextureFilter, mipmap: TextureFilter) -> GLenum {
        match normal {
            TextureFilter::Nearest => match mipmap {
                TextureFilter::Nearest => gl::NEAREST_MIPMAP_NEAREST,
                TextureFilter::Linear => gl::NEAREST_MIPMAP_LINEAR,
            },
            TextureFilter::Linear => match mipmap {
                TextureFilter::Nearest => gl::LINEAR_MIPMAP_NEAREST,
                TextureFilter::Linear => gl::LINEAR_MIPMAP_LINEAR,
            },
        }
    }
}

#[allow(non_camel_case_types, dead_code)]
#[derive(Debug, Copy, Clone)]
/// Represents a OpenGL texture format. Use in OpenGL functions like `TextureFormat::* as GLenum`
pub enum TextureFormat {
    RGBA_F32 = gl::RGBA32F as isize,
    RGBA_F16 = gl::RGBA16F as isize,
    RGB_F32  = gl::RGB32F as isize,
    RGB_F16  = gl::RGB16F as isize,
    R_F32    = gl::R32F as isize,
    R_F16    = gl::R16F as isize,

    RGBA_8   = gl::RGBA8 as isize,
    RGB_8    = gl::RGB8 as isize,
    R_8      = gl::R8 as isize,
}
impl TextureFormat {
    /// Retrives the unsized version of the given format
    pub fn unsized_format(&self) -> GLenum {
        match *self {
            TextureFormat::RGBA_F32 | TextureFormat::RGBA_F16 | TextureFormat::RGBA_8 => gl::RGBA,
            TextureFormat::RGB_F32 | TextureFormat::RGB_F16 | TextureFormat::RGB_8 => gl::RGB,
            TextureFormat::R_F32 | TextureFormat::R_F16 | TextureFormat::R_8 => gl::RED
        }
    }
}

<|endoftext|>