<commit_before>use crate::common::*;
use crate::data::packet::Packet;
use crate::data::value::*;
use std::any::Any;
use std::io::{ErrorKind, Seek, SeekFrom, Write};
use std::sync::Arc;

use crate::error::*;

pub trait WriteSeek: Write + Seek {}

impl<T: Write + Seek> WriteSeek for T {}

/// Runtime wrapper around either a [`Write`] or a [`WriteSeek`] trait object which supports querying
/// for seek support.
pub enum Writer<W: Write, WS: WriteSeek> {
    NonSeekable(W, u64),
    Seekable(WS),
}

impl<W: Write, WS: WriteSeek> Writer<W, WS> {
    /// Creates a [`Writer`] from an object that implements both [`Write`] and [`Seek`] traits.
    pub fn from_seekable(inner: WS) -> Self {
        Self::Seekable(inner)
    }

    /// Creates a [`Writer`] from an object that implements the [`Write`] trait.
    pub fn from_nonseekable(inner: W) -> Self {
        Self::NonSeekable(inner, 0)
    }

    /// Returns whether the [`Writer`] can seek within the source.
    pub fn can_seek(&self) -> bool {
        matches!(self, Self::Seekable(_))
    }
}

impl<W: Write, WS: WriteSeek> Write for Writer<W, WS> {
    fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
        match self {
            Self::NonSeekable(inner, ref mut index) => {
                let result = inner.write(bytes);

                if let Ok(written) = result {
                    *index += written as u64;
                }

                result
            }
            Self::Seekable(inner) => inner.write(bytes),
        }
    }

    fn flush(&mut self) -> std::io::Result<()> {
        match self {
            Self::NonSeekable(inner, ..) => inner.flush(),
            Self::Seekable(inner) => inner.flush(),
        }
    }
}

impl<W: Write, WS: WriteSeek> Seek for Writer<W, WS> {
    fn seek(&mut self, seek: SeekFrom) -> std::io::Result<u64> {
        match self {
            Self::NonSeekable(_, index) => {
                if let SeekFrom::Current(0) = seek {
                    Ok(*index)
                } else {
                    Err(std::io::Error::new(
                        ErrorKind::Other,
                        "Seeking not supported",
                    ))
                }
            }
            Self::Seekable(inner) => inner.seek(seek),
        }
    }
}

/// Used to implement muxing operations.
pub trait Muxer: Send {
    /// Configures a muxer.
    fn configure(&mut self) -> Result<()>;
    /// Writes a stream header into a data structure implementing
    /// the `Write` trait.
    fn write_header<W: Write, WS: WriteSeek>(&mut self, out: &mut Writer<W, WS>) -> Result<()>;
    /// Writes a stream packet into a data structure implementing
    /// the `Write` trait.
    fn write_packet<W: Write, WS: WriteSeek>(
        &mut self,
        out: &mut Writer<W, WS>,
        pkt: Arc<Packet>,
    ) -> Result<()>;
    /// Writes a stream trailer into a data structure implementing
    /// the `Write` trait.
    fn write_trailer<W: Write, WS: WriteSeek>(&mut self, out: &mut Writer<W, WS>) -> Result<()>;

    /// Sets global media file information for a muxer.
    fn set_global_info(&mut self, info: GlobalInfo) -> Result<()>;
    /// Sets a muxer option.
    ///
    /// This method should be called as many times as the number of options
    /// present in a muxer.
    fn set_option<'a>(&mut self, key: &str, val: Value<'a>) -> Result<()>;
}

/// Auxiliary structure to encapsulate a muxer object and
/// its additional data.
pub struct Context<M: Muxer + Send, W: Write, WS: WriteSeek> {
    muxer: M,
    writer: Writer<W, WS>,
    /// User private data.
    ///
    /// This data cannot be cloned.
    pub user_private: Option<Box<dyn Any + Send + Sync>>,
}

impl<M: Muxer + Send, W: Write, WS: WriteSeek> Context<M, W, WS> {
    /// Creates a new `Context` instance.
    pub fn new(muxer: M, writer: Writer<W, WS>) -> Self {
        Context {
            muxer,
            writer,
            user_private: None,
        }
    }

    /// Configures a muxer.
    pub fn configure(&mut self) -> Result<()> {
        self.muxer.configure()
    }

    /// Writes a stream header to an internal buffer and returns how many
    /// bytes were written or an error.
    pub fn write_header(&mut self) -> Result<()> {
        self.muxer.write_header(&mut self.writer)
    }

    /// Writes a stream packet to an internal buffer and returns how many
    /// bytes were written or an error.
    pub fn write_packet(&mut self, pkt: Arc<Packet>) -> Result<()> {
        self.muxer.write_packet(&mut self.writer, pkt)
    }

    /// Writes a stream trailer to an internal buffer and returns how many
    /// bytes were written or an error.
    pub fn write_trailer(&mut self) -> Result<()> {
        self.muxer.write_trailer(&mut self.writer)?;
        self.writer.flush()?;

        Ok(())
    }

    /// Sets global media file information for a muxer.
    pub fn set_global_info(&mut self, info: GlobalInfo) -> Result<()> {
        self.muxer.set_global_info(info)
    }

    /// Sets a muxer option.
    ///
    /// This method should be called as many times as the number of options
    /// present in a muxer.
    pub fn set_option<'a, V>(&mut self, key: &str, val: V) -> Result<()>
    where
        V: Into<Value<'a>>,
    {
        self.muxer.set_option(key, val.into())
    }
}

/// Format descriptor.
///
/// Contains information on a format and its own muxer.
#[derive(Clone, Debug, PartialEq)]
pub struct Descr {
    /// Format name.
    pub name: &'static str,
    /// Muxer name.
    pub demuxer: &'static str,
    /// Format description.
    pub description: &'static str,
    /// Format media file extensions.
    pub extensions: &'static [&'static str],
    /// Format MIME.
    pub mime: &'static [&'static str],
}

/// Used to get a format descriptor and create a new muxer.
pub trait Descriptor {
    type OutputMuxer: Muxer + Send;

    /// Creates a new muxer for the requested format.
    fn create(&self) -> Self::OutputMuxer;
    /// Returns the descriptor of a format.
    fn describe(&self) -> &Descr;
}

/// Used to look for a specific format.
pub trait Lookup<T: Descriptor + ?Sized> {
    /// Retrieves a specific format by name.
    fn by_name(&self, name: &str) -> Option<&'static T>;
}

impl<T: Descriptor + ?Sized> Lookup<T> for [&'static T] {
    fn by_name(&self, name: &str) -> Option<&'static T> {
        self.iter().find(|&&d| d.describe().name == name).copied()
    }
}
<commit_msg>format: Add an API to get the underlying writer<commit_after>use crate::common::*;
use crate::data::packet::Packet;
use crate::data::value::*;
use std::any::Any;
use std::io::{ErrorKind, Seek, SeekFrom, Write};
use std::sync::Arc;

use crate::error::*;

pub trait WriteSeek: Write + Seek {}

impl<T: Write + Seek> WriteSeek for T {}

/// Runtime wrapper around either a [`Write`] or a [`WriteSeek`] trait object which supports querying
/// for seek support.
pub enum Writer<W: Write, WS: WriteSeek> {
    NonSeekable(W, u64),
    Seekable(WS),
}

impl<W: Write, WS: WriteSeek> Writer<W, WS> {
    /// Creates a [`Writer`] from an object that implements both [`Write`] and [`Seek`] traits.
    pub fn from_seekable(inner: WS) -> Self {
        Self::Seekable(inner)
    }

    /// Creates a [`Writer`] from an object that implements the [`Write`] trait.
    pub fn from_nonseekable(inner: W) -> Self {
        Self::NonSeekable(inner, 0)
    }

    /// Returns whether the [`Writer`] can seek within the source.
    pub fn can_seek(&self) -> bool {
        matches!(self, Self::Seekable(_))
    }
}

impl<W: Write, WS: WriteSeek> Write for Writer<W, WS> {
    fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
        match self {
            Self::NonSeekable(inner, ref mut index) => {
                let result = inner.write(bytes);

                if let Ok(written) = result {
                    *index += written as u64;
                }

                result
            }
            Self::Seekable(inner) => inner.write(bytes),
        }
    }

    fn flush(&mut self) -> std::io::Result<()> {
        match self {
            Self::NonSeekable(inner, ..) => inner.flush(),
            Self::Seekable(inner) => inner.flush(),
        }
    }
}

impl<W: Write, WS: WriteSeek> Seek for Writer<W, WS> {
    fn seek(&mut self, seek: SeekFrom) -> std::io::Result<u64> {
        match self {
            Self::NonSeekable(_, index) => {
                if let SeekFrom::Current(0) = seek {
                    Ok(*index)
                } else {
                    Err(std::io::Error::new(
                        ErrorKind::Other,
                        "Seeking not supported",
                    ))
                }
            }
            Self::Seekable(inner) => inner.seek(seek),
        }
    }
}

/// Used to implement muxing operations.
pub trait Muxer: Send {
    /// Configures a muxer.
    fn configure(&mut self) -> Result<()>;
    /// Writes a stream header into a data structure implementing
    /// the `Write` trait.
    fn write_header<W: Write, WS: WriteSeek>(&mut self, out: &mut Writer<W, WS>) -> Result<()>;
    /// Writes a stream packet into a data structure implementing
    /// the `Write` trait.
    fn write_packet<W: Write, WS: WriteSeek>(
        &mut self,
        out: &mut Writer<W, WS>,
        pkt: Arc<Packet>,
    ) -> Result<()>;
    /// Writes a stream trailer into a data structure implementing
    /// the `Write` trait.
    fn write_trailer<W: Write, WS: WriteSeek>(&mut self, out: &mut Writer<W, WS>) -> Result<()>;

    /// Sets global media file information for a muxer.
    fn set_global_info(&mut self, info: GlobalInfo) -> Result<()>;
    /// Sets a muxer option.
    ///
    /// This method should be called as many times as the number of options
    /// present in a muxer.
    fn set_option<'a>(&mut self, key: &str, val: Value<'a>) -> Result<()>;
}

/// Auxiliary structure to encapsulate a muxer object and
/// its additional data.
pub struct Context<M: Muxer + Send, W: Write, WS: WriteSeek> {
    muxer: M,
    writer: Writer<W, WS>,
    /// User private data.
    ///
    /// This data cannot be cloned.
    pub user_private: Option<Box<dyn Any + Send + Sync>>,
}

impl<M: Muxer + Send, W: Write, WS: WriteSeek> Context<M, W, WS> {
    /// Creates a new `Context` instance.
    pub fn new(muxer: M, writer: Writer<W, WS>) -> Self {
        Context {
            muxer,
            writer,
            user_private: None,
        }
    }

    /// Configures a muxer.
    pub fn configure(&mut self) -> Result<()> {
        self.muxer.configure()
    }

    /// Writes a stream header to an internal buffer and returns how many
    /// bytes were written or an error.
    pub fn write_header(&mut self) -> Result<()> {
        self.muxer.write_header(&mut self.writer)
    }

    /// Writes a stream packet to an internal buffer and returns how many
    /// bytes were written or an error.
    pub fn write_packet(&mut self, pkt: Arc<Packet>) -> Result<()> {
        self.muxer.write_packet(&mut self.writer, pkt)
    }

    /// Writes a stream trailer to an internal buffer and returns how many
    /// bytes were written or an error.
    pub fn write_trailer(&mut self) -> Result<()> {
        self.muxer.write_trailer(&mut self.writer)?;
        self.writer.flush()?;

        Ok(())
    }

    /// Sets global media file information for a muxer.
    pub fn set_global_info(&mut self, info: GlobalInfo) -> Result<()> {
        self.muxer.set_global_info(info)
    }

    /// Sets a muxer option.
    ///
    /// This method should be called as many times as the number of options
    /// present in a muxer.
    pub fn set_option<'a, V>(&mut self, key: &str, val: V) -> Result<()>
    where
        V: Into<Value<'a>>,
    {
        self.muxer.set_option(key, val.into())
    }

    /// Returns the underlying writer.
    pub fn writer(&self) -> &Writer<W, WS> {
        &self.writer
    }
}

/// Format descriptor.
///
/// Contains information on a format and its own muxer.
#[derive(Clone, Debug, PartialEq)]
pub struct Descr {
    /// Format name.
    pub name: &'static str,
    /// Muxer name.
    pub demuxer: &'static str,
    /// Format description.
    pub description: &'static str,
    /// Format media file extensions.
    pub extensions: &'static [&'static str],
    /// Format MIME.
    pub mime: &'static [&'static str],
}

/// Used to get a format descriptor and create a new muxer.
pub trait Descriptor {
    type OutputMuxer: Muxer + Send;

    /// Creates a new muxer for the requested format.
    fn create(&self) -> Self::OutputMuxer;
    /// Returns the descriptor of a format.
    fn describe(&self) -> &Descr;
}

/// Used to look for a specific format.
pub trait Lookup<T: Descriptor + ?Sized> {
    /// Retrieves a specific format by name.
    fn by_name(&self, name: &str) -> Option<&'static T>;
}

impl<T: Descriptor + ?Sized> Lookup<T> for [&'static T] {
    fn by_name(&self, name: &str) -> Option<&'static T> {
        self.iter().find(|&&d| d.describe().name == name).copied()
    }
}
<|endoftext|>


<commit_before>use ws;
use time;
use std::fmt;
use uuid::Uuid;
use std::sync::{Arc,Mutex};
use std::collections::{HashMap,HashSet};

#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum Item {
    Potatoes,
    Berries,
    TreeSap,
}

trait ParseFrom<T, Out = Self> {
    fn parse (from: T) -> Result<Out, String>;
}

impl ParseFrom<i32> for Item {
    fn parse (i: i32) -> Result<Self, String> {
        match i {
            0 => Ok(Item::Potatoes),
            1 => Ok(Item::Berries),
            2 => Ok(Item::TreeSap),
            _ => Err(format!("Item.parse<i32> failure: unrecognized Item {}", i)),
        }
    }
}

#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum Action {
    addItemToInventory(Item),
}

#[derive(Debug, Clone)]
pub struct PlayerState {
    inventory: HashSet<Item>,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum Player {
    AnonymousPlayer {
        state : PlayerState,
    },
    RegisteredPlayer {
        id    : i32,
        name  : String,
        state : PlayerState,
    }
}

#[allow(non_snake_case)]
mod AnonymousPlayer {
    use super::*;
    pub fn new () -> Player {
        let mut new_player_inventory = HashSet::new();
        new_player_inventory.insert(Item::Potatoes);

        Player::AnonymousPlayer {
            state: PlayerState {
                inventory: new_player_inventory
            }
        }
    }
}

#[allow(non_snake_case,dead_code)]
mod RegisteredPlayer {
    use super::*;
    pub fn new (id: i32, name: String) -> Player {
        let mut new_player_inventory = HashSet::new();
        new_player_inventory.insert(Item::Potatoes);

        Player::RegisteredPlayer {
            id: id,
            name: name,
            state: PlayerState {
                inventory: new_player_inventory
            }
        }
    }
}

#[derive(Debug, Clone)]
pub struct Connection {
    uuid   : Uuid,
    player : Player,
}

impl Connection {
    fn new(u: Uuid, p: Player) -> Connection {
        Connection {
            uuid  : u,
            player: p
        }
    }
}

impl fmt::Display for Connection {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "conn{{{}}}", self.uuid)
    }
}

fn parse_cookies (req: &ws::Request) -> HashMap<String, String> {
    req.header("cookie")
        .and_then(|cookies_bytes| String::from_utf8(cookies_bytes.to_vec()).ok())
        .unwrap_or(String::from(""))
        .rsplit(";")
        .filter_map(|cookie_string| {
            let mut cookie_pair = cookie_string.split("=");
            match (cookie_pair.next(), cookie_pair.next()) {
                (Some(name), Some(value)) => {
                    Some((String::from(name.trim()), String::from(value.trim())))
                },
                _ => None
            }
        })
        .collect()
}

fn put_cookie (name: String, value: String, resp: &mut ws::Response) {
    let headers = resp.headers_mut();
    let cookie_bytes = format!("{}={}", name, value).as_bytes().to_vec();
    headers.push((String::from("Set-Cookie"), cookie_bytes));
}

fn authorize_ticket_dumb (ticket: Uuid, msg_type: Vec<String>, conn_map: ConnectionMap) -> Result<Vec<String>, String> {
   if conn_map.lock().unwrap().contains_key(&ticket) {
        Ok(msg_type)
   } else {
        Err(String::from("authorize_ticket_dumb authorization failed"))
   }
}

type ConnectionMap = Arc<Mutex<HashMap<Uuid, Connection>>>;

struct WSServer {
    out: ws::Sender,
    connections: ConnectionMap,
}

fn parse_message (msg: ws::Message) -> Result<(Uuid, Vec<String>), String> {
    fn parse_error_msg (reason: &str) -> String {
        format!("parse_message failure: {}", reason)
    }

    msg.into_text()
        .or(Err(parse_error_msg("cannot get text from Message; is it a binary Message?")))
        ?.split(":")                         // String -> Split<'_, &str>
        .map(|p| p.to_string())              // Split<'_, &str> -> &[String]
        .collect::<Vec<String>>()            // &[String] -> Vec<String>
        .split_first()                       // Vec<String> -> Option<(String, Vec<String>)>
        .ok_or(parse_error_msg("no ticket")) // Option -> Result<(String, &[String]), String>
        .and_then(|(uuid_string, rest)| {    // Result<(String, &[String]), String>
            Uuid::parse_str(uuid_string)     //   |-> Result<(Uuid, Vec<String>), String>
                .or(Err(parse_error_msg("ticket is invalid uuidv4")))
                .map(|uuid| (uuid, Vec::from(rest)))
        })
}

impl ws::Handler for WSServer {
    fn on_request(&mut self, req: &ws::Request) -> ws::Result<ws::Response> {
        let mut resp = ws::Response::from_request(req).unwrap();

        let mut conn_map = self.connections.lock().unwrap();

        println!("ws:req[{}]", time::precise_time_ns());

        let cookies : HashMap<String, String> = parse_cookies(req);

        let mut cookie_existed = true;
        let ticket = cookies.get("bzwf_anon_wstx")
            .and_then(|uuid_string| Uuid::parse_str(uuid_string.as_str()).ok())
            .unwrap_or_else(|| {
                println!("ws:req[{}]: no bzwf_anon_wstx cookie found", time::precise_time_ns());
                cookie_existed = false;
                Uuid::new_v4()
            });

        let users_count = conn_map.len();

        if conn_map.contains_key(&ticket) {
            println!("ws:req[{}]: reconnect: uuid {}", time::precise_time_ns(), ticket);
            println!("{} connected users", users_count);
        } else {
            println!("ws:req[{}]: new connection with uuid {}", time::precise_time_ns(), ticket);

            let cookie_op_str = if cookie_existed {
                "replacing persistence cookie"
            } else {
                "creating persistence cookie"
            };

            println!("ws:req[{}]: {}: bzwf_anon_wstx={}", time::precise_time_ns(), cookie_op_str, ticket);
            println!("{} connected users", users_count + 1);
            put_cookie(String::from("bzwf_anon_wstx"), ticket.to_string(), &mut resp);
            conn_map.insert(ticket, Connection::new(ticket, AnonymousPlayer::new()));
        }

        Ok(resp)
    }

    fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
        println!("ws:rcv[{}]: user with uuid {} sent ws msg {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 msg);

        parse_message(msg)
            .and_then(|(msg_ticket, msg_contents)| {
                println!("authorizing ticket {}", msg_ticket);
                authorize_ticket_dumb(msg_ticket, msg_contents, self.connections.clone())
            })
            .and_then(|msg_contents| {
                // FIXME possible out of bounds error
                let ref msg_type  = msg_contents[0];
                let ref msg_param = msg_contents[1];

                println!("trying to read message contents of {}({})", msg_type, msg_param);

                match (msg_type.as_str(), msg_param) {
                    ("addItemToInventory", item) => {
                        if let Ok(item_num) = item.parse::<i32>() {
                            Item::parse(item_num)
                                .map(|item| Action::addItemToInventory(item))
                        } else {
                            Err(String::from("parse_message failure: addItemToInventory: invalid item code"))
                        }
                    },
                    _ => Err(String::from("parse_message failure: unrecognized message type"))
                }
            })
            .map(|action| {
                self.out.send(format!("gotcha, you want to {:?}", action))
            })
            .unwrap_or_else(|err| {
                println!("{}", err);
                self.out.send("got your message, but not sure what it meant")
            });

        Ok(())
    }

    fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
        // TODO
        println!("ws:opn[{}]: user with uuid {} opened ws cxn",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]");
        Ok(())
    }

    fn on_close(&mut self, code: ws::CloseCode, reason: &str) {
        println!("ws:cls[{}]: user with uuid {} closed ws cxn\n\tCode [{:?}] reason: {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 code,
                 reason);
        // TODO
    }

    fn on_error(&mut self, err: ws::Error) {
        println!("ws:err[{}]: user with uuid {} got error {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 err);
        // TODO
    }
}

pub fn server (domain: String, port: i32) -> () {
    let connections : ConnectionMap = Arc::new(Mutex::new(HashMap::new()));
    let addr = format!("{}:{}", domain, port);
    println!("WebSockets server listening on {}", addr);
    ws::listen(addr, |out| WSServer {
        out: out,
        connections: connections.clone(),
    }).unwrap();
}
<commit_msg>Bugfix: don't crash when parse fails<commit_after>use ws;
use time;
use std::fmt;
use uuid::Uuid;
use std::sync::{Arc,Mutex};
use std::collections::{HashMap,HashSet};

#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum Item {
    Potatoes,
    Berries,
    TreeSap,
}

trait ParseFrom<T, Out = Self> {
    fn parse (from: T) -> Result<Out, String>;
}

impl ParseFrom<i32> for Item {
    fn parse (i: i32) -> Result<Self, String> {
        match i {
            0 => Ok(Item::Potatoes),
            1 => Ok(Item::Berries),
            2 => Ok(Item::TreeSap),
            _ => Err(format!("Item.parse<i32> failure: unrecognized Item {}", i)),
        }
    }
}

#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum Action {
    addItemToInventory(Item),
}

#[derive(Debug, Clone)]
pub struct PlayerState {
    inventory: HashSet<Item>,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum Player {
    AnonymousPlayer {
        state : PlayerState,
    },
    RegisteredPlayer {
        id    : i32,
        name  : String,
        state : PlayerState,
    }
}

#[allow(non_snake_case)]
mod AnonymousPlayer {
    use super::*;
    pub fn new () -> Player {
        let mut new_player_inventory = HashSet::new();
        new_player_inventory.insert(Item::Potatoes);

        Player::AnonymousPlayer {
            state: PlayerState {
                inventory: new_player_inventory
            }
        }
    }
}

#[allow(non_snake_case,dead_code)]
mod RegisteredPlayer {
    use super::*;
    pub fn new (id: i32, name: String) -> Player {
        let mut new_player_inventory = HashSet::new();
        new_player_inventory.insert(Item::Potatoes);

        Player::RegisteredPlayer {
            id: id,
            name: name,
            state: PlayerState {
                inventory: new_player_inventory
            }
        }
    }
}

#[derive(Debug, Clone)]
pub struct Connection {
    uuid   : Uuid,
    player : Player,
}

impl Connection {
    fn new(u: Uuid, p: Player) -> Connection {
        Connection {
            uuid  : u,
            player: p
        }
    }
}

impl fmt::Display for Connection {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "conn{{{}}}", self.uuid)
    }
}

fn parse_cookies (req: &ws::Request) -> HashMap<String, String> {
    req.header("cookie")
        .and_then(|cookies_bytes| String::from_utf8(cookies_bytes.to_vec()).ok())
        .unwrap_or(String::from(""))
        .rsplit(";")
        .filter_map(|cookie_string| {
            let mut cookie_pair = cookie_string.split("=");
            match (cookie_pair.next(), cookie_pair.next()) {
                (Some(name), Some(value)) => {
                    Some((String::from(name.trim()), String::from(value.trim())))
                },
                _ => None
            }
        })
        .collect()
}

fn put_cookie (name: String, value: String, resp: &mut ws::Response) {
    let headers = resp.headers_mut();
    let cookie_bytes = format!("{}={}", name, value).as_bytes().to_vec();
    headers.push((String::from("Set-Cookie"), cookie_bytes));
}

fn authorize_ticket_dumb (ticket: Uuid, msg_type: Vec<String>, conn_map: ConnectionMap) -> Result<Vec<String>, String> {
   if conn_map.lock().unwrap().contains_key(&ticket) {
        Ok(msg_type)
   } else {
        Err(String::from("authorize_ticket_dumb authorization failed"))
   }
}

type ConnectionMap = Arc<Mutex<HashMap<Uuid, Connection>>>;

struct WSServer {
    out: ws::Sender,
    connections: ConnectionMap,
}

fn parse_message (msg: ws::Message) -> Result<(Uuid, Vec<String>), String> {
    fn parse_error_msg (reason: &str) -> String {
        format!("parse_message failure: {}", reason)
    }

    msg.into_text()
        .or(Err(parse_error_msg("cannot get text from Message; is it a binary Message?")))
        ?.split(":")                         // String -> Split<'_, &str>
        .map(|p| p.to_string())              // Split<'_, &str> -> &[String]
        .collect::<Vec<String>>()            // &[String] -> Vec<String>
        .split_first()                       // Vec<String> -> Option<(String, Vec<String>)>
        .ok_or(parse_error_msg("no ticket")) // Option -> Result<(String, &[String]), String>
        .and_then(|(uuid_string, rest)| {    // Result<(String, &[String]), String>
            Uuid::parse_str(uuid_string)     //   |-> Result<(Uuid, Vec<String>), String>
                .or(Err(parse_error_msg("ticket is invalid uuidv4")))
                .map(|uuid| (uuid, Vec::from(rest)))
        })
}

impl ws::Handler for WSServer {
    fn on_request(&mut self, req: &ws::Request) -> ws::Result<ws::Response> {
        let mut resp = ws::Response::from_request(req).unwrap();

        let mut conn_map = self.connections.lock().unwrap();

        println!("ws:req[{}]", time::precise_time_ns());

        let cookies : HashMap<String, String> = parse_cookies(req);

        let mut cookie_existed = true;
        let ticket = cookies.get("bzwf_anon_wstx")
            .and_then(|uuid_string| Uuid::parse_str(uuid_string.as_str()).ok())
            .unwrap_or_else(|| {
                println!("ws:req[{}]: no bzwf_anon_wstx cookie found", time::precise_time_ns());
                cookie_existed = false;
                Uuid::new_v4()
            });

        let users_count = conn_map.len();

        if conn_map.contains_key(&ticket) {
            println!("ws:req[{}]: reconnect: uuid {}", time::precise_time_ns(), ticket);
            println!("{} connected users", users_count);
        } else {
            println!("ws:req[{}]: new connection with uuid {}", time::precise_time_ns(), ticket);

            let cookie_op_str = if cookie_existed {
                "replacing persistence cookie"
            } else {
                "creating persistence cookie"
            };

            println!("ws:req[{}]: {}: bzwf_anon_wstx={}", time::precise_time_ns(), cookie_op_str, ticket);
            println!("{} connected users", users_count + 1);
            put_cookie(String::from("bzwf_anon_wstx"), ticket.to_string(), &mut resp);
            conn_map.insert(ticket, Connection::new(ticket, AnonymousPlayer::new()));
        }

        Ok(resp)
    }

    fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
        println!("ws:rcv[{}]: user with uuid {} sent ws msg {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 msg);

        parse_message(msg)
            .and_then(|(msg_ticket, msg_contents)| {
                println!("authorizing ticket {}", msg_ticket);
                authorize_ticket_dumb(msg_ticket, msg_contents, self.connections.clone())
            })
            .and_then(|msg_contents| {
                msg_contents.split_first()
                    .ok_or(String::from("missing message type and params"))
                    .and_then(|(f, r)| {
                        if f.len() == 0 {
                            Err(String::from("msg_type is empty"))
                        } else {
                            Ok((f.to_owned(), Vec::from(r)))
                        }
                    })
            })
            .and_then(|(msg_type, msg_params)| {
                println!("trying to read message contents of {}({})", msg_type, msg_params.join(","));

                match msg_type.as_str() {
                    "addItemToInventory" => {
                        if msg_params.len() != 1 {
                            Err(String::from("parse_message failure: addItemToInventory: no item code"))
                        } else if let Ok(item_num) = msg_params[0].parse::<i32>() {
                            Item::parse(item_num)
                                .map(|item| Action::addItemToInventory(item))
                        } else {
                            Err(String::from("parse_message failure: addItemToInventory: invalid i32 item code"))
                        }
                    },
                    _ => Err(String::from("parse_message failure: unrecognized message type"))
                }
            })
            .map(|action| {
                self.out.send(format!("gotcha, you want to {:?}", action))
            })
            .unwrap_or_else(|err| {
                println!("{}", err);
                self.out.send("got your message, but not sure what it meant")
            });

        Ok(())
    }

    fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
        // TODO
        println!("ws:opn[{}]: user with uuid {} opened ws cxn",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]");
        Ok(())
    }

    fn on_close(&mut self, code: ws::CloseCode, reason: &str) {
        println!("ws:cls[{}]: user with uuid {} closed ws cxn\n\tCode [{:?}] reason: {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 code,
                 reason);
        // TODO
    }

    fn on_error(&mut self, err: ws::Error) {
        println!("ws:err[{}]: user with uuid {} got error {}",
                 time::precise_time_ns(),
                 "[[[ we don't have uuids in ws yet ]]]",
                 err);
        // TODO
    }
}

pub fn server (domain: String, port: i32) -> () {
    let connections : ConnectionMap = Arc::new(Mutex::new(HashMap::new()));
    let addr = format!("{}:{}", domain, port);
    println!("WebSockets server listening on {}", addr);
    ws::listen(addr, |out| WSServer {
        out: out,
        connections: connections.clone(),
    }).unwrap();
}
<|endoftext|>


<commit_before>//! Validating and decomposing a decimal string of the form:
//!
//! `(digits | digits? '.'? digits?) (('e' | 'E') ('+' | '-')? digits)?`
//!
//! In other words, standard floating-point syntax, with two exceptions: No sign, and no
//! handling of "inf" and "NaN". These are handled by the driver function (super::dec2flt).
//!
//! Although recognizing valid inputs is relatively easy, this module also has to reject the
//! countless invalid variations, never panic, and perform numerous checks that the other
//! modules rely on to not panic (or overflow) in turn.
//! To make matters worse, all that happens in a single pass over the input.
//! So, be careful when modifying anything, and double-check with the other modules.
use self::ParseResult::{Invalid, ShortcutToInf, ShortcutToZero, Valid};
use super::num;

#[derive(Debug)]
pub enum Sign {
    Positive,
    Negative,
}

#[derive(Debug, PartialEq, Eq)]
/// The interesting parts of a decimal string.
pub struct Decimal<'a> {
    pub integral: &'a [u8],
    pub fractional: &'a [u8],
    /// The decimal exponent, guaranteed to have fewer than 18 decimal digits.
    pub exp: i64,
}

impl<'a> Decimal<'a> {
    pub fn new(integral: &'a [u8], fractional: &'a [u8], exp: i64) -> Decimal<'a> {
        Decimal { integral, fractional, exp }
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum ParseResult<'a> {
    Valid(Decimal<'a>),
    ShortcutToInf,
    ShortcutToZero,
    Invalid,
}

/// Checks if the input string is a valid floating point number and if so, locate the integral
/// part, the fractional part, and the exponent in it. Does not handle signs.
pub fn parse_decimal(s: &str) -> ParseResult<'_> {
    if s.is_empty() {
        return Invalid;
    }

    let s = s.as_bytes();
    let (integral, s) = eat_digits(s);

    match s.first() {
        None => Valid(Decimal::new(integral, b"", 0)),
        Some(&b'e' | &b'E') => {
            if integral.is_empty() {
                return Invalid; // No digits before 'e'
            }

            parse_exp(integral, b"", &s[1..])
        }
        Some(&b'.') => {
            let (fractional, s) = eat_digits(&s[1..]);
            if integral.is_empty() && fractional.is_empty() {
                // We require at least a single digit before or after the point.
                return Invalid;
            }

            match s.first() {
                None => Valid(Decimal::new(integral, fractional, 0)),
                Some(&b'e' | &b'E') => parse_exp(integral, fractional, &s[1..]),
                _ => Invalid, // Trailing junk after fractional part
            }
        }
        _ => Invalid, // Trailing junk after first digit string
    }
}

/// Carves off decimal digits up to the first non-digit character.
fn eat_digits(s: &[u8]) -> (&[u8], &[u8]) {
    let mut i = 0;
    while i < s.len() && b'0' <= s[i] && s[i] <= b'9' {
        i += 1;
    }
    (&s[..i], &s[i..])
}

/// Exponent extraction and error checking.
fn parse_exp<'a>(integral: &'a [u8], fractional: &'a [u8], rest: &'a [u8]) -> ParseResult<'a> {
    let (sign, rest) = match rest.first() {
        Some(&b'-') => (Sign::Negative, &rest[1..]),
        Some(&b'+') => (Sign::Positive, &rest[1..]),
        _ => (Sign::Positive, rest),
    };
    let (mut number, trailing) = eat_digits(rest);
    if !trailing.is_empty() {
        return Invalid; // Trailing junk after exponent
    }
    if number.is_empty() {
        return Invalid; // Empty exponent
    }
    // At this point, we certainly have a valid string of digits. It may be too long to put into
    // an `i64`, but if it's that huge, the input is certainly zero or infinity. Since each zero
    // in the decimal digits only adjusts the exponent by +/- 1, at exp = 10^18 the input would
    // have to be 17 exabyte (!) of zeros to get even remotely close to being finite.
    // This is not exactly a use case we need to cater to.
    while number.first() == Some(&b'0') {
        number = &number[1..];
    }
    if number.len() >= 18 {
        return match sign {
            Sign::Positive => ShortcutToInf,
            Sign::Negative => ShortcutToZero,
        };
    }
    let abs_exp = num::from_str_unchecked(number);
    let e = match sign {
        Sign::Positive => abs_exp as i64,
        Sign::Negative => -(abs_exp as i64),
    };
    Valid(Decimal::new(integral, fractional, e))
}
<commit_msg>simplify eat_digits<commit_after>//! Validating and decomposing a decimal string of the form:
//!
//! `(digits | digits? '.'? digits?) (('e' | 'E') ('+' | '-')? digits)?`
//!
//! In other words, standard floating-point syntax, with two exceptions: No sign, and no
//! handling of "inf" and "NaN". These are handled by the driver function (super::dec2flt).
//!
//! Although recognizing valid inputs is relatively easy, this module also has to reject the
//! countless invalid variations, never panic, and perform numerous checks that the other
//! modules rely on to not panic (or overflow) in turn.
//! To make matters worse, all that happens in a single pass over the input.
//! So, be careful when modifying anything, and double-check with the other modules.
use self::ParseResult::{Invalid, ShortcutToInf, ShortcutToZero, Valid};
use super::num;

#[derive(Debug)]
pub enum Sign {
    Positive,
    Negative,
}

#[derive(Debug, PartialEq, Eq)]
/// The interesting parts of a decimal string.
pub struct Decimal<'a> {
    pub integral: &'a [u8],
    pub fractional: &'a [u8],
    /// The decimal exponent, guaranteed to have fewer than 18 decimal digits.
    pub exp: i64,
}

impl<'a> Decimal<'a> {
    pub fn new(integral: &'a [u8], fractional: &'a [u8], exp: i64) -> Decimal<'a> {
        Decimal { integral, fractional, exp }
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum ParseResult<'a> {
    Valid(Decimal<'a>),
    ShortcutToInf,
    ShortcutToZero,
    Invalid,
}

/// Checks if the input string is a valid floating point number and if so, locate the integral
/// part, the fractional part, and the exponent in it. Does not handle signs.
pub fn parse_decimal(s: &str) -> ParseResult<'_> {
    if s.is_empty() {
        return Invalid;
    }

    let s = s.as_bytes();
    let (integral, s) = eat_digits(s);

    match s.first() {
        None => Valid(Decimal::new(integral, b"", 0)),
        Some(&b'e' | &b'E') => {
            if integral.is_empty() {
                return Invalid; // No digits before 'e'
            }

            parse_exp(integral, b"", &s[1..])
        }
        Some(&b'.') => {
            let (fractional, s) = eat_digits(&s[1..]);
            if integral.is_empty() && fractional.is_empty() {
                // We require at least a single digit before or after the point.
                return Invalid;
            }

            match s.first() {
                None => Valid(Decimal::new(integral, fractional, 0)),
                Some(&b'e' | &b'E') => parse_exp(integral, fractional, &s[1..]),
                _ => Invalid, // Trailing junk after fractional part
            }
        }
        _ => Invalid, // Trailing junk after first digit string
    }
}

/// Carves off decimal digits up to the first non-digit character.
fn eat_digits(s: &[u8]) -> (&[u8], &[u8]) {
    let pos = s.iter().position(|c| !c.is_ascii_digit()).unwrap_or(s.len());
    s.split_at(pos)
}

/// Exponent extraction and error checking.
fn parse_exp<'a>(integral: &'a [u8], fractional: &'a [u8], rest: &'a [u8]) -> ParseResult<'a> {
    let (sign, rest) = match rest.first() {
        Some(&b'-') => (Sign::Negative, &rest[1..]),
        Some(&b'+') => (Sign::Positive, &rest[1..]),
        _ => (Sign::Positive, rest),
    };
    let (mut number, trailing) = eat_digits(rest);
    if !trailing.is_empty() {
        return Invalid; // Trailing junk after exponent
    }
    if number.is_empty() {
        return Invalid; // Empty exponent
    }
    // At this point, we certainly have a valid string of digits. It may be too long to put into
    // an `i64`, but if it's that huge, the input is certainly zero or infinity. Since each zero
    // in the decimal digits only adjusts the exponent by +/- 1, at exp = 10^18 the input would
    // have to be 17 exabyte (!) of zeros to get even remotely close to being finite.
    // This is not exactly a use case we need to cater to.
    while number.first() == Some(&b'0') {
        number = &number[1..];
    }
    if number.len() >= 18 {
        return match sign {
            Sign::Positive => ShortcutToInf,
            Sign::Negative => ShortcutToZero,
        };
    }
    let abs_exp = num::from_str_unchecked(number);
    let e = match sign {
        Sign::Positive => abs_exp as i64,
        Sign::Negative => -(abs_exp as i64),
    };
    Valid(Decimal::new(integral, fractional, e))
}
<|endoftext|>


<commit_before>/*!
 * Processing the markdown and resolving references.
 */

use std::{
    collections::HashSet,
    fs::File,
    io::{Read, Write},
    path::{Path, PathBuf},
};

use pulldown_cmark::{html, Event, LinkType, Parser, Tag};
use url::Url;

struct RenderAdapter<'a, 'b, 'c: 'a, I: Iterator<Item = Event<'b>>> {
    styles: &'a mut HashSet<&'c str>,
    render_stack: &'a mut Vec<PathBuf>,
    base_dir: &'a Path,
    out_dir: &'a Path,
    filename: &'a Path,
    iter: I,
}

impl<'a, 'b, 'c: 'a, I: Iterator<Item = Event<'b>>> Iterator for RenderAdapter<'a, 'b, 'c, I> {
    type Item = Event<'b>;

    fn next(&mut self) -> Option<Self::Item> {
        let item = self.iter.next()?;
        Some(match item {
            Event::Start(tag) => match tag {
                i @ Tag::Image(..) => {
                    self.styles.insert("image");
                    Event::Start(i)
                }
                p @ Tag::Paragraph => {
                    self.styles.insert("paragraph");
                    Event::Start(p)
                }
                Tag::Link(ty, url, title) => {
                    self.styles.insert("link");
                    match ty {
                        LinkType::Inline => {
                            if let Ok(parsed) = Url::parse(&url) {
                                // check if scheme is hyperref, if so add to stack and rewrite url
                                if parsed.scheme() == "hyperref" {
                                    let parsed_path: &Path = parsed.path().as_ref();
                                    let fname: PathBuf = if parsed_path.is_absolute() {
                                        self.out_dir.join(parsed_path.strip_prefix("/").unwrap())
                                    } else {
                                        self.filename
                                            .parent()
                                            .unwrap_or("/".as_ref())
                                            .join(parsed.path())
                                    }
                                    .with_extension("md");
                                    #[cfg(target_os = "windows")]
                                    // replace with backslashes so that \\?\ isn't broken
                                    let fname: PathBuf =
                                        fname.to_str().unwrap().replace("/", "\\").into();
                                    if let Ok(fname) = fname.canonicalize() {
                                        let fname_for_url =
                                            fname.strip_prefix(&self.base_dir).unwrap();
                                        #[cfg(target_os = "windows")]
                                    // windows is dumb again
                                    let fname_for_url: PathBuf =
                                        fname_for_url.to_str().unwrap().replace("\\", "/").into();
                                        // figure out new location
                                        let new_location = format!(
                                            "/{}",
                                            fname_for_url.with_extension("html").to_str().unwrap(),
                                        );
                                        if !self.render_stack.contains(&fname) {
                                            println!(
                                                "walk: {}",
                                                fname.to_str().unwrap_or("unknown")
                                            );
                                            self.render_stack.push(fname);
                                        }
                                        Event::Start(Tag::Link(ty, new_location.into(), title))
                                        // link.url = new_location.into_bytes();
                                    } else {
                                        println!("Couldn't resolve hyperref: {}", url);
                                        Event::Start(Tag::Link(ty, url, title))
                                    }
                                } else {
                                    Event::Start(Tag::Link(ty, url, title))
                                }
                            } else {
                                Event::Start(Tag::Link(ty, url, title))
                            }
                        }
                        _ => Event::Start(Tag::Link(ty, url, title)),
                    }
                }
                t => Event::Start(t),
            },
            Event::Text(s) => {
                let new_text = s.replace(":eyes:", "👀");
                Event::Text(new_text.into())
            }
            ev => ev,
        })
    }
}

pub fn render(
    filename: &Path,
    base_dir: &Path,
    out_dir: &Path,
    prelude_html: &Path,
    style_chunks_root: &Path,
    base: &str,
    force: bool,
) -> anyhow::Result<()> {
    if !filename.exists() {
        println!(
            "nonexistent: {}, nothing to do",
            filename.to_str().unwrap_or("unknown")
        );
        return Ok(());
    }

    // create out dir if doesn't exist
    if !out_dir.exists() {
        std::fs::create_dir_all(out_dir)?;
    }
    // // canonicalize paths
    // let (filename, base_dir, out_dir) = (
    //     filename.as_ref().canonicalize()?,
    //     base_dir.as_ref().canonicalize()?,
    //     out_dir.as_ref().canonicalize()?,
    // );
    // as-ref paths
    // let (filename, base_dir, out_dir) = (filename.as_ref(), base_dir.as_ref(), out_dir.as_ref());

    // NOTE: can't canonicalize here since the output path may not exist
    let out_path = out_dir
        .join(filename.strip_prefix(&base_dir)?)
        .with_extension("html");
    println!("out: {}", out_path.to_str().unwrap_or("unknown"));

    let buf = {
        let mut s = String::new();
        let mut f = File::open(&filename)?;
        f.read_to_string(&mut s)?;
        Ok::<_, std::io::Error>(s)
    }?;

    let mut styles = {
        let mut h = HashSet::new();
        h.insert("_global");
        h
    };
    let mut stack = Vec::new();

    let html = {
        let parser = Parser::new(&buf);
        let adapter = RenderAdapter {
            base_dir,
            filename,
            out_dir,
            render_stack: &mut stack,
            styles: &mut styles,
            iter: parser,
        };

        let mut s = String::new();
        html::push_html(&mut s, adapter);
        s
    };

    let styles = {
        let mut new_styles = Vec::new();
        for sname in styles.into_iter() {
            let mut s = String::new();
            let path = style_chunks_root.join(sname).with_extension("css");
            // skip missing files
            if let Ok(path) = AsRef::<Path>::as_ref(&path).canonicalize() {
                let mut f = File::open(&path)?;
                f.read_to_string(&mut s)?;
                new_styles.push(s);
            }
        }
        Ok::<_, std::io::Error>(new_styles)
    }?;
    let html = {
        println!("path = {}", prelude_html.to_str().unwrap_or("unknown"));
        let mut f = File::open(prelude_html)?;
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        Ok::<_, std::io::Error>(s)
    }?
    .replace(
        "@@@SLOT_STYLES@@@",
        &format!("<style>\n{}\n</style>", styles.join("\n")),
    )
    .replace("@@@SLOT_CONTENT@@@", &html);

    // write only if file doesn't exist
    let needs_update =
        if let (Ok(out_metadata), Ok(in_metadata)) = (out_path.metadata(), filename.metadata()) {
            in_metadata.modified()? >= out_metadata.modified()?
        } else {
            // failed to get metadata, or either path doesn't exist
            true
        };
    if !needs_update && !force {
        // nothing to do
        println!("fresh: {}", out_path.to_str().unwrap_or("unknown"));
    } else {
        // first, recursively create parents
        if let Some(p) = out_path.parent() {
            std::fs::create_dir_all(p)?;
        }

        if out_path.file_stem().unwrap().to_str().unwrap() == "_keep" {
            println!("special: _keep, no output");
        } else {
            let mut f = File::create(&out_path)?;
            f.write_all(html.as_bytes())?;
            // println!("{}", html);
            println!("new: {}", out_path.to_str().unwrap_or("unknown"));
        }
    }

    // write stack
    for fname in stack {
        let base_add = fname.strip_prefix(&base_dir).unwrap();
        let new_base = format!("{}/{}", base, base_add.to_str().unwrap());
        render(
            &fname,
            base_dir,
            out_dir,
            prelude_html,
            style_chunks_root,
            &new_base,
            force,
        )?;
    }

    Ok(())
}
<commit_msg>switch to embedding<commit_after>/*!
 * Processing the markdown and resolving references.
 */

use std::{
    collections::HashSet,
    fs::File,
    io::{Read, Write},
    path::{Path, PathBuf},
};

use pulldown_cmark::{html, Event, LinkType, Parser, Tag};
use url::Url;

struct RenderAdapter<'a, 'b, 'c: 'a, I: Iterator<Item = Event<'b>>> {
    styles: &'a mut HashSet<&'c str>,
    render_stack: &'a mut Vec<PathBuf>,
    base_dir: &'a Path,
    out_dir: &'a Path,
    filename: &'a Path,
    iter: I,
}

impl<'a, 'b, 'c: 'a, I: Iterator<Item = Event<'b>>> Iterator for RenderAdapter<'a, 'b, 'c, I> {
    type Item = Event<'b>;

    fn next(&mut self) -> Option<Self::Item> {
        let item = self.iter.next()?;
        Some(match item {
            Event::Start(tag) => match tag {
                i @ Tag::Image(..) => {
                    self.styles.insert("image");
                    Event::Start(i)
                }
                p @ Tag::Paragraph => {
                    self.styles.insert("paragraph");
                    Event::Start(p)
                }
                Tag::Link(ty, url, title) => {
                    self.styles.insert("link");
                    match ty {
                        LinkType::Inline => {
                            if let Ok(parsed) = Url::parse(&url) {
                                // check if scheme is hyperref, if so add to stack and rewrite url
                                if parsed.scheme() == "hyperref" {
                                    let parsed_path: &Path = parsed.path().as_ref();
                                    let fname: PathBuf = if parsed_path.is_absolute() {
                                        self.out_dir.join(parsed_path.strip_prefix("/").unwrap())
                                    } else {
                                        self.filename
                                            .parent()
                                            .unwrap_or("/".as_ref())
                                            .join(parsed.path())
                                    }
                                    .with_extension("md");
                                    #[cfg(target_os = "windows")]
                                    // replace with backslashes so that \\?\ isn't broken
                                    let fname: PathBuf =
                                        fname.to_str().unwrap().replace("/", "\\").into();
                                    if let Ok(fname) = fname.canonicalize() {
                                        let fname_for_url =
                                            fname.strip_prefix(&self.base_dir).unwrap();
                                        #[cfg(target_os = "windows")]
                                    // windows is dumb again
                                    let fname_for_url: PathBuf =
                                        fname_for_url.to_str().unwrap().replace("\\", "/").into();
                                        // figure out new location
                                        let new_location = format!(
                                            "/{}",
                                            fname_for_url.with_extension("html").to_str().unwrap(),
                                        );
                                        if !self.render_stack.contains(&fname) {
                                            println!(
                                                "walk: {}",
                                                fname.to_str().unwrap_or("unknown")
                                            );
                                            self.render_stack.push(fname);
                                        }
                                        Event::Start(Tag::Link(ty, new_location.into(), title))
                                        // link.url = new_location.into_bytes();
                                    } else {
                                        println!("Couldn't resolve hyperref: {}", url);
                                        Event::Start(Tag::Link(ty, url, title))
                                    }
                                } else {
                                    Event::Start(Tag::Link(ty, url, title))
                                }
                            } else {
                                Event::Start(Tag::Link(ty, url, title))
                            }
                        }
                        _ => Event::Start(Tag::Link(ty, url, title)),
                    }
                }
                t => Event::Start(t),
            },
            Event::Text(s) => {
                let new_text = s.replace(":eyes:", "👀");
                Event::Text(new_text.into())
            }
            ev => ev,
        })
    }
}

pub fn render(
    filename: &Path,
    base_dir: &Path,
    out_dir: &Path,
    prelude_html: &Path,
    style_chunks_root: &Path,
    base: &str,
    force: bool,
) -> anyhow::Result<()> {
    if !filename.exists() {
        println!(
            "nonexistent: {}, nothing to do",
            filename.to_str().unwrap_or("unknown")
        );
        return Ok(());
    }

    // create out dir if doesn't exist
    if !out_dir.exists() {
        std::fs::create_dir_all(out_dir)?;
    }
    // // canonicalize paths
    // let (filename, base_dir, out_dir) = (
    //     filename.as_ref().canonicalize()?,
    //     base_dir.as_ref().canonicalize()?,
    //     out_dir.as_ref().canonicalize()?,
    // );
    // as-ref paths
    // let (filename, base_dir, out_dir) = (filename.as_ref(), base_dir.as_ref(), out_dir.as_ref());

    // NOTE: can't canonicalize here since the output path may not exist
    let out_path = out_dir
        .join(filename.strip_prefix(&base_dir)?)
        .with_extension("html");
    println!("out: {}", out_path.to_str().unwrap_or("unknown"));

    let buf = {
        let mut s = String::new();
        let mut f = File::open(&filename)?;
        f.read_to_string(&mut s)?;
        Ok::<_, std::io::Error>(s)
    }?;

    let mut styles = {
        let mut h = HashSet::new();
        h.insert("_global");
        h
    };
    let mut stack = Vec::new();

    let html = {
        let parser = Parser::new(&buf);
        let adapter = RenderAdapter {
            base_dir,
            filename,
            out_dir,
            render_stack: &mut stack,
            styles: &mut styles,
            iter: parser,
        };

        let mut s = String::new();
        html::push_html(&mut s, adapter);
        s
    };

    let styles = {
        let mut new_styles = Vec::new();
        for sname in styles.into_iter() {
            let path = style_chunks_root.join(sname).with_extension("css");
            // skip missing files
            if let Ok(path) = AsRef::<Path>::as_ref(&path).canonicalize() {
                let css_out_path = out_dir.join("css").join(sname).with_extension("css");
                if let Some(p) = css_out_path.parent() {
                    std::fs::create_dir_all(p)?;
                }
                std::fs::copy(&path, &css_out_path)?;
                new_styles.push(format!(
                    r#"
<link rel="preload" href="/{0}" as="style" />
<link rel="stylesheet" type="text/css" href="/{0}" />
"#,
                    css_out_path
                        .strip_prefix(out_dir)
                        .unwrap_or(&css_out_path)
                        .to_str()
                        .unwrap_or("unknown")
                        .replace("\\", "/")
                ));
            }
        }
        Ok::<_, std::io::Error>(new_styles)
    }?;
    let html = {
        println!("path = {}", prelude_html.to_str().unwrap_or("unknown"));
        let mut f = File::open(prelude_html)?;
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        Ok::<_, std::io::Error>(s)
    }?
    .replace("@@@SLOT_STYLES@@@", &format!("\n{}\n", styles.join("\n")))
    .replace("@@@SLOT_CONTENT@@@", &html);

    // write only if file doesn't exist
    let needs_update =
        if let (Ok(out_metadata), Ok(in_metadata)) = (out_path.metadata(), filename.metadata()) {
            in_metadata.modified()? >= out_metadata.modified()?
        } else {
            // failed to get metadata, or either path doesn't exist
            true
        };
    if !needs_update && !force {
        // nothing to do
        println!("fresh: {}", out_path.to_str().unwrap_or("unknown"));
    } else {
        // first, recursively create parents
        if let Some(p) = out_path.parent() {
            std::fs::create_dir_all(p)?;
        }

        if out_path.file_stem().unwrap().to_str().unwrap() == "_keep" {
            println!("special: _keep, no output");
        } else {
            let mut f = File::create(&out_path)?;
            f.write_all(html.as_bytes())?;
            // println!("{}", html);
            println!("new: {}", out_path.to_str().unwrap_or("unknown"));
        }
    }

    // write stack
    for fname in stack {
        let base_add = fname.strip_prefix(&base_dir).unwrap();
        let new_base = format!("{}/{}", base, base_add.to_str().unwrap());
        render(
            &fname,
            base_dir,
            out_dir,
            prelude_html,
            style_chunks_root,
            &new_base,
            force,
        )?;
    }

    Ok(())
}
<|endoftext|>


<commit_before>#![cfg(feature = "serde")]

extern crate serde;

use EnumMap;
use Internal;

use self::serde::ser::{Serialize, Serializer, SerializeMap};
use self::serde::de::{self, Deserialize, Deserializer, Error, MapAccess};

use core::fmt;
use core::marker::PhantomData;

/// Requires crate feature `"serde"`
impl<K: Internal<V> + Serialize, V: Serialize> Serialize for EnumMap<K, V> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut map = serializer.serialize_map(Some(self.len()))?;
        for (key, value) in self {
            map.serialize_entry(&key, value)?;
        }
        map.end()
    }
}

/// Requires crate feature `"serde"`
impl<'de, K, V> Deserialize<'de> for EnumMap<K, V>
where
    K: Internal<V>
        + Internal<Option<V>>
        + Deserialize<'de>,
    V: Deserialize<'de>,
{
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        deserializer.deserialize_map(Visitor(PhantomData))
    }
}

struct Visitor<K, V>(PhantomData<(K, V)>);

impl<'de, K, V> de::Visitor<'de> for Visitor<K, V>
where
    K: Internal<V>
        + Internal<Option<V>>
        + Deserialize<'de>,
    V: Deserialize<'de>,
{
    type Value = EnumMap<K, V>;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "a map")
    }

    fn visit_map<M: MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
        let mut entries = enum_map! { _ => None };
        while let Some((key, value)) = access.next_entry()? {
            entries[key] = Some(value);
        }
        for value in entries.values() {
            value.as_ref().ok_or_else(|| M::Error::custom("key not specified"))?;
        }
        Ok(enum_map! { key => entries[key].take().unwrap() })
    }
}
<commit_msg>Now EnumMap::new can be properly used<commit_after>#![cfg(feature = "serde")]

extern crate serde;

use EnumMap;
use Internal;

use self::serde::ser::{Serialize, Serializer, SerializeMap};
use self::serde::de::{self, Deserialize, Deserializer, Error, MapAccess};

use core::fmt;
use core::marker::PhantomData;

/// Requires crate feature `"serde"`
impl<K: Internal<V> + Serialize, V: Serialize> Serialize for EnumMap<K, V> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut map = serializer.serialize_map(Some(self.len()))?;
        for (key, value) in self {
            map.serialize_entry(&key, value)?;
        }
        map.end()
    }
}

/// Requires crate feature `"serde"`
impl<'de, K, V> Deserialize<'de> for EnumMap<K, V>
where
    K: Internal<V>
        + Internal<Option<V>>
        + Deserialize<'de>,
    V: Deserialize<'de>,
{
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        deserializer.deserialize_map(Visitor(PhantomData))
    }
}

struct Visitor<K, V>(PhantomData<(K, V)>);

impl<'de, K, V> de::Visitor<'de> for Visitor<K, V>
where
    K: Internal<V>
        + Internal<Option<V>>
        + Deserialize<'de>,
    V: Deserialize<'de>,
{
    type Value = EnumMap<K, V>;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "a map")
    }

    fn visit_map<M: MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
        let mut entries = EnumMap::new();
        while let Some((key, value)) = access.next_entry()? {
            entries[key] = Some(value);
        }
        for value in entries.values() {
            value.as_ref().ok_or_else(|| M::Error::custom("key not specified"))?;
        }
        Ok(enum_map! { key => entries[key].take().unwrap() })
    }
}
<|endoftext|>


<commit_before>extern crate gl;
extern crate glfw;

use gl::types::*;
use glfw::{Context, OpenGlProfileHint, WindowHint, WindowMode};

fn main() {
    let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();

    glfw.window_hint(WindowHint::ContextVersion(3, 2));
    glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
    glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
    glfw.window_hint(WindowHint::Resizable(false));

    let (mut window, events) = glfw.create_window(800, 600, "OpenGL", WindowMode::Windowed)
        .expect("Failed to create GLFW window.");

    // Listen for keyboard events on this window.
    window.set_key_polling(true);

    // Make this window's OpenGL context the current context. This must be done before calling
    // `gl::load_with`.
    window.make_current();

    // Load OpenGL function pointers.
    gl::load_with(|symbol| window.get_proc_address(symbol));

    static VERTICES: [GLfloat; 6] = [
         0.0,  0.5,
         0.5, -0.5,
        -0.5, -0.5,
    ];

    let mut vbo = 0;

    unsafe {
        gl::GenBuffers(1, &mut vbo);
        gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl::BufferData(gl::ARRAY_BUFFER,
                       (VERTICES.len() * std::mem::size_of::<GLfloat>()) as GLsizeiptr,
                       &VERTICES as *const _ as *const GLvoid,
                       gl::STATIC_DRAW);
    }

    while !window.should_close() {
        glfw.poll_events();

        for (_, event) in glfw::flush_messages(&events) {
            handle_window_event(&mut window, event);
        }

        window.swap_buffers();
    }
}

fn handle_window_event(window: &mut glfw::Window, event: glfw::WindowEvent) {
    use glfw::{Action, Key, WindowEvent};

    match event {
        WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
            window.set_should_close(true);
        },
        _ => {},
    }
}
<commit_msg>Compile vertex and fragment shaders with error checking.<commit_after>extern crate gl;
extern crate glfw;

use gl::types::*;
use glfw::{Context, OpenGlProfileHint, WindowHint, WindowMode};

fn main() {
    let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();

    glfw.window_hint(WindowHint::ContextVersion(3, 2));
    glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
    glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
    glfw.window_hint(WindowHint::Resizable(false));

    let (mut window, events) = glfw.create_window(800, 600, "OpenGL", WindowMode::Windowed)
        .expect("Failed to create GLFW window.");

    // Listen for keyboard events on this window.
    window.set_key_polling(true);

    // Make this window's OpenGL context the current context. This must be done before calling
    // `gl::load_with`.
    window.make_current();

    // Load OpenGL function pointers.
    gl::load_with(|symbol| window.get_proc_address(symbol));

    static VERTICES: [GLfloat; 6] = [
         0.0,  0.5,
         0.5, -0.5,
        -0.5, -0.5,
    ];

    let mut vbo = 0;

    unsafe {
        gl::GenBuffers(1, &mut vbo);
        gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl::BufferData(gl::ARRAY_BUFFER,
                       (VERTICES.len() * std::mem::size_of::<GLfloat>()) as GLsizeiptr,
                       &VERTICES as *const _ as *const GLvoid,
                       gl::STATIC_DRAW);
    }

    const VERTEX_SHADER_SOURCE: &'static str = "
        #version 150

        in vec2 position;

        void main() {
            gl_Position = vec4(position, 0.0, 1.0);
        }
    ";

    const FRAGMENT_SHADER_SOURCE: &'static str = "
        #version 150

        out vec4 out_color;

        void main() {
            out_color = vec4(1.0, 1.0, 1.0, 1.0);
        }
    ";

    fn compile_shader(shader_type: GLenum, source: &str) -> Result<GLuint, String> {
        let shader;
        unsafe {
            shader = gl::CreateShader(shader_type);
            let source_ptr = source.as_bytes().as_ptr() as *const GLchar;
            let source_len = source.len() as GLint;
            gl::ShaderSource(shader, 1, &source_ptr, &source_len);
            gl::CompileShader(shader);

            let mut status = gl::FALSE as GLint;
            gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

            if status == gl::TRUE as GLint {
                Ok(shader)
            } else {
                let mut len = 0;
                gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
                if len == 0 { return Err(String::new()) }

                let mut buf = Vec::with_capacity(len as usize);
                buf.set_len(len as usize - 1); // Subtract 1 to ignore the trailing null.
                gl::GetShaderInfoLog(shader, len, std::ptr::null_mut(),
                                     buf.as_mut_ptr() as *mut GLchar);

                Err(String::from_utf8_lossy(&buf).into_owned())
            }
        }
    }

    let vertex_shader = compile_shader(gl::VERTEX_SHADER, VERTEX_SHADER_SOURCE).unwrap();
    let fragment_shader = compile_shader(gl::FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE).unwrap();

    while !window.should_close() {
        glfw.poll_events();

        for (_, event) in glfw::flush_messages(&events) {
            handle_window_event(&mut window, event);
        }

        window.swap_buffers();
    }
}

fn handle_window_event(window: &mut glfw::Window, event: glfw::WindowEvent) {
    use glfw::{Action, Key, WindowEvent};

    match event {
        WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
            window.set_should_close(true);
        },
        _ => {},
    }
}
<|endoftext|>


<commit_before>extern crate gl;
extern crate glfw;
extern crate imagefmt;
extern crate time;

mod math;

use gl::types::*;
use glfw::{Context, OpenGlProfileHint, WindowHint, WindowMode};
use std::mem;
use std::ptr;

macro_rules! gl_str {
    ($string_literal:expr) => (
        concat!($string_literal, '\0').as_bytes().as_ptr() as *const GLchar
    )
}

const VERTEX_SHADER_SOURCE: &'static str = "
    #version 150

    in vec2 position;
    in vec3 color;
    in vec2 texcoord;

    out vec3 Color;
    out vec2 Texcoord;

    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 proj;

    void main() {
        Color = color;
        Texcoord = texcoord;
        gl_Position = proj * view * model * vec4(position, 0.0, 1.0);
    }
";

const FRAGMENT_SHADER_SOURCE: &'static str = "
    #version 150

    in vec3 Color;
    in vec2 Texcoord;

    out vec4 out_color;

    uniform sampler2D tex_kitten;
    uniform sampler2D tex_puppy;
    uniform float time;

    void main() {
        float mix_factor = (sin(time * 3.0) + 1.0) / 2.0;
        vec4 col_kitten = texture(tex_kitten, Texcoord);
        vec4 col_puppy = texture(tex_puppy, Texcoord);
        vec4 mixed_texture = mix(col_kitten, col_puppy, mix_factor);
        out_color = mix(vec4(Color, 1.0), mixed_texture, 0.25);
    }
";

#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C, packed)]
struct Vertex {
    // Position.
    x: GLfloat, y: GLfloat,

    // Color.
    r: GLfloat, g: GLfloat, b: GLfloat,

    // Texture.
    s: GLfloat, t: GLfloat,
}

static VERTICES: [Vertex; 4] = [
    Vertex { x: -0.5, y:  0.5, r: 1.0, g: 0.0, b: 0.0, s: 0.0, t: 0.0 }, // Top-left
    Vertex { x:  0.5, y:  0.5, r: 0.0, g: 1.0, b: 0.0, s: 1.0, t: 0.0 }, // Top-right
    Vertex { x:  0.5, y: -0.5, r: 0.0, g: 0.0, b: 1.0, s: 1.0, t: 1.0 }, // Bottom-right
    Vertex { x: -0.5, y: -0.5, r: 1.0, g: 1.0, b: 1.0, s: 0.0, t: 1.0 }, // Bottom-left
];

static ELEMENTS: [GLuint; 6] = [
    0, 1, 2, // Top-right triangle
    2, 3, 0, // Bottom-left triangle
];

unsafe fn compile_shader(shader_type: GLenum, source: &str) -> Result<GLuint, String> {
    let shader = gl::CreateShader(shader_type);
    let source_ptr = source.as_bytes().as_ptr() as *const GLchar;
    let source_len = source.len() as GLint;
    gl::ShaderSource(shader, 1, &source_ptr, &source_len);
    gl::CompileShader(shader);

    let mut status = gl::FALSE as GLint;
    gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

    if status == gl::TRUE as GLint {
        Ok(shader)
    } else {
        let mut log_len = 0;
        gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut log_len);
        if log_len == 0 { return Err(String::new()) }

        let mut buf = Vec::with_capacity(log_len as usize);
        buf.set_len(log_len as usize - 1); // Subtract 1 to ignore the trailing null.
        gl::GetShaderInfoLog(shader, log_len, ptr::null_mut(), buf.as_mut_ptr() as *mut GLchar);

        Err(String::from_utf8_lossy(&buf).into_owned())
    }
}

fn main() {
    let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();

    glfw.window_hint(WindowHint::ContextVersion(3, 2));
    glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
    glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
    glfw.window_hint(WindowHint::Resizable(false));

    let (mut window, events) = glfw.create_window(800, 600, "OpenGL", WindowMode::Windowed)
        .expect("Failed to create GLFW window.");

    // Listen for keyboard events on this window.
    window.set_key_polling(true);

    // Make this window's OpenGL context the current context. This must be done before calling
    // `gl::load_with`.
    window.make_current();

    // Load OpenGL function pointers.
    gl::load_with(|symbol| window.get_proc_address(symbol));

    let vertex_shader;
    let fragment_shader;
    let shader_program;
    let mut vao = 0;
    let mut vbo = 0;
    let mut ebo = 0;
    let mut textures = [0; 2];

    unsafe {
        // Create a vertex array object.
        gl::GenVertexArrays(1, &mut vao);
        gl::BindVertexArray(vao);

        // Create a vertex buffer object and copy the vertex data to it.
        gl::GenBuffers(1, &mut vbo);
        gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl::BufferData(gl::ARRAY_BUFFER,
                       mem::size_of_val(&VERTICES) as GLsizeiptr,
                       VERTICES.as_ptr() as *const GLvoid,
                       gl::STATIC_DRAW);

        // Create an element buffer object and copy the element data to it.
        gl::GenBuffers(1, &mut ebo);
        gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
        gl::BufferData(gl::ELEMENT_ARRAY_BUFFER,
                       mem::size_of_val(&ELEMENTS) as GLsizeiptr,
                       ELEMENTS.as_ptr() as *const GLvoid,
                       gl::STATIC_DRAW);

        // Compile the vertex and fragment shaders.
        vertex_shader = compile_shader(gl::VERTEX_SHADER, VERTEX_SHADER_SOURCE).unwrap();
        fragment_shader = compile_shader(gl::FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE).unwrap();

        // Link the vertex and fragment shaders into a shader program.
        shader_program = gl::CreateProgram();
        gl::AttachShader(shader_program, vertex_shader);
        gl::AttachShader(shader_program, fragment_shader);
        gl::BindFragDataLocation(shader_program, 0, gl_str!("out_color"));
        gl::LinkProgram(shader_program);
        gl::UseProgram(shader_program);

        // Specify the layout of the vertex data.
        let position_attrib = gl::GetAttribLocation(shader_program, gl_str!("position"));
        gl::EnableVertexAttribArray(position_attrib as GLuint);
        gl::VertexAttribPointer(position_attrib as GLuint, 2, gl::FLOAT, gl::FALSE,
                                mem::size_of::<Vertex>() as GLint, ptr::null());

        let position_attrib = gl::GetAttribLocation(shader_program, gl_str!("color"));
        gl::EnableVertexAttribArray(position_attrib as GLuint);
        gl::VertexAttribPointer(position_attrib as GLuint, 3, gl::FLOAT, gl::FALSE,
                                mem::size_of::<Vertex>() as GLint,
                                (2 * mem::size_of::<GLfloat>()) as *const GLvoid);

        let position_attrib = gl::GetAttribLocation(shader_program, gl_str!("texcoord"));
        gl::EnableVertexAttribArray(position_attrib as GLuint);
        gl::VertexAttribPointer(position_attrib as GLuint, 2, gl::FLOAT, gl::FALSE,
                                mem::size_of::<Vertex>() as GLint,
                                (5 * mem::size_of::<GLfloat>()) as *const GLvoid);

        // Create and load textures.
        gl::GenTextures(2, textures.as_mut_ptr());

        gl::ActiveTexture(gl::TEXTURE0);
        gl::BindTexture(gl::TEXTURE_2D, textures[0]);
        let image = imagefmt::read("sample.png", imagefmt::ColFmt::RGB).unwrap();
        gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGB as GLint, image.w as GLint, image.h as GLint,
                       0, gl::RGB, gl::UNSIGNED_BYTE, image.buf.as_ptr() as *const GLvoid);
        gl::Uniform1i(gl::GetUniformLocation(shader_program, gl_str!("tex_kitten")), 0);

        gl::GenerateMipmap(gl::TEXTURE_2D);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER,
                          gl::LINEAR_MIPMAP_LINEAR as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER,
                          gl::LINEAR_MIPMAP_LINEAR as GLint);

        gl::ActiveTexture(gl::TEXTURE1);
        gl::BindTexture(gl::TEXTURE_2D, textures[1]);
        let image = imagefmt::read("sample2.png", imagefmt::ColFmt::RGB).unwrap();
        gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGB as GLint, image.w as GLint, image.h as GLint,
                       0, gl::RGB, gl::UNSIGNED_BYTE, image.buf.as_ptr() as *const GLvoid);
        gl::Uniform1i(gl::GetUniformLocation(shader_program, gl_str!("tex_puppy")), 1);

        gl::GenerateMipmap(gl::TEXTURE_2D);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER,
                          gl::LINEAR_MIPMAP_LINEAR as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER,
                          gl::LINEAR_MIPMAP_LINEAR as GLint);
    }

    let view = math::Mat4::look_at(
        math::Vec3([1.2, 1.2, 1.2]),
        math::Vec3([0.0, 0.0, 0.0]),
        math::Vec3([0.0, 0.0, 1.0]));
    let proj = math::Mat4::perspective(math::TAU / 8.0, 800.0 / 600.0, 1.0, 10.0);

    let model_uniform = unsafe { gl::GetUniformLocation(shader_program, gl_str!("model")) };
    let view_uniform = unsafe { gl::GetUniformLocation(shader_program, gl_str!("view")) };
    let proj_uniform = unsafe { gl::GetUniformLocation(shader_program, gl_str!("proj")) };

    unsafe {
        gl::UniformMatrix4fv(view_uniform, 1, gl::FALSE, &view[0][0]);
        gl::UniformMatrix4fv(proj_uniform, 1, gl::FALSE, &proj[0][0]);
    }

    let time_uniform = unsafe { gl::GetUniformLocation(shader_program, gl_str!("time")) };
    let time_start = time::precise_time_ns();

    while !window.should_close() {
        glfw.poll_events();
        for (_, event) in glfw::flush_messages(&events) {
            handle_window_event(&mut window, event);
        }

        unsafe {
            // Update the `time` uniform.
            let time_now = time::precise_time_ns();
            let elapsed_seconds = (time_now - time_start) as f32 / 1e9;
            gl::Uniform1f(time_uniform, elapsed_seconds);

            // Vary the rotation matrix over time.
            let model = math::Mat4::rotate_z(math::TAU / 2.0 * elapsed_seconds);
            gl::UniformMatrix4fv(model_uniform, 1, gl::FALSE, &model[0][0]);

            // Clear the screen to black.
            gl::ClearColor(0.0, 0.0, 0.0, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);

            // Draw the triangles described by the elements array.
            gl::DrawElements(gl::TRIANGLES, ELEMENTS.len() as GLint, gl::UNSIGNED_INT,
                             ptr::null());
        }

        window.swap_buffers();
    }

    unsafe {
        gl::DeleteTextures(2, textures.as_ptr());
        gl::DeleteProgram(shader_program);
        gl::DeleteShader(fragment_shader);
        gl::DeleteShader(vertex_shader);
        gl::DeleteBuffers(1, &ebo);
        gl::DeleteBuffers(1, &vbo);
        gl::DeleteVertexArrays(1, &vao);
    }
}

fn handle_window_event(window: &mut glfw::Window, event: glfw::WindowEvent) {
    use glfw::{Action, Key, WindowEvent};

    match event {
        WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
            window.set_should_close(true);
        },
        _ => {},
    }
}
<commit_msg>Add zooming animation.<commit_after>extern crate gl;
extern crate glfw;
extern crate imagefmt;
extern crate time;

mod math;

use gl::types::*;
use glfw::{Context, OpenGlProfileHint, WindowHint, WindowMode};
use std::mem;
use std::ptr;

macro_rules! gl_str {
    ($string_literal:expr) => (
        concat!($string_literal, '\0').as_bytes().as_ptr() as *const GLchar
    )
}

const VERTEX_SHADER_SOURCE: &'static str = "
    #version 150

    in vec2 position;
    in vec3 color;
    in vec2 texcoord;

    out vec3 Color;
    out vec2 Texcoord;

    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 proj;

    void main() {
        Color = color;
        Texcoord = texcoord;
        gl_Position = proj * view * model * vec4(position, 0.0, 1.0);
    }
";

const FRAGMENT_SHADER_SOURCE: &'static str = "
    #version 150

    in vec3 Color;
    in vec2 Texcoord;

    out vec4 out_color;

    uniform sampler2D tex_kitten;
    uniform sampler2D tex_puppy;
    uniform float time;

    void main() {
        float mix_factor = (sin(time * 3.0) + 1.0) / 2.0;
        vec4 col_kitten = texture(tex_kitten, Texcoord);
        vec4 col_puppy = texture(tex_puppy, Texcoord);
        vec4 mixed_texture = mix(col_kitten, col_puppy, mix_factor);
        out_color = mix(vec4(Color, 1.0), mixed_texture, 0.25);
    }
";

#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C, packed)]
struct Vertex {
    // Position.
    x: GLfloat, y: GLfloat,

    // Color.
    r: GLfloat, g: GLfloat, b: GLfloat,

    // Texture.
    s: GLfloat, t: GLfloat,
}

static VERTICES: [Vertex; 4] = [
    Vertex { x: -0.5, y:  0.5, r: 1.0, g: 0.0, b: 0.0, s: 0.0, t: 0.0 }, // Top-left
    Vertex { x:  0.5, y:  0.5, r: 0.0, g: 1.0, b: 0.0, s: 1.0, t: 0.0 }, // Top-right
    Vertex { x:  0.5, y: -0.5, r: 0.0, g: 0.0, b: 1.0, s: 1.0, t: 1.0 }, // Bottom-right
    Vertex { x: -0.5, y: -0.5, r: 1.0, g: 1.0, b: 1.0, s: 0.0, t: 1.0 }, // Bottom-left
];

static ELEMENTS: [GLuint; 6] = [
    0, 1, 2, // Top-right triangle
    2, 3, 0, // Bottom-left triangle
];

unsafe fn compile_shader(shader_type: GLenum, source: &str) -> Result<GLuint, String> {
    let shader = gl::CreateShader(shader_type);
    let source_ptr = source.as_bytes().as_ptr() as *const GLchar;
    let source_len = source.len() as GLint;
    gl::ShaderSource(shader, 1, &source_ptr, &source_len);
    gl::CompileShader(shader);

    let mut status = gl::FALSE as GLint;
    gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

    if status == gl::TRUE as GLint {
        Ok(shader)
    } else {
        let mut log_len = 0;
        gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut log_len);
        if log_len == 0 { return Err(String::new()) }

        let mut buf = Vec::with_capacity(log_len as usize);
        buf.set_len(log_len as usize - 1); // Subtract 1 to ignore the trailing null.
        gl::GetShaderInfoLog(shader, log_len, ptr::null_mut(), buf.as_mut_ptr() as *mut GLchar);

        Err(String::from_utf8_lossy(&buf).into_owned())
    }
}

fn main() {
    let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();

    glfw.window_hint(WindowHint::ContextVersion(3, 2));
    glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
    glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
    glfw.window_hint(WindowHint::Resizable(false));

    let (mut window, events) = glfw.create_window(800, 600, "OpenGL", WindowMode::Windowed)
        .expect("Failed to create GLFW window.");

    // Listen for keyboard events on this window.
    window.set_key_polling(true);

    // Make this window's OpenGL context the current context. This must be done before calling
    // `gl::load_with`.
    window.make_current();

    // Load OpenGL function pointers.
    gl::load_with(|symbol| window.get_proc_address(symbol));

    let vertex_shader;
    let fragment_shader;
    let shader_program;
    let mut vao = 0;
    let mut vbo = 0;
    let mut ebo = 0;
    let mut textures = [0; 2];

    unsafe {
        // Create a vertex array object.
        gl::GenVertexArrays(1, &mut vao);
        gl::BindVertexArray(vao);

        // Create a vertex buffer object and copy the vertex data to it.
        gl::GenBuffers(1, &mut vbo);
        gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl::BufferData(gl::ARRAY_BUFFER,
                       mem::size_of_val(&VERTICES) as GLsizeiptr,
                       VERTICES.as_ptr() as *const GLvoid,
                       gl::STATIC_DRAW);

        // Create an element buffer object and copy the element data to it.
        gl::GenBuffers(1, &mut ebo);
        gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
        gl::BufferData(gl::ELEMENT_ARRAY_BUFFER,
                       mem::size_of_val(&ELEMENTS) as GLsizeiptr,
                       ELEMENTS.as_ptr() as *const GLvoid,
                       gl::STATIC_DRAW);

        // Compile the vertex and fragment shaders.
        vertex_shader = compile_shader(gl::VERTEX_SHADER, VERTEX_SHADER_SOURCE).unwrap();
        fragment_shader = compile_shader(gl::FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE).unwrap();

        // Link the vertex and fragment shaders into a shader program.
        shader_program = gl::CreateProgram();
        gl::AttachShader(shader_program, vertex_shader);
        gl::AttachShader(shader_program, fragment_shader);
        gl::BindFragDataLocation(shader_program, 0, gl_str!("out_color"));
        gl::LinkProgram(shader_program);
        gl::UseProgram(shader_program);

        // Specify the layout of the vertex data.
        let position_attrib = gl::GetAttribLocation(shader_program, gl_str!("position"));
        gl::EnableVertexAttribArray(position_attrib as GLuint);
        gl::VertexAttribPointer(position_attrib as GLuint, 2, gl::FLOAT, gl::FALSE,
                                mem::size_of::<Vertex>() as GLint, ptr::null());

        let position_attrib = gl::GetAttribLocation(shader_program, gl_str!("color"));
        gl::EnableVertexAttribArray(position_attrib as GLuint);
        gl::VertexAttribPointer(position_attrib as GLuint, 3, gl::FLOAT, gl::FALSE,
                                mem::size_of::<Vertex>() as GLint,
                                (2 * mem::size_of::<GLfloat>()) as *const GLvoid);

        let position_attrib = gl::GetAttribLocation(shader_program, gl_str!("texcoord"));
        gl::EnableVertexAttribArray(position_attrib as GLuint);
        gl::VertexAttribPointer(position_attrib as GLuint, 2, gl::FLOAT, gl::FALSE,
                                mem::size_of::<Vertex>() as GLint,
                                (5 * mem::size_of::<GLfloat>()) as *const GLvoid);

        // Create and load textures.
        gl::GenTextures(2, textures.as_mut_ptr());

        gl::ActiveTexture(gl::TEXTURE0);
        gl::BindTexture(gl::TEXTURE_2D, textures[0]);
        let image = imagefmt::read("sample.png", imagefmt::ColFmt::RGB).unwrap();
        gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGB as GLint, image.w as GLint, image.h as GLint,
                       0, gl::RGB, gl::UNSIGNED_BYTE, image.buf.as_ptr() as *const GLvoid);
        gl::Uniform1i(gl::GetUniformLocation(shader_program, gl_str!("tex_kitten")), 0);

        gl::GenerateMipmap(gl::TEXTURE_2D);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER,
                          gl::LINEAR_MIPMAP_LINEAR as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER,
                          gl::LINEAR_MIPMAP_LINEAR as GLint);

        gl::ActiveTexture(gl::TEXTURE1);
        gl::BindTexture(gl::TEXTURE_2D, textures[1]);
        let image = imagefmt::read("sample2.png", imagefmt::ColFmt::RGB).unwrap();
        gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGB as GLint, image.w as GLint, image.h as GLint,
                       0, gl::RGB, gl::UNSIGNED_BYTE, image.buf.as_ptr() as *const GLvoid);
        gl::Uniform1i(gl::GetUniformLocation(shader_program, gl_str!("tex_puppy")), 1);

        gl::GenerateMipmap(gl::TEXTURE_2D);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER,
                          gl::LINEAR_MIPMAP_LINEAR as GLint);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER,
                          gl::LINEAR_MIPMAP_LINEAR as GLint);
    }

    let view = math::Mat4::look_at(
        math::Vec3([1.2, 1.2, 1.2]),
        math::Vec3([0.0, 0.0, 0.0]),
        math::Vec3([0.0, 0.0, 1.0]));
    let proj = math::Mat4::perspective(math::TAU / 8.0, 800.0 / 600.0, 1.0, 10.0);

    let model_uniform = unsafe { gl::GetUniformLocation(shader_program, gl_str!("model")) };
    let view_uniform = unsafe { gl::GetUniformLocation(shader_program, gl_str!("view")) };
    let proj_uniform = unsafe { gl::GetUniformLocation(shader_program, gl_str!("proj")) };

    unsafe {
        gl::UniformMatrix4fv(view_uniform, 1, gl::FALSE, &view[0][0]);
        gl::UniformMatrix4fv(proj_uniform, 1, gl::FALSE, &proj[0][0]);
    }

    let time_uniform = unsafe { gl::GetUniformLocation(shader_program, gl_str!("time")) };
    let time_start = time::precise_time_ns();

    while !window.should_close() {
        glfw.poll_events();
        for (_, event) in glfw::flush_messages(&events) {
            handle_window_event(&mut window, event);
        }

        unsafe {
            // Update the `time` uniform.
            let time_now = time::precise_time_ns();
            let elapsed_seconds = (time_now - time_start) as f32 / 1e9;
            gl::Uniform1f(time_uniform, elapsed_seconds);

            // Vary the model matrix over time.
            let scale = (elapsed_seconds * 5.0).sin() * 0.25 + 0.75;
            let model =
                math::Mat4::rotate_z(math::TAU / 2.0 * elapsed_seconds) *
                math::Mat4::scale(scale, scale, scale);
            gl::UniformMatrix4fv(model_uniform, 1, gl::FALSE, &model[0][0]);

            // Clear the screen to black.
            gl::ClearColor(0.0, 0.0, 0.0, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);

            // Draw the triangles described by the elements array.
            gl::DrawElements(gl::TRIANGLES, ELEMENTS.len() as GLint, gl::UNSIGNED_INT,
                             ptr::null());
        }

        window.swap_buffers();
    }

    unsafe {
        gl::DeleteTextures(2, textures.as_ptr());
        gl::DeleteProgram(shader_program);
        gl::DeleteShader(fragment_shader);
        gl::DeleteShader(vertex_shader);
        gl::DeleteBuffers(1, &ebo);
        gl::DeleteBuffers(1, &vbo);
        gl::DeleteVertexArrays(1, &vao);
    }
}

fn handle_window_event(window: &mut glfw::Window, event: glfw::WindowEvent) {
    use glfw::{Action, Key, WindowEvent};

    match event {
        WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
            window.set_should_close(true);
        },
        _ => {},
    }
}
<|endoftext|>


<commit_before>// Copyright 2013 The gl-rs developers. For a full listing of the authors,
// refer to the AUTHORS file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate gl;
extern crate glfw;

use glfw::{Context, OpenGlProfileHint, WindowHint, WindowMode};

fn main() {
    let glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();

    // Choose a GL profile that is compatible with OS X 10.7+
    glfw.window_hint(WindowHint::ContextVersion(3, 2));
    glfw.window_hint(WindowHint::OpenglForwardCompat(true));
    glfw.window_hint(WindowHint::OpenglProfile(OpenGlProfileHint::Core));

    let (window, _) = glfw.create_window(800, 600, "OpenGL", WindowMode::Windowed)
        .expect("Failed to create GLFW window.");

    // It is essential to make the context current before calling `gl::load_with`.
    window.make_current();

    // Load the OpenGL function pointers
    gl::load_with(|s| window.get_proc_address(s));

    while !window.should_close() {
        // Poll events
        glfw.poll_events();

        // Clear the screen to black
        unsafe {
            gl::ClearColor(0.3, 0.3, 0.3, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);
        }

        // Swap buffers
        window.swap_buffers();
    }
}
<commit_msg>Fix static_basic example to work with latest rust and glfw-rs<commit_after>// Copyright 2013 The gl-rs developers. For a full listing of the authors,
// refer to the AUTHORS file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate gl;
extern crate glfw;

use glfw::{Context, OpenGlProfileHint, WindowHint, WindowMode};

fn main() {
    let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();

    // Choose a GL profile that is compatible with OS X 10.7+
    glfw.window_hint(WindowHint::ContextVersion(3, 2));
    glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
    glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));

    let (mut window, _) = glfw.create_window(800, 600, "OpenGL", WindowMode::Windowed)
        .expect("Failed to create GLFW window.");

    // It is essential to make the context current before calling `gl::load_with`.
    window.make_current();

    // Load the OpenGL function pointers
    gl::load_with(|s| window.get_proc_address(s));

    while !window.should_close() {
        // Poll events
        glfw.poll_events();

        // Clear the screen to black
        unsafe {
            gl::ClearColor(0.3, 0.3, 0.3, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);
        }

        // Swap buffers
        window.swap_buffers();
    }
}
<|endoftext|>